这是一个非常好的图形学问题,涉及法线变换的本质。让我从数学角度解释为什么法线矩阵需要这样定义。
法线变换的核心问题
法线(Normal)是 3D 渲染中非常重要的几何元素,比如平面的朝向。但当我们对物体进行变换(平移、旋转、缩放)时,法线也需要相应变换。
问题场景:非均匀缩放
考虑一个简单的例子:
原始三角形:
顶点 A(0,0,0), B(1,0,0), C(0,1,0) ------ 一个 45° 角的直角三角形
法线指向 +Z 方向
经过 X 轴缩放 2 倍后:
顶点 A'(0,0,0), B'(2,0,0), C'(0,1,0) ------ 变成长方形
问题 :如果简单用模型矩阵变换法线 (0,0,1) * M,得到的结果还是 (0,0,1),但实际上法线应该倾斜!因为原本垂直于斜边的法线,在缩放后应该不再垂直于斜边了。
数学推导
1. 切线与法线的正交关系
在曲面上:
- 切线向量 T 沿着表面方向
- 法线向量 N 垂直于表面
- 它们满足:
T · N = 0(点积为零,表示正交)
2. 变换后的正交性要求
对物体应用模型矩阵 M 后:
- 新的切线:
T' = T * M⁻¹(逆变换) - 新的法线:
N' = N * G(待求的变换矩阵)
变换后仍需满足:T' · N' = 0
代入:
(T * M⁻¹) · (N * G) = 0
(T · M⁻¹) · (N * G) = 0
T · (M⁻¹ * N * G) = 0
要使这个等式对任意切线 T 成立,必须有:
M⁻¹ * N * G = N
即:
G = M⁻¹ (当 N 是 M 的特征向量时)
3. 标准情况的精确推导
对于任意向量,需要同时满足:
T' · N' = 0
用矩阵形式,变换后的切线是 T' = (M⁻¹)ᵀ * T
要保持正交:T' · N' = 0 ⟹ T · M⁻¹ · N' = 0
如果法线也用 M⁻¹ 变换:N' = (M⁻¹)ᵀ * N
则:T · M⁻¹ · (M⁻¹)ᵀ * N = T · N ✓ (完美正交)
为什么是"逆矩阵的转置"?
完整数学表达式
NormalMatrix = (ModelMatrix⁻¹)ᵀ
直观理解
| 变换类型 | 普通顶点 | 法线 |
|---|---|---|
| 平移 | 直接使用 M | 使用 (M⁻¹)ᵀ(平移不影响方向) |
| 旋转 | 使用 M | 使用 (M⁻¹)ᵀ(逆旋转可抵消) |
| 缩放 | 使用 M | 使用 (M⁻¹)ᵀ(关键!修正变形) |
缩放影响的本质
当你在 X 方向缩放 2 倍:
- 原本 45° 的表面变得"更平"了
- 法线需要向相反方向倾斜来保持垂直
- 逆矩阵的转置恰好完成这个修正
为什么只取 3x3 部分?
模型矩阵通常是 4x4(MVP 矩阵):
┌ ┐
│ [3x3 旋转/缩放] │ ← 这部分决定方向变换
│ │
│ [0 0 0] │ ← 透视列(不影响方向)
└────────────────────┘
法线是方向向量(没有位置),所以只需要 3x3 旋转/缩放部分。W 分量在计算 (M⁻¹)ᵀ 时自然被处理。
实际应用示例
glsl
// GLSL 中
mat3 normalMatrix = transpose(inverse(modelMatrix));
// 应用到法线
vec3 transformedNormal = normalize(normalMatrix * normal);
总结
| 问题 | 原因 |
|---|---|
| 为什么用逆矩阵? | 抵消模型变换,恢复正确的方向关系 |
| 为什么用转置? | 法线是行向量,需要 (M⁻¹)ᵀ 来正确变换 |
| 为什么取 3x3? | 法线是方向向量,不需要平移分量 |
这就是法线矩阵的数学本质 ------ 它确保法线在任何非均匀变换下都能保持与表面的正交关系。