文章摘要
这段代码float3 normal = UnpackNormal(tex2D(_BumpMap, uv))是Unity中从法线贴图采样并还原切线空间法线的标准写法。tex2D采样法线贴图的颜色值,UnpackNormal将其转换为[-1,1]范围的切线空间法线向量。Unity内置的实现通过R、G通道计算XY分量,并用勾股定理还原Z分量。法线贴图需设置为Normal Map类型,推荐使用DXT5nm等专用压缩格式。解包后的法线需通过TBN矩阵转换到世界空间参与光照计算。该技术为PBR渲染提供精细的表面凹凸细节。
这行代码:
float3 normal = UnpackNormal(tex2D(_BumpMap, uv));
这是在Unity标准PBR Shader中从法线贴图中采样并还原切线空间法线的典型写法。下面我会详细剖析它的原理、源码实现和注意事项。
1. 作用说明
tex2D(_BumpMap, uv)
:用uv坐标从法线贴图采样,得到一个颜色值(通常是RGB)。UnpackNormal(...)
:把采样到的颜色值(通常范围是[0,1])转换为切线空间的法线向量(范围[-1,1])。
2. Unity内置UnpackNormal源码
在Unity的内置头文件UnityCG.cginc
中,UnpackNormal
的实现如下:
inline float3 UnpackNormal( float4 packednormal )
{
// 法线贴图的RGB通常存储在[0,1],需要还原到[-1,1]
float3 normal;
normal.xy = packednormal.xy * 2 - 1;
// Z分量用勾股定理还原
normal.z = sqrt(1.0 - saturate(dot(normal.xy, normal.xy)));
return normal;
}
解释:
- 法线贴图的R、G分量分别代表X、Y方向,存储时是[0,1],解包后变成[-1,1]。
- Z分量(垂直于表面)用
z = sqrt(1 - x^2 - y^2)
还原,保证法线长度为1。 - 这样得到的
normal
是切线空间法线,用于后续的PBR光照计算。
3. 法线贴图的存储格式
- Unity默认的法线贴图是切线空间法线贴图(Tangent Space Normal Map)。
- 贴图的R、G通道存储X、Y分量,B通道有时存储Z分量,有时不用(节省带宽)。
- 采样后需要解包,还原为[-1,1]的向量。
4. 性能与注意事项
UnpackNormal
的计算非常快,只有简单的乘法和开方。- 如果法线贴图压缩格式不对(如未设置为Normal Map类型),采样结果会出错,导致表面光照异常。
- 在移动端,建议用DXT5nm或ETC2等专用法线贴图压缩格式,避免精度损失。
5. 实际案例
Shader片段:
sampler2D _BumpMap;
float2 uv = ...; // 当前像素的UV
float3 tangentNormal = UnpackNormal(tex2D(_BumpMap, uv));
// 后续将tangentNormal变换到世界空间或视空间参与光照计算
效果:
- 让表面产生细致的凹凸感(bump),即使模型本身没有高模细节。
6. 进阶:法线空间变换
- 切线空间法线需要通过TBN矩阵(Tangent, Bitangent, Normal)变换到世界空间或视空间,才能和光照方向正确计算。
- Unity内置函数
UnityObjectToWorldNormal
、UnityObjectToWorldDir
等可辅助完成空间变换。
总结
UnpackNormal(tex2D(_BumpMap, uv))
是Unity标准法线贴图采样与解包的标准写法。- 其本质是把贴图中的颜色值还原为切线空间的法线向量,为PBR等高级光照提供细节支持。
7、TBN矩阵推导、法线贴图压缩对比、世界空间法线贴图处理等更深入内容
下面我将分别详细讲解:
1. TBN矩阵推导
1.1 为什么需要TBN矩阵?
- 法线贴图通常存储的是切线空间法线(Tangent Space Normal),即以模型表面为基准的局部坐标系。
- 光照计算通常在世界空间或视空间进行。
- 所以需要把切线空间的法线变换到世界空间/视空间,这就需要TBN矩阵。
1.2 TBN矩阵的组成
- T:Tangent(切线),沿着UV的U方向。
- B:Bitangent(副切线/副法线),沿着UV的V方向。
- N:Normal(法线),模型表面法线。
这三个向量在每个顶点都可以通过模型数据或顶点着色器计算得到,且三者正交。
1.3 TBN矩阵的推导
设有顶点P0、P1、P2及其UV坐标UV0、UV1、UV2。
-
计算三角形的两个边:
Edge1 = P1 - P0 Edge2 = P2 - P0 DeltaUV1 = UV1 - UV0 DeltaUV2 = UV2 - UV0
-
计算Tangent和Bitangent:
float f = 1.0 / (DeltaUV1.x * DeltaUV2.y - DeltaUV2.x * DeltaUV1.y); Tangent = f * (DeltaUV2.y * Edge1 - DeltaUV1.y * Edge2); Bitangent = f * (-DeltaUV2.x * Edge1 + DeltaUV1.x * Edge2);
-
法线N通常由模型自带。
-
归一化T、B、N,组成TBN矩阵:
| T.x B.x N.x | | T.y B.y N.y | | T.z B.z N.z |
1.4 在Shader中的应用
- 顶点着色器传递T、B、N到片元着色器。
- 片元着色器中用TBN矩阵将切线空间法线变换到世界空间:
float3 worldNormal = mul(tangentSpaceNormal, float3x3(T, B, N));
2. 法线贴图压缩对比
2.1 常见压缩格式
- DXT5nm(BC5):PC平台常用,R通道存X,A通道存Y,Z分量重建。
- ETC2:移动端OpenGL ES 3.0+,支持两通道压缩,适合法线贴图。
- RGBA8:无损,空间大,移动端慎用。
- ASTC:高端移动端,支持多通道高质量压缩。
2.2 压缩对比
格式 | 通道 | 质量 | 体积 | 适用平台 | 备注 |
---|---|---|---|---|---|
DXT5nm | 2 | 较高 | 小 | PC/主机 | 需特殊导入设置 |
ETC2 | 2 | 较高 | 小 | 移动端OpenGL ES3 | |
RGBA8 | 4 | 无损 | 大 | 全平台 | 占用大 |
ASTC | 2-4 | 极高 | 可调 | 高端移动/PC | 需硬件支持 |
2.3 Unity设置建议
- 导入法线贴图时,Texture Type设为Normal Map,Unity会自动选择合适的压缩方式。
- PC建议DXT5nm,移动端建议ETC2或ASTC。
- 压缩会带来精度损失,极端情况下(如大面积蓝色/紫色)可能出现伪影。
3. 世界空间法线贴图处理
3.1 区别
- 切线空间法线贴图:每个像素的法线是相对于表面切线空间的。
- 世界空间法线贴图:每个像素的法线直接存储在世界空间,无需TBN变换。
3.2 使用场景
- 世界空间法线贴图常用于三维体积贴图、投影贴图、特殊地形效果等。
- 适合法线方向不随模型变形而变化的场景。
3.3 Shader处理方式
- 切线空间法线贴图:采样后需TBN变换。
- 世界空间法线贴图:采样后直接用,无需TBN。
示例:
// 切线空间法线
float3 tangentNormal = UnpackNormal(tex2D(_BumpMap, uv));
float3 worldNormal = mul(tangentNormal, float3x3(T, B, N));
// 世界空间法线
float3 worldNormal = UnpackNormal(tex2D(_WorldNormalMap, uv));
3.4 注意事项
- 世界空间法线贴图在模型变形(如骨骼动画)时会出现法线错位。
- 贴图制作时需保证法线方向与世界坐标系一致。
4. 总结与建议
- TBN矩阵是切线空间法线转世界/视空间的桥梁,推导公式需掌握。
- 法线贴图压缩要兼顾质量和平台,Unity自动处理大部分情况,但特殊需求需手动调整。
- 世界空间法线贴图适合静态、特殊效果,动态模型优先用切线空间法线贴图。