切换Gouraud与Phong着色时立方体全紫问题求助
嘿,我之前也踩过WebGL切换Phong着色的坑,咱们一步步来排查你遇到的「全紫色无光照」问题:
Gouraud和Phong着色的本质区别是光照计算的阶段:Gouraud在顶点着色器算完颜色再插值,Phong要在片元着色器里基于插值后的顶点位置、法向量重新计算光照。切换后失效,大概率是切换过程中遗漏了Phong必需的配置。
1. 切换程序时Uniform变量未重新绑定
你切换到program2(Phong程序)后,有没有重新传递光照相关的Uniform?比如光源位置、视图位置、模型矩阵、法向量矩阵这些。如果只切换了程序,没把这些关键参数传给Phong着色器,片元着色器会用未初始化的默认值,最后就只会输出材质的默认颜色(也就是你看到的紫色)。
举个例子,切换按钮的处理逻辑要补上Uniform设置:
function switchShading() { flag = !flag; if (flag) { gl.useProgram(program); // 切回Gouraud setGouraudUniforms(); // 之前的Gouraud配置函数 } else { gl.useProgram(program2); // 切换到Phong // !!必须重新设置Phong需要的所有Uniform setPhongUniforms(); } }
2. Phong着色器的法向量处理错误
Phong依赖每个片元的法向量做光照计算,顶点着色器里必须把法向量做正确的空间变换:要用模型视图矩阵的逆转置矩阵(normalMatrix)变换法向量,否则法向量会被拉伸变形,变成零向量后光照计算直接失效。
检查你的Phong顶点着色器:
attribute vec3 vNormal; varying vec3 fNormal; // 传给片元着色器的插值法向量 uniform mat4 modelViewMatrix; uniform mat3 normalMatrix; // 必须是模型视图矩阵的逆转置 void main() { // 正确做法:用normalMatrix变换后再归一化 fNormal = normalize(normalMatrix * vNormal); gl_Position = modelViewMatrix * projectionMatrix * vec4(vPosition, 1.0); }
如果没传递normalMatrix,或者计算逆转置的逻辑错了,法向量就会失效,光照等于零。
3. 片元着色器的光照计算逻辑错误
检查Phong片元着色器里的光照公式,常见坑点:
- 光照方向搞反(比如用
fPosition - lightPos而不是lightPos - fPosition) - 法向量或光照方向没做归一化,导致dot乘积异常
- 空间不统一(比如光源在世界空间,而顶点位置在视图空间)
举个正确的Phong片元着色器示例:
varying vec3 fNormal; varying vec3 fPosition; uniform vec3 lightPos; uniform vec3 viewPos; uniform vec3 materialColor; void main() { vec3 normal = normalize(fNormal); vec3 lightDir = normalize(lightPos - fPosition); // 漫反射计算 float diff = max(dot(normal, lightDir), 0.0); vec3 diffuse = diff * materialColor; // 环境光兜底 vec3 ambient = 0.1 * materialColor; // 高光计算(可选) vec3 viewDir = normalize(viewPos - fPosition); vec3 reflectDir = reflect(-lightDir, normal); float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32.0); vec3 specular = spec * vec3(1.0); gl_FragColor = vec4(ambient + diffuse + specular, 1.0); }
可以先把ambient改成白色,测试如果全白,说明光照计算确实没生效,只剩环境光。
4. 切换程序后顶点属性未重新绑定
不同的着色器程序,顶点属性(比如vPosition、vNormal)的location可能不一样。切换到program2后,需要重新获取属性位置并绑定缓冲区,否则Phong着色器拿不到顶点数据。
示例代码:
// 初始化时提前获取两个程序的属性位置 const programAttribs = { position: gl.getAttribLocation(program, "vPosition"), normal: gl.getAttribLocation(program, "vNormal") }; const program2Attribs = { position: gl.getAttribLocation(program2, "vPosition"), normal: gl.getAttribLocation(program2, "vNormal") }; // 切换到Phong时重新绑定属性 gl.useProgram(program2); // 绑定法向量缓冲区 gl.bindBuffer(gl.ARRAY_BUFFER, nBuffer); gl.vertexAttribPointer(program2Attribs.normal, 3, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(program2Attribs.normal); // 同理绑定顶点位置缓冲区 gl.bindBuffer(gl.ARRAY_BUFFER, vBuffer); gl.vertexAttribPointer(program2Attribs.position, 3, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(program2Attribs.position);
用Chrome DevTools的WebGL Inspector工具,切换到Phong程序后:
- 查看Uniform变量的值是否正确(比如lightPos是不是你设置的坐标)
- 检查顶点属性是否有数据传入
- 单步调试片元着色器,看光照计算的每一步结果是否符合预期
内容的提问来源于stack exchange,提问作者user9516512

