如果法线简单地跟着顶点变换有时会出现如下图情况,
若图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));
}
如果需要则运用上面解释的方法计算正确的最终法线.