如果模型矩阵执行了不等比缩放,顶点的改变会导致法向量不再垂直于表面了。因此,我们不能用这样的模型矩阵来变换法向量。下面的图展示了应用了不等比缩放的模型矩阵对法向量的影响:
每当我们应用一个不等比缩放时(注意:等比缩放不会破坏法线,因为法线的方向没被改变,仅仅改变了法线的长度,而这很容易通过标准化来修复),法向量就不会再垂直于对应的表面了,这样光照就会被破坏。
修复这个行为的诀窍是使用一个为法向量专门定制的模型矩阵。这个矩阵称之为法线矩阵(Normal Matrix),它使用了一些线性代数的操作来移除对法向量错误缩放的影响。
法线矩阵被定义为「模型矩阵左上角的逆矩阵的转置矩阵」。
具体推导过程为:
vertexEyeSpace = modelViewMatrix * vertex;
modelEyeSpace = modelViewMatrix * vec4(normal, 0);
已知线段T = p2-p1;(P2和P1是两个顶点的位置)
T' = T * modelViewMatrix = (p2 - p1) * modelview;
= p2 * modelview - p1 * modelview;
也就是说相当于p‘2 - p'1
假设:
N = Q2-Q1;
因为线段和法线的点乘必须要为0
dot(T,N) = 0;
所以转换后的线段和法线的点乘也必须为0才是我们想要的
dot(T',N') = 0;
假设视图左上角的子矩阵为M
然后假设矩阵G是变换法向量的正确矩阵
得:
dot(G*N, M*T) = 0;
根据点乘公式得出dot(t,n)=|t|*|n|*cosα。
得:
dot(T', N') = dot(G*N, M*T) = inverse(GN) * MT;
已知矩阵GN和GN的逆矩阵的乘积为单元矩阵。
inverse(GN)*GN = I
同时正交矩阵的transpose(GN)*GN = I
两边都乘以转换矩阵时
transpose(GN)*inverse(GN) * GN = inverse(GN)
得
transpose(GN) = inverse(GN)
所以
dot(T', N') = transpose(GN) * MT;
拆解后得:
dot(T', N') = transpose(G) * transpose(N) * M*T;
假设transpose(G) * M = I时
dot(N', T') = dot(N, T) = 0;
所以得出我们要求的
G = transpose(inverse(M))
这就是“模型矩阵左上角的逆矩阵的转置矩阵”的整体推导过程。
另外:法向量只是一个方向向量,不能表达空间中的特定位置。同时,法向量没有齐次坐标(顶点位置中的w分量)。这意味着,位移不应该影响到法向量。因此,如果我们打算把法向量乘以一个模型矩阵,我们就要从矩阵中移除位移部分,只选用模型矩阵左上角3×3的矩阵(注意,我们也可以把法向量的w分量设置为0,再乘以4×4矩阵;这同样可以移除位移)。对于法向量,我们只希望对它实施缩放和旋转变换。
最终的做法是:
Normal = mat3(transpose(inverse(model))) * aNormal;