You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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处逻辑错误:

  1. 顶点着色器输出的vPosition、vNormal仅完成了模型空间到世界空间的转换,并没有转到预期的观察空间。
  2. 片元着色器计算视线向量vVec时,给世界空间的vPosition额外乘了一次uView矩阵,导致算出的视线向量处于观察空间,和其余在世界空间计算的光线方向、法线、反射向量坐标系不匹配,高光结果自然无法随相机正确变化。
  3. 反射向量计算逻辑冗余易错:reflect接口要求传入的第一个参数是从光源指向表面的入射方向,原代码直接传入了从表面指向光源的lVec,后续又对反射向量取反,平白增加出错概率。
  4. 漫反射、高光项的alpha通道值写死为0,会导致这两部分光照结果透明失效。

修复方案

选择任意一种统一坐标空间的方案即可,推荐第二种观察空间计算方案,逻辑更简洁:

方案1:统一在世界空间计算(改动量小)

不需要修改顶点着色器,仅调整片元着色器逻辑:

  • 删掉视线向量计算时多余的uView矩阵乘法,额外传入相机的世界坐标uCameraWorldPos,视线向量直接计算为normalize(uCameraWorldPos - vPosition)
  • 修正反射向量计算逻辑,传入-lVec作为reflect的第一个参数,去掉后续对rVec的取反操作
  • 把漫反射、高光项的alpha值从0改为1.0

方案2:统一在观察空间计算(逻辑更顺)

观察空间下相机固定在坐标原点,不需要额外传入相机位置参数:

  1. 修改顶点着色器,输出观察空间下的顶点位置和法线:
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;
}
  1. 修改片元着色器,统一所有向量到观察空间,修正反射和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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.01 02:31:05