我正在尝试在片段着色器中实现 Oren-Nayar 照明,如下所示。
但是,我在地形上得到了一些奇怪的照明效果,如下所示。
我目前正在向着色器发送“视图方向”统一作为相机的“前”矢量。我不确定这是否正确,因为移动相机会改变伪影。
将“前”矢量乘以 MVP 矩阵会得到更好的结果,但从某些角度观察地形时,伪影仍然非常明显。在黑暗区域和屏幕边缘周围尤其明显。
是什么原因导致了这种影响?
工件示例
场景应该是什么样子
顶点着色器
#version 450
layout(location = 0) in vec3 position;
layout(location = 1) in vec3 normal;
out VS_OUT {
vec3 normal;
} vert_out;
void main() {
vert_out.normal = normal;
gl_Position = vec4(position, 1.0);
}
镶嵌控制着色器
#version 450
layout(vertices = 3) out;
in VS_OUT {
vec3 normal;
} tesc_in[];
out TESC_OUT {
vec3 normal;
} tesc_out[];
void main() {
if(gl_InvocationID == 0) {
gl_TessLevelInner[0] = 1.0;
gl_TessLevelInner[1] = 1.0;
gl_TessLevelOuter[0] = 1.0;
gl_TessLevelOuter[1] = 1.0;
gl_TessLevelOuter[2] = 1.0;
gl_TessLevelOuter[3] = 1.0;
}
tesc_out[gl_InvocationID].normal = tesc_in[gl_InvocationID].normal;
gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;
}
镶嵌评估着色器
#version 450
layout(triangles, equal_spacing) in;
in TESC_OUT {
vec3 normal;
} tesc_in[];
out TESE_OUT {
vec3 normal;
float height;
vec4 shadow_position;
} tesc_out;
uniform mat4 model_view;
uniform mat4 model_view_perspective;
uniform mat3 normal_matrix;
uniform mat4 depth_matrix;
vec3 lerp(vec3 v0, vec3 v1, vec3 v2) {
return (
(vec3(gl_TessCoord.x) * v0) +
(vec3(gl_TessCoord.y) * v1) +
(vec3(gl_TessCoord.z) * v2)
);
}
vec4 lerp(vec4 v0, vec4 v1, vec4 v2) {
return (
(vec4(gl_TessCoord.x) * v0) +
(vec4(gl_TessCoord.y) * v1) +
(vec4(gl_TessCoord.z) * v2)
);
}
void main() {
gl_Position = lerp(
gl_in[0].gl_Position,
gl_in[1].gl_Position,
gl_in[2].gl_Position
);
tesc_out.normal = normal_matrix * lerp(
tesc_in[0].normal,
tesc_in[1].normal,
tesc_in[2].normal
);
tesc_out.height = gl_Position.y;
tesc_out.shadow_position = depth_matrix * gl_Position;
gl_Position = model_view_perspective * gl_Position;
}
片段着色器
#version 450
in TESE_OUT {
vec3 normal;
float height;
vec4 shadow_position;
} frag_in;
out vec4 colour;
uniform vec3 view_direction;
uniform vec3 light_position;
#define PI 3.141592653589793
void main() {
const vec3 ambient = vec3(0.1, 0.1, 0.1);
const float roughness = 0.8;
const vec4 water = vec4(0.0, 0.0, 0.8, 1.0);
const vec4 sand = vec4(0.93, 0.87, 0.51, 1.0);
const vec4 grass = vec4(0.0, 0.8, 0.0, 1.0);
const vec4 ground = vec4(0.49, 0.27, 0.08, 1.0);
const vec4 snow = vec4(0.9, 0.9, 0.9, 1.0);
if(frag_in.height == 0.0) {
colour = water;
} else if(frag_in.height < 0.2) {
colour = sand;
} else if(frag_in.height < 0.575) {
colour = grass;
} else if(frag_in.height < 0.8) {
colour = ground;
} else {
colour = snow;
}
vec3 normal = normalize(frag_in.normal);
vec3 view_dir = normalize(view_direction);
vec3 light_dir = normalize(light_position);
float NdotL = dot(normal, light_dir);
float NdotV = dot(normal, view_dir);
float angleVN = acos(NdotV);
float angleLN = acos(NdotL);
float alpha = max(angleVN, angleLN);
float beta = min(angleVN, angleLN);
float gamma = dot(view_dir - normal * dot(view_dir, normal), light_dir - normal * dot(light_dir, normal));
float roughnessSquared = roughness * roughness;
float roughnessSquared9 = (roughnessSquared / (roughnessSquared + 0.09));
// calculate C1, C2 and C3
float C1 = 1.0 - 0.5 * (roughnessSquared / (roughnessSquared + 0.33));
float C2 = 0.45 * roughnessSquared9;
if(gamma >= 0.0) {
C2 *= sin(alpha);
} else {
C2 *= (sin(alpha) - pow((2.0 * beta) / PI, 3.0));
}
float powValue = (4.0 * alpha * beta) / (PI * PI);
float C3 = 0.125 * roughnessSquared9 * powValue * powValue;
// now calculate both main parts of the formula
float A = gamma * C2 * tan(beta);
float B = (1.0 - abs(gamma)) * C3 * tan((alpha + beta) / 2.0);
// put it all together
float L1 = max(0.0, NdotL) * (C1 + A + B);
// also calculate interreflection
float twoBetaPi = 2.0 * beta / PI;
float L2 = 0.17 * max(0.0, NdotL) * (roughnessSquared / (roughnessSquared + 0.13)) * (1.0 - gamma * twoBetaPi * twoBetaPi);
colour = vec4(colour.xyz * (L1 + L2), 1.0);
}
我知道这是一个长期的死线程,但我一直遇到同样的问题(几年了),终于找到了解决方案......
可以通过固定表面法线的方向以匹配多边形缠绕方向来部分解决,但您也可以通过更改以下两条线来摆脱着色器中的伪影......
float angleVN = acos(cos_nv);
float angleLN = acos(cos_nl);
对此...
float angleVN = acos(clamp(cos_nv, -1.0, 1.0));
float angleLN = acos(clamp(cos_nl, -1.0, 1.0));
然后!
首先,我使用我的视图/法线/光矢量将您的片段着色器插入我的渲染器中,它运行良好。所以问题必须在于你计算这些向量的方式。
接下来,您说您将view_dir
设置为相机的前矢量。我假设你的意思是“相机在世界空间中的前矢量”,这是不正确的。由于您在相机空间中使用矢量计算点积,因此view_dir
也必须在相机空间中。也就是说,vec3(0,0,1)
将是一种简单的检查方法。如果它有效 - 我们发现了你的问题。
但是,在进行透视投影时,使用 (0,0,1)
作为视图方向并不完全正确,因为从片段到相机的方向取决于片段在屏幕上的位置。正确的公式是 view_dir = normalize(-pos),
其中 pos
是片段在相机空间中的位置(即在没有投影的情况下应用模型视图矩阵)。此外,此数量现在仅取决于屏幕上的片段位置,因此您可以将其计算为:
view_dir = normalize(vec3(-(gl_FragCoord.xy - frame_size/2) / (frame_width/2), flen))
Flen
是相机的焦距,您可以计算为flen = cot(fovx/2)。
我真的不明白片段着色器是如何工作的。 我知道 顶点着色器每个顶点运行一次 片段着色器对每个片段运行一次 由于片段着色器不是按顶点而是按片段工作,它如何将数据发送到片段着色器?顶点的数量和片段的数量不相等。 它如何决定哪个碎片属于哪个顶点?
我的目标是使用c和OpenGL进行简单的沙子模拟。现在我的计划是拥有一个2d像素颜色数组和一个相同大小的纹理。为了模拟沙子,我将根据沙子坐标和它必须移动的位置来更新数组。我正在考虑将2d像素阵列发送到片段着色器,并使用阵列上的颜色更新那里的纹理。问题是我找不到改变纹理上像素颜色的方法。 那么我如何改变纹理上特定坐标处像素的颜色呢?这样做真的可行吗?如果没有,还有什么办法?
我希望在2D OpenGL应用程序上实现着色器。我的计划是将场景渲染到帧缓冲区对象,然后使用着色器将该帧缓冲区对象渲染到屏幕。 这是我绘制到帧缓冲区对象的场景,然后从那里绘制到屏幕。使用箭头键可以让月亮四处移动(我很自豪!) 但是,当我尝试使用着色器程序将帧缓冲区对象渲染到屏幕上时,我得到以下结果: 这是非常可悲的。这个片段着色器是我从一个教程中得到的,我相信问题一定是统一变量。 以下是片段着色器
我对OpenGL有点生疏,我已经实现了一个场景的照明,场景中有小行星围绕行星旋转的实例。出于某种原因,我得到的唯一输出似乎是场景的环境照明。我无法使漫反射或镜面反射工作。 这是现场的一张照片(很难看到…)仅限环境 片段着色器代码如下所示。FragPos、Normal和TexCoords都是你所期待的。纹理_diffuse1是行星/小行星纹理,具体取决于使用的着色器。LightPos可以移动,它由图
我似乎无法理解从顶点到像素的OpenGL管道过程。 有人能告诉我顶点法线在这两种着色技术中有多重要吗?据我所知,在gouraud中,在每个顶点计算照明,然后在顶点之间的多边形上插值结果颜色(在光栅化之前,这是在片段操作中完成的吗?),phong着色包括首先插值顶点法线,然后计算每个法线上的照明。 另一件事是,当凹凸贴图应用于一个平面(2个三角形)和一个砖纹理作为漫反射时,使用其相应的凹凸贴图,所有
OpenGL的渲染管线分为几个步骤。一个简单的OpenGL渲染管线将包含一个顶点着色器和一个片段着色器。 顶点着色器接收顶点数据,并且在程序最后赋值给gl_Position。然后,顶点将会被裁剪,转换和栅格化后作为像素输出。 片段(像素)进入片段着色器,进一步对片段操作并将结果的颜色赋值给gl_FragColor。顶点着色器调用多边形每个角的点(顶点=3D中的点),负责这些点的3D处理。片段(片度
我设法用OpenGL(OpenTK,因为我使用C#和Winforms)创建了一个明亮的场景,使用了以下片段着色器: 计划是,左边的球体是云层。纹理包含一个alpha通道,这应该会产生一个透明的球体,上面有可见的云。不要困惑:当一切恢复正常时,云球体将被移回地球上方。 在实现环境、漫反射和镜面照明之前,我使用了以下片段着色器: 这个着色器考虑了alpha通道,它确实按预期工作。重要的是,云和地球是独