URP 渲染实战 / 第 7 章 法线贴图与视差 / 案例 7.3
案例 7.3三种常见错误的实拍对比入门
目标:把“手写 rgb*2−1”和“Y 方向用反”这两个最常见的错误放在同一张图里,记住它们的长相,以后一眼就能认出来。
Assets/Cookbook/Ch07/NormalMistakes.shader(节选:frag)
half4 frag(Varyings i) : SV_Target { half4 packed = SAMPLE_TEXTURE2D(_BumpMap, sampler_BumpMap, i.uv); half3 normalTS; if (_Mistake > 0.5 && _Mistake < 1.5) { // 错误 1:照搬“rgb*2-1”。桌面平台法线贴图默认压成 DXT5nm/BC5, // 数据在 A/G(或 R/G)通道里,B 通道是 0,这样解出来的向量完全不对 normalTS = packed.rgb * 2.0 - 1.0; normalTS.xy *= _BumpScale; } else { normalTS = UnpackNormalScale(packed, _BumpScale); // 错误 2:贴图按 DirectX 约定(Y 朝下)烘焙,却直接给 Unity(OpenGL 约定,Y 朝上)用 if (_Mistake > 1.5 && _Mistake < 2.5) normalTS.y = -normalTS.y; } float3 bitangent = i.tangentWS.w * cross(i.normalWS, i.tangentWS.xyz); half3 normalWS = TransformTangentToWorld(normalTS, half3x3(i.tangentWS.xyz, bitangent, i.normalWS)); // 错误 3:忘了归一化(插值后的 TBN 和缩放过的 normalTS 都不是单位长度) if (_Mistake < 2.5) normalWS = normalize(normalWS); half3 albedo = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, i.uv).rgb; return half4(Ch07Shade(i.positionWS, normalWS, albedo, 0.45, 0.15, 1), 1); }
#ifdef FRAGMENT void main() { vec4 packed = texture(_BumpMap, uv); vec3 normalTS; if (_Mistake > 0.5 && _Mistake < 1.5) { // 错误 1:照搬“rgb*2-1”。桌面平台法线贴图默认压成 DXT5nm/BC5, // 数据在 A/G(或 R/G)通道里,B 通道是 0,这样解出来的向量完全不对。 // (网页版的法线贴图是未压缩的 RGB,这里先把 B 清零,模拟 Unity 桌面平台 BC5 采样到的数据) packed.b = 0.0; normalTS = packed.rgb * 2.0 - 1.0; normalTS.xy *= _BumpScale; } else { normalTS = UnpackNormalScale(packed, _BumpScale); // 错误 2:贴图按 DirectX 约定(Y 朝下)烘焙,却直接给 Unity(OpenGL 约定,Y 朝上)用 if (_Mistake > 1.5 && _Mistake < 2.5) normalTS.y = -normalTS.y; } vec3 bitangent = tangentWS.w * cross(normalWS, tangentWS.xyz); vec3 nWS = TransformTangentToWorld(normalTS, mat3(tangentWS.xyz, bitangent, normalWS)); // 错误 3:忘了归一化(插值后的 TBN 和缩放过的 normalTS 都不是单位长度) if (_Mistake < 2.5) nWS = normalize(nWS); vec3 albedo = texture(_BaseMap, uv).rgb; fragColor = vec4(Ch07Shade(positionWS, nWS, albedo, 0.45, 0.15, 1.0), 1.0); } #endif

rgb*2-1,在 DXT5nm 存储下 R 恒为 1、B 恒为 0,解出的法线朝一侧倒并且 z 为负,整体发暗、凹凸几乎消失;右:Y 方向取反,光从左上方照来,砖块却是下沿亮、上沿暗,凸起看起来像凹进墙里。中图的错误在编辑器里尤其隐蔽:如果你在 Android 上测试(可能走 ASTC 或 UNITY_NO_DXT5nm),手写的 rgb*2-1 有时“看起来是对的”,换到 PC 上就坏掉。永远使用 UnpackNormal 系列函数,让引擎根据平台选择解码路径。右图的错误则常发生在跨软件协作时,解决办法是在源头统一约定,而不是在 Shader 里加一堆开关。第三种错误“忘记归一化”在 _Mistake = 3 时可以看到:强度越大、表面越亮,高光也跟着变形,原理很简单——点积的结果被向量长度放大了。