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

Love2D 3D渲染引擎法线贴图光照异常问题排查

Love2D 3D渲染引擎法线贴图异常修复方案

核心错误排查

  • 视线方向计算反向:观察空间下相机默认位于坐标原点,视线方向为片元指向相机,原有代码计算为相机指向片元,方向完全颠倒,直接导致光照沿相机位置分割、高光计算错误。
  • 法线/切线变换矩阵使用错误:法线、切线属于方向向量,不能直接使用modelView矩阵变换,原有代码未使用专门的法线矩阵(modelView矩阵的逆转置3x3矩阵),导致模型存在缩放时法线方向扭曲,这也是归一化顶点法线后出现黑屏的核心原因。
  • TBN矩阵未做正交化处理:按三角形面平均计算得到的顶点切线,无法保证与顶点法线严格垂直,直接叉乘得到的副切线会导致TBN三个基向量不正交,变换切线空间法线时出现方向偏移,触发三角形面之间光照交替跳变。
  • 反射计算入射方向不符合接口要求:GLSL内置reflect函数要求入射光线向量指向表面,原有代码传入的是从片元指向光源的向量,方向反向,导致高光计算完全错误。

修复后代码

顶点着色器

// 顶点属性输入
attribute vec3 VertexPosition;
attribute vec3 VertexNormal;
attribute vec3 VertexTangent;
attribute vec2 TexCoord;

// Uniform矩阵
uniform mat4 modelMatrix;
uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;
uniform mat3 normalMatrix; // 预计算值:transpose(inverse(mat3(viewMatrix * modelMatrix)))

// 传递给片元着色器的 varying 变量
varying vec3 fragmentPosition_viewSpace;
varying vec2 texture_coords;
varying mat3 tbnMatrix_viewSpace;

void main() {
    texture_coords = TexCoord;
    // 计算观察空间片元位置
    vec4 viewSpacePos = viewMatrix * modelMatrix * vec4(VertexPosition, 1.0);
    fragmentPosition_viewSpace = viewSpacePos.xyz;
    gl_Position = projectionMatrix * viewSpacePos;

    // 用法线矩阵正确变换T、N向量到观察空间
    vec3 T = normalize(normalMatrix * VertexTangent);
    vec3 N = normalize(normalMatrix * VertexNormal);
    // Gram-Schmidt 重正交,保证T与N严格垂直,消除平均切线带来的正交误差
    T = normalize(T - dot(T, N) * N);
    // 按右手坐标系叉乘计算副切线B
    vec3 B = cross(N, T);
    // 构造观察空间TBN矩阵
    tbnMatrix_viewSpace = mat3(T, B, N);
}

片元着色器

varying vec3 fragmentPosition_viewSpace;
varying vec2 texture_coords;
varying mat3 tbnMatrix_viewSpace;

// 纹理采样器
uniform sampler2D diffuseTextureSampler;
uniform sampler2D normalTextureSampler;
uniform sampler2D specularTextureSampler;

// 光照参数
uniform vec3 lightPosition_worldSpace;
uniform vec3 lightColor;
uniform float lightIntensity;
uniform float specularLobeFactor;
uniform mat4 viewMatrix;

// 对齐原有项目的纹理采样封装
vec4 Texel(sampler2D samp, vec2 uv) {
    return texture2D(samp, uv);
}

void main() {
    vec4 diffuseColor = Texel(diffuseTextureSampler, texture_coords);
    vec4 normalColor = Texel(normalTextureSampler, texture_coords);
    vec4 specularColor = Texel(specularTextureSampler, texture_coords);

    // 采样切线空间法线,映射到[-1, 1]区间后通过TBN矩阵变换到观察空间
    vec3 tangentNormal = normalize(normalColor.rgb * 2.0 - 1.0);
    // 若使用DX格式法线贴图(Y轴朝下),取消下一行注释
    // tangentNormal.y = -tangentNormal.y;
    vec3 normal_viewSpace = normalize(tbnMatrix_viewSpace * tangentNormal);

    // 计算观察空间光照方向
    vec3 lightPosition_viewSpace = (viewMatrix * vec4(lightPosition_worldSpace, 1.0)).xyz;
    vec3 lightDirection_viewSpace = normalize(lightPosition_viewSpace - fragmentPosition_viewSpace);
    // 修复视线方向:从片元指向原点(相机位置)
    vec3 viewDirection_viewSpace = normalize(-fragmentPosition_viewSpace);

    // 光照衰减计算
    vec3 lightColorIntensity = lightColor * lightIntensity;
    float distanceFromLight = length(lightPosition_viewSpace - fragmentPosition_viewSpace);
    float attenuation = 1.0 / (distanceFromLight * distanceFromLight);

    // 漫反射光照
    float diffuseStrength = max(dot(normal_viewSpace, lightDirection_viewSpace), 0.0);
    vec3 diffuseLight = lightColorIntensity * diffuseStrength * attenuation;

    // 修复高光反射计算:入射光线方向指向表面,传入-lightDirection_viewSpace
    vec3 reflectDirection = reflect(-lightDirection_viewSpace, normal_viewSpace);
    float specularStrength = pow(max(dot(viewDirection_viewSpace, reflectDirection), 0.0), specularLobeFactor);
    vec3 specularLight = lightColorIntensity * specularStrength * attenuation;

    // 合并输出颜色
    gl_FragColor.rgb = diffuseColor.rgb * diffuseLight + specularColor.rgb * specularLight;
    gl_FragColor.a = diffuseColor.a;
}

额外注意事项

  • 顶点切线计算阶段,需确保每个三角形的切线方向与UV坐标的U轴严格对齐,共享顶点的切线需要按三角形面积加权平均,避免相邻三角形切线方向差异过大导致的光照硬边。
  • 若项目中所有模型都只做均匀缩放,可直接使用mat3(viewMatrix * modelMatrix)代替预计算的normalMatrix,但使用逆转置法线矩阵是通用兼容方案,不会出现法线方向错误。
  • 若修复后仍出现法线方向上下颠倒的问题,检查使用的法线贴图格式,OpenGL格式法线Y轴朝上,DX格式法线Y轴朝下,按代码中注释调整Y分量符号即可。

内容的提问来源于stack exchange,提问作者Razorboot

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 05:39:16