如果法线简单地跟着顶点变换有时会出现如下图情况,
若图2所示,垂直关系变了.
可以用线性代数知识证明
设原来的切线为 T T T,法线 N N N,则有
T T N = 0 T^TN=0 TTN=0
如果切线经过缩放矩阵S变换,则新的切线为 T ′ T' T′,新的法线为 N ′ N' N′,
T ′ = S T T'=ST T′=ST
假设 N ′ N' N′=SN,则有
( T ′ ) T N ′ = ( S T ) T ( S N ) = T T ( S T S ) N \left( T' \right) ^TN'=\left( ST \right) ^T\left( SN \right) =T^T\left( S^TS \right) N (T′)TN′=(ST)T(SN)=TT(STS)N
而缩放矩阵有以下特征
S = [ a 0 0 0 b 0 0 0 c ] S=\left[ \begin{matrix} a& 0& 0\\ 0& b& 0\\ 0& 0& c\\ \end{matrix} \right] S=⎣⎡a000b000c⎦⎤
故有
S T S = [ a 2 0 0 0 b 2 0 0 0 c 2 ] S^TS=\left[ \begin{matrix} a^2& 0& 0\\ 0& b^2& 0\\ 0& 0& c^2\\ \end{matrix} \right] STS=⎣⎡a2000b2000c2⎦⎤
如果 a = b = c a=b=c a=b=c,则 S = k E S=kE S=kE,故
( T ′ ) T N ′ = 0 \left( T' \right) ^TN'=0 (T′)TN′=0
成立.否则则说明不能对法线应用和切线相同的变换.
类似地,如果对切线空间应用旋转矩阵 R R R,则应该主要关注
R T R ? = k E R^TR?=kE RTR?=kE
而我们知道旋转矩阵是正交矩阵,故上述关系肯定满足.
接下来是平移矩阵 T r Tr Tr, T r Tr Tr的左 3 × 3 3\times 3 3×3矩阵为 E E E,故肯定也满足.
所以综合下来,只要缩放矩阵的Uniform的那么缩放,旋转,平移其一或多个组合组成 M M M矩阵,则法线只需同左乘左 3 × 3 M 3\times 3M 3×3M就可以.如果不是uniform的,则需要不同的变换矩阵,而这个矩阵求法也跟上面的类似
( M T ) T ( ? N ) = 0 → T T M T ? N = 0 \left( MT \right) ^T\left( ?N \right) =0\rightarrow T^TM^T?N=0 (MT)T(?N)=0→TTMT?N=0
只需 M T ? = E M^T?=E MT?=E即可故最终的 M N M^N MN应为 ( M T ) − 1 \left( M^T \right) ^{-1} (MT)−1,转置和求逆可以交换顺序,故结果同 ( M − 1 ) T \left( M^{-1} \right) ^T (M−1)T,而 M − 1 M^{-1} M−1即为世界坐标转为模型空间的矩阵 W W W,即最终法线为 W T N W^TN WTN而对一个矩阵转置比对一个向量转置更费时,故可以求最终法线的转置
( W T N ) T = N T W \left( W^TN \right) ^T=N^TW (WTN)T=NTW
也即
N ′ = { N T W . c o l 0 , N T W . c o l 1 , N T W . c o l 2 } N'=\left\{ N^TW.col0,N^TW.col1,N^TW.col2 \right\} N′={NTW.col0,NTW.col1,NTW.col2}
Unity 3D中就是采用的这种思路
UnityObjectToWorldNormal函数可在UnityCG.cginc中找到
// Transforms normal from object to world space
inline float3 UnityObjectToWorldNormal( in float3 norm )
{
#ifdef UNITY_ASSUME_UNIFORM_SCALING
return UnityObjectToWorldDir(norm);
#else
// mul(IT_M, norm) => mul(norm, I_M) => {dot(norm, I_M.col0), dot(norm, I_M.col1), dot(norm, I_M.col2)}
return normalize(mul(norm, (float3x3)unity_WorldToObject));
#endif
}
函数使用了一个宏 UNITY_ASSUME_UNIFORM_SCALING来判断是否需要特殊处理,不用特殊处理的话
则用以下函数
// Transforms direction from object to world space
inline float3 UnityObjectToWorldDir( in float3 dir )
{
return normalize(mul((float3x3)unity_ObjectToWorld, dir));
}
如果需要则运用上面解释的方法计算正确的最终法线.