Phong Shader高光不随相机移动的代码错误排查
Phong着色器高光不随相机移动问题修复
问题现象
- 漫反射光照计算逻辑运行正常,高光部分存在异常,不会随相机移动产生对应变化
- 单侧视角效果:

- 另一侧视角效果:

- 开发者原以为已将顶点转换至观察空间,但代码仍存在错误。
原始问题代码
顶点着色器
#version 330 #extension GL_ARB_explicit_attrib_location : enable layout(location=0) in vec3 aPosition; layout(location=1) in vec3 aNormal; out vec3 vPosition; out vec3 vNormal; uniform mat4 uModel; uniform mat4 uView; uniform mat4 uProjection; void main(void) { vPosition = vec3(uModel * vec4(aPosition,1.0f)); vNormal = vec3(uModel * vec4(aNormal, 0.0f)); gl_Position = uProjection * uView * uModel * vec4(aPosition, 1.0); }
片元着色器
#version 330 out vec4 FragColor; in vec3 vPosition; in vec3 vNormal; uniform mat4 uView; uniform vec3 uColor; uniform vec3 uLightpositions[10]; uniform vec3 uLightcolors[10]; uniform float uPhongSpecular; void main(void) { FragColor = vec4(0.4*uColor, 1.0);//Ambient Value for(int i = 0; i < 5; i++){ vec3 lVec = normalize(uLightpositions[i] - vPosition); vec3 nVec = normalize(vNormal); float diffuse = max(dot(lVec,nVec), 0); FragColor += 0.5* vec4(uLightcolors[i] * diffuse,0.0f); vec3 rVec = normalize(reflect(lVec,nVec)); vec3 vVec = -normalize(vec3(uView * vec4(vPosition,1.0))); float specular = 0; if(dot(rVec,vVec) < 0.0) { specular = pow(max(dot(-rVec,vVec),0),uPhongSpecular); } FragColor += 0.2*vec4(uLightcolors[i] * specular,0.0f); } }
问题根因
核心问题是光照计算涉及的所有向量没有统一在同一个坐标空间,另外存在3处逻辑错误:
- 顶点着色器输出的
vPosition、vNormal仅完成了模型空间到世界空间的转换,并没有转到预期的观察空间。 - 片元着色器计算视线向量
vVec时,给世界空间的vPosition额外乘了一次uView矩阵,导致算出的视线向量处于观察空间,和其余在世界空间计算的光线方向、法线、反射向量坐标系不匹配,高光结果自然无法随相机正确变化。 - 反射向量计算逻辑冗余易错:
reflect接口要求传入的第一个参数是从光源指向表面的入射方向,原代码直接传入了从表面指向光源的lVec,后续又对反射向量取反,平白增加出错概率。 - 漫反射、高光项的alpha通道值写死为0,会导致这两部分光照结果透明失效。
修复方案
选择任意一种统一坐标空间的方案即可,推荐第二种观察空间计算方案,逻辑更简洁:
方案1:统一在世界空间计算(改动量小)
不需要修改顶点着色器,仅调整片元着色器逻辑:
- 删掉视线向量计算时多余的
uView矩阵乘法,额外传入相机的世界坐标uCameraWorldPos,视线向量直接计算为normalize(uCameraWorldPos - vPosition) - 修正反射向量计算逻辑,传入
-lVec作为reflect的第一个参数,去掉后续对rVec的取反操作 - 把漫反射、高光项的alpha值从0改为1.0
方案2:统一在观察空间计算(逻辑更顺)
观察空间下相机固定在坐标原点,不需要额外传入相机位置参数:
- 修改顶点着色器,输出观察空间下的顶点位置和法线:
void main(void) { vec4 worldPos = uModel * vec4(aPosition,1.0f); // 输出观察空间下的顶点位置 vPosition = vec3(uView * worldPos); // 法线用模型视图矩阵的逆转置矩阵转换,避免非等比缩放导致法线变形,无非等比缩放时可简化为mat3(uView*uModel)*aNormal vNormal = mat3(transpose(inverse(uView * uModel))) * aNormal; gl_Position = uProjection * uView * worldPos; }
- 修改片元着色器,统一所有向量到观察空间,修正反射和alpha逻辑:
void main(void) { FragColor = vec4(0.4*uColor, 1.0);//环境光 for(int i = 0; i < 5; i++){ // 光源位置转到观察空间 vec3 lightPosView = vec3(uView * vec4(uLightpositions[i], 1.0)); vec3 lVec = normalize(lightPosView - vPosition); vec3 nVec = normalize(vNormal); // 漫反射计算 float diffuse = max(dot(lVec,nVec), 0); FragColor += 0.5* vec4(uLightcolors[i] * diffuse,1.0f); // 高光计算 vec3 rVec = normalize(reflect(-lVec,nVec)); // 观察空间下相机在原点,视线方向为片元位置指向原点 vec3 vVec = normalize(-vPosition); float specular = pow(max(dot(rVec,vVec),0),uPhongSpecular); FragColor += 0.2*vec4(uLightcolors[i] * specular,1.0f); } }
内容的提问来源于stack exchange,提问作者Fitzeputz
相关产品推荐
相关产品推荐

