webgl法线贴图核心是用纹理存储切线空间法线并经tbn矩阵变换后参与光照计算;需正确采样还原(rgb×2−1)、构建tbn、统一空间、归一化法线,并注意纹理格式与色彩空间设置。

WebGL 实现法线贴图(Normal Mapping)的核心是:用纹理存储表面法线方向,代替几何细分来模拟凹凸细节。关键不在“贴图本身”,而在着色器中如何正确采样、变换和使用这些法线。
法线贴图的原理与纹理准备
法线贴图是一张 RGB 纹理,其中每个像素的 R/G/B 通道分别对应切线空间下法线的 X/Y/Z 分量(通常 Z 指向表面外,范围 [0,1] 映射到 [-1,1])。常见格式是“OpenGL 风格”(Y 向上,绿色为正 Y),需确保模型导出时法线贴图生成方式匹配(如 Blender 中勾选 “Non-Color Data” 作为法线贴图纹理色彩空间)。
- 纹理必须启用 gl.UNSIGNED_BYTE 格式加载,且采样时需做 vec3 n = texture2D(uNormalMap, vUv).rgb * 2.0 - 1.0; 还原到 [-1,1]
- 避免直接用
gl.LINEAR插值法线——虽可接受,但建议搭配 MipMap 和各向异性过滤提升远距离表现(需扩展支持) - 法线贴图应与漫反射贴图共用 UV 坐标,否则会错位
切线空间构建与矩阵传递
顶点着色器需为每个顶点计算切线(T)、副法线(B)、法线(N)构成的 TBN 矩阵,并传给片元着色器。该矩阵把采样到的切线空间法线转换到世界或视图空间,用于光照计算。
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
- 切线可通过模型导出时携带,或在 CPU 端根据顶点位置/UV 自动计算(常用 MikkTSpace 算法);WebGL 中多数引擎(如 Three.js)已内置生成逻辑
- TBN 矩阵通常以三个 vec3 形式(
vTangent,vBitangent,vNormal)传入片元着色器,避免 3×3 矩阵乘法开销;也可传逆矩阵用于从世界转回切线空间(较少见) - 若使用视图空间光照,TBN 应先将法线变换到视图空间:用 modelView 矩阵分别变换 T/B/N,再归一化
片元着色器中的法线替换与光照
在片元着色器中,不再使用插值得到的顶点法线,而是采样法线贴图 → 还原为切线空间法线 → 通过 TBN 变换到所需空间 → 代入 Phong/Blinn-Phong 或 PBR 光照模型。
- 示例关键片段:
vec3 tangentNormal = texture2D(uNormalMap, vUv).rgb * 2.0 - 1.0;<br> vec3 worldNormal = normalize(tangentNormal.x * vTangent +<br> tangentNormal.y * vBitangent +<br> tangentNormal.z * vNormal); - 务必对变换后的法线再次 normalize() —— 插值和矩阵变换会破坏单位长度
- 配合环境光、漫反射、镜面反射时,所有光照向量(光源方向、视线方向)也需统一转换到同一空间(推荐世界或视图空间),否则结果错误
常见问题与调试技巧
法线贴图失效往往不是代码写错,而是空间不一致或数据未归一化。快速定位方法:
- 临时让片元着色器输出
gl_FragColor = vec4((worldNormal + 1.0) * 0.5, 1.0);—— 正常应看到蓝紫主调(Z>0),若偏红/绿说明切线或副法线方向反了 - 检查法线贴图是否被 sRGB 转换干扰:加载时设置
texture.image.colorSpace = 'srgb';并禁用自动转换(WebGL2)或明确指定gl.RGBA, gl.UNSIGNED_BYTE, gl.RGBA格式 - 若凹凸感“平”或“反向”,大概率是 Y 分量符号颠倒(DirectX 风格贴图需翻转绿色通道)或 TBN 构建时叉积顺序错误
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










