背景
在冯氏光照模型中,其中的漫反射项需要我们对法向量和光线做点乘计算。
从顶点着色器中读入的法向量数据处于模型空间,我们需要将法向量转换到世界空间,然后在世界空间中让法向量和光线做运算。这里便有一个问题,如何将法线从当前的模型空间变换到世界空间?
首先,法向量只是一个方向向量,不能表达空间中的特定位置。同时,法向量没有齐次坐标(顶点位置中的w分量)。这意味着,位移不应该影响到法向量。因此,如果我们打算把法向量乘以一个模型矩阵,我们就要从矩阵中移除位移部分,只选用模型矩阵左上角3×3的矩阵(注意,我们也可以把法向量的w分量设置为0,再乘以4×4矩阵;这同样可以移除位移)。对于法向量,我们只希望对它实施缩放和旋转变换。
其次,如果模型矩阵执行了不等比缩放,顶点的改变会导致法向量不再垂直于表面了。因此,我们不能用这样的模型矩阵来变换法向量。下面的图展示了应用了不等比缩放的模型矩阵对法向量的影响:
当我们应用一个不等比缩放时(注意:等比缩放不会破坏法线,因为法线的方向没被改变,仅仅改变了法线的长度,而这很容易通过标准化来修复),法向量就不会再垂直于对应的表面了,这样光照就会被破坏。
修复这个行为的诀窍是使用一个为法向量专门定制的模型矩阵。这个矩阵称之为法线矩阵(Normal Matrix),它使用了一些线性代数的操作来移除对法向量错误缩放的影响。
推导过程
为了将一个顶点从模型空间转换到世界空间,我们可以乘上一个模型矩阵model,包含物体的移动、旋转、缩放信息。在shader中的代码如下:
FragPos = vec3(model * vec4(aPos, 1.0));
对于一个向量,正如上面的图展示的一样,我们不能简单乘上model矩阵。如果乘上model矩阵,向量就不再和原来的表面切线垂直了。
我们可以定义表面切线为
T
=
P
2
−
P
1
T = P_2 - P1
T=P2−P1,其中
P
1
,
P
2
P_1,P_2
P1,P2都是表面上的顶点。当表面前线乘上model矩阵时,我们有:
m
o
d
e
l
∗
T
=
m
o
d
e
l
∗
P
2
−
m
o
d
e
l
∗
P
1
T
′
=
P
2
′
−
P
1
′
model * T = model * P_2 - model * P_1 \\ T' = P_2' - P_1'
model∗T=model∗P2−model∗P1T′=P2′−P1′
变换后的表面切线
T
′
T'
T′仍然可以表示成表面上顶点的差,因此乘上model矩阵之后,表面切线不会被破坏。
对于表面上的法线
N
N
N,我们无法从表面上找到两个顶点来表示,但是我们知道表面法线与切线互相垂直,即
N
⋅
T
=
0
N \cdot T = 0
N⋅T=0
我们假设矩阵
G
G
G就是可以将法线从模型空间转换到世界空间的正确矩阵,并用
M
M
M来表示模型矩阵model,于是有下式:
N
′
⋅
T
′
=
(
G
N
)
⋅
(
M
T
)
=
0
N' \cdot T' = (GN)\cdot(MT) = 0
N′⋅T′=(GN)⋅(MT)=0
转化成矩阵表示的形式
(
G
N
)
⋅
(
M
T
)
=
(
G
N
)
T
∗
(
M
T
)
=
N
T
G
T
M
T
=
0
(GN)\cdot(MT) = (GN)^T*(MT) = N^TG^TMT = 0
(GN)⋅(MT)=(GN)T∗(MT)=NTGTMT=0
我们知道
N
⋅
T
=
N
T
T
=
0
N\cdot T = N^TT = 0
N⋅T=NTT=0,所以如果
G
T
M
=
a
I
G^TM = aI
GTM=aI,
a
a
a是任意非零常数,我们便有
N
′
⋅
T
′
=
N
T
G
T
M
T
=
N
T
a
I
T
=
a
N
T
T
=
0
N'\cdot T' = N^TG^TMT = N^TaIT = aN^TT = 0
N′⋅T′=NTGTMT=NTaIT=aNTT=0
由于我们不想改变法向量的模长,因此令
a
=
1
a = 1
a=1,只要满足
G
T
M
=
I
G^TM = I
GTM=I的条件,我们就可以说
G
G
G是我们最终需要的矩阵,进一步计算
G
T
M
=
I
⟷
G
=
(
M
−
1
)
T
G^TM = I \longleftrightarrow G = (M^{-1})^T
GTM=I⟷G=(M−1)T
最终可得,将法线从模型空间转换到世界空间的矩阵为
(
M
−
1
)
T
(M^{-1})^T
(M−1)T。
补充说明
当模型矩阵只进行了旋转或等比缩放时,我们用这个矩阵来变换法线向量,可以得到正确的结果。
这是因为旋转矩阵和等比缩放矩阵都是正交矩阵,正交矩阵有一个属性:矩阵的转置等于矩阵的逆。
因此
M
−
1
=
M
T
→
G
=
(
M
−
1
)
T
=
M
M^{-1} = M^T \rightarrow G = (M^{-1})^T = M
M−1=MT→G=(M−1)T=M
参考
https://learnopengl-cn.github.io/02%20Lighting/02%20Basic%20Lighting/
http://www.lighthouse3d.com/tutorials/glsl-12-tutorial/the-normal-matrix/