[Rotation Transform] 旋轉變換
這篇文章紀錄一些關於旋轉變換的知識,和Unity引擎中的旋轉相關API。
遊戲中對Transform的變換通常包含平移旋轉與縮放,常用的旋轉變換有旋轉矩陣和四元數兩種。
旋轉矩陣
繞當前座標系指定軸的變換矩陣
一個對向量進行位移旋轉縮放的複合變換的矩陣可以表示成以下形式:
P n e w = M t r a n s l a t i o n M r o t a t i o n M s c a l e P o l d {P_{new}} = {M_{translation}}{M_{rotation}}{M_{scale}}{P_{old}} P n e w = M t r a n s l a t i o n M r o t a t i o n M s c a l e P o l d
旋轉變換與縮放變換是線性變換,在三維空間中變換矩陣可以表示成3X3的形式,而位移變換是仿射變換,需要4X4的矩陣表示,因爲本文只討論旋轉變換,定義後面的部分會直接使用3X3矩陣。且對於線性變換,我們使用列向量代表點,則3X3過渡矩陣的行向量代表座標變換中的基(x 、 y 、 z x、y、z x 、 y 、 z 軸)。
繞x 、 y 、 z x、y、z x 、 y 、 z 軸旋轉θ \theta θ 度的矩陣:
R x ( θ ) = [ 1 0 0 0 0 cos θ − sin θ 0 0 sin θ cos θ 0 0 0 0 1 ] {{R}_{x}}(\theta )=\left[ \begin{matrix}
1 & 0 & 0 & 0 \\
0 & \cos \theta & -\sin \theta & 0 \\
0 & \sin \theta & \cos \theta & 0 \\
0 & 0 & 0 & 1 \\
\end{matrix} \right] R x ( θ ) = ⎣ ⎢ ⎢ ⎡ 1 0 0 0 0 cos θ sin θ 0 0 − sin θ cos θ 0 0 0 0 1 ⎦ ⎥ ⎥ ⎤
R y ( θ ) = [ cos θ 0 − sin θ 0 0 1 0 0 sin θ 0 cos θ 0 0 0 0 1 ] {{R}_{y}}(\theta )=\left[ \begin{matrix}
\cos \theta & 0 & -\sin \theta & 0 \\
0 & \text{1} & \text{0} & 0 \\
\sin \theta & \text{0} & \cos \theta & 0 \\
0 & 0 & 0 & 1 \\
\end{matrix} \right] R y ( θ ) = ⎣ ⎢ ⎢ ⎡ cos θ 0 sin θ 0 0 1 0 0 − sin θ 0 cos θ 0 0 0 0 1 ⎦ ⎥ ⎥ ⎤
R z ( θ ) = [ cos θ − sin θ 0 0 sin θ cos θ 0 0 0 0 1 0 0 0 0 1 ] {{R}_{z}}(\theta )=\left[ \begin{matrix}
\cos \theta & -\sin \theta & 0 & 0 \\
\sin \theta & \cos \theta & \text{0} & 0 \\
0 & \text{0} & \text{1} & 0 \\
0 & 0 & 0 & 1 \\
\end{matrix} \right] R z ( θ ) = ⎣ ⎢ ⎢ ⎡ cos θ sin θ 0 0 − sin θ cos θ 0 0 0 0 1 0 0 0 0 1 ⎦ ⎥ ⎥ ⎤
給定歐拉角的變換
如果旋轉的需求是一個歐拉角,那麼我們要怎麼處理呢?事實上對於一個給定歐拉角的旋轉,我們需要確定一個旋轉順序,可以想象以不同的順序繞x 、 y 、 z x、y、z x 、 y 、 z 軸旋轉得到的結果是不同的(對於四元數也是一樣的)。Unity中的約定爲z 、 x 、 y z、x、y z 、 x 、 y : “Applies a rotation of eulerAngles.z degrees around the z-axis, eulerAngles.x degrees around the x-axis, and eulerAngles.y degrees around the y-axis (in that order)”。
還需要注意的是這個z 、 x 、 y z、x、y z 、 x 、 y 順序所在的座標系指的是旋轉前的座標系,不隨旋轉而改變,我們觀察一個組合旋轉矩陣:
M r o t a t i o n = M r o t a t i o n Z M r o t a t i o n X M r o t a t i o n Y {{M}_{rotation}}={{M}_{rotationZ}}{{M}_{rotationX}}{{M}_{rotationY}} M r o t a t i o n = M r o t a t i o n Z M r o t a t i o n X M r o t a t i o n Y
座標系Transform可以用一個矩陣來表示,而旋轉矩陣實際作用就是旋轉座標系,從上面的3個旋轉矩陣中可以知道,每一個旋轉都是對當前座標系的旋轉。該矩陣去變換一個座標系的的過程爲,繞當前座標系Y軸旋轉θ \theta θ 度,得到一個新的旋轉後坐標系,繼續繞當前旋轉後坐標系X軸旋轉θ \theta θ 度,得到一個新的兩次旋轉後坐標系,繼續繞當前旋轉後坐標系Z軸旋轉θ \theta θ 度。這代表的是一個在當前座標系,按y 、 x 、 z y、x、z y 、 x 、 z 順序的旋轉。
上面的過程與按照旋轉前座標系的旋轉順序z 、 x 、 y z、x、y z 、 x 、 y 是等價的,就是說他們所代表的順序是相反的。
證明:
按照旋轉前座標系的旋轉順序z、x、y可以用以下矩陣表示
M r o t a t i o n = M Y M X M Z {{M}_{rotation}}={{M}_{Y}}{{M}_{X}}{{M}_{Z}} M r o t a t i o n = M Y M X M Z
第一步旋轉變換M Z {{M}_{Z}} M Z ,因爲還沒有發生座標系變換M Z = M r o t a t i o n Z {{M}_{Z}}={{M}_{rotationZ}} M Z = M r o t a t i o n Z ,初始座標系的基爲I I I ,經過M Z {{M}_{Z}} M Z 變換,新座標的基爲M Z T {{M}_{Z}}^{T} M Z T (轉置是因爲使用列向量表示點,所以M r o t a t i o n Z {{M}_{rotationZ}} M r o t a t i o n Z 變換矩陣的行向量表示基,求解過渡矩陣需要的是列向量形式的基),基I I I 到基M Z T {{M}_{Z}}^{T} M Z T 的過渡矩陣P有I × P = M Z T I\times P={{M}_{Z}}^{T} I × P = M Z T , P = M r o t a t i o n Z T P={{M}_{rotationZ}}^{T} P = M r o t a t i o n Z T ,旋轉矩陣正交 P = M r o t a t i o n Z − 1 P={{M}_{rotationZ}}^{-1} P = M r o t a t i o n Z − 1 。
第二步旋轉變換M X {{M}_{X}} M X ,是M r o t a t i o n X {{M}_{rotationX}} M r o t a t i o n X 在基I I I 中的表達,而矩陣連續變化則已經更換了基,線性變換M r o t a t i o n X {{M}_{rotationX}} M r o t a t i o n X 在新基M Z T {{M}_{Z}}^{T} M Z T 中表示爲M X = P − 1 M r o t a t i o n X P = M r o t a t i o n Z M r o t a t i o n X M r o t a t i o n Z − 1 {{M}_{X}}={{P}^{-1}}{{M}_{rotationX}}{P}={{M}_{rotationZ}}{{M}_{rotationX}}{{M}_{rotationZ}}^{-1} M X = P − 1 M r o t a t i o n X P = M r o t a t i o n Z M r o t a t i o n X M r o t a t i o n Z − 1 ,由I I I 到第二步變換後基( M X M Z ) T ({{M}_{X}}{{M}_{Z}})^{T} ( M X M Z ) T 的的過度矩陣爲P = M r o t a t i o n Z M r o t a t i o n X {P}={{M}_{rotationZ}}{{M}_{rotationX}} P = M r o t a t i o n Z M r o t a t i o n X 。
第三步旋轉變換M Y {{M}_{Y}} M Y ,同理是M r o t a t i o n Y {{M}_{rotationY}} M r o t a t i o n Y 在基I I I 中的表達,線性變換M r o t a t i o n Y {{M}_{rotationY}} M r o t a t i o n Y 在新基( M X M Z ) T ({{M}_{X}}{{M}_{Z}})^{T} ( M X M Z ) T 中表示爲M Y = P − 1 M r o t a t i o n Y P = M r o t a t i o n Z M r o t a t i o n X M r o t a t i o n Y ( M r o t a t i o n Z M r o t a t i o n X ) − 1 {{M}_{Y}}={{P}^{-1}}{{M}_{rotationY}}{P}={{M}_{rotationZ}}{{M}_{rotationX}}{{M}_{rotationY}}({{M}_{rotationZ}}{{M}_{rotationX}})^{-1} M Y = P − 1 M r o t a t i o n Y P = M r o t a t i o n Z M r o t a t i o n X M r o t a t i o n Y ( M r o t a t i o n Z M r o t a t i o n X ) − 1 。
M r o t a t i o n = M Y M X M Z = ( M r o t a t i o n Z M r o t a t i o n X M r o t a t i o n Y ( M r o t a t i o n Z M r o t a t i o n X ) − 1 ) ( M r o t a t i o n Z M r o t a t i o n X M r o t a t i o n Z ) ( M r o t a t i o n Z − 1 ) = M r o t a t i o n Z M r o t a t i o n X M r o t a t i o n Y {{M}_{rotation}}={{M}_{Y}}{{M}_{X}}{{M}_{Z}} \\
=({{M}_{rotationZ}}{{M}_{rotationX}}{{M}_{rotationY}}{{({{M}_{rotationZ}}{{M}_{rotationX}})}^{-1}})({{M}_{rotationZ}}{{M}_{rotationX}}{{M}_{rotationZ}})({{M}_{rotationZ}}^{-1}) \\
={{M}_{rotationZ}}{{M}_{rotationX}}{{M}_{rotationY}}
M r o t a t i o n = M Y M X M Z = ( M r o t a t i o n Z M r o t a t i o n X M r o t a t i o n Y ( M r o t a t i o n Z M r o t a t i o n X ) − 1 ) ( M r o t a t i o n Z M r o t a t i o n X M r o t a t i o n Z ) ( M r o t a t i o n Z − 1 ) = M r o t a t i o n Z M r o t a t i o n X M r o t a t i o n Y
實際上Unity中的旋轉按照歐拉角的表達就是:M r o t a t i o n = M r o t a t i o n Z M r o t a t i o n X M r o t a t i o n Y {{M}_{rotation}}={{M}_{rotationZ}}{{M}_{rotationX}}{{M}_{rotationY}} M r o t a t i o n = M r o t a t i o n Z M r o t a t i o n X M r o t a t i o n Y
歐拉角表示旋轉的缺點
我們可以看出,用歐拉角表示旋轉是順序相關的,會導致結果不夠直觀,所謂的萬向節死鎖 就是這種不直觀造成的現象。具體現象是第二個旋轉軸(X軸)的旋轉角度爲±90°時,第三次旋轉與第一次旋轉對應相同或相反的旋轉軸,也可以理解爲第二次旋轉將第三次旋轉的旋轉軸旋轉到與第一次旋轉的旋轉軸共線的位置,把transform面板上的Rotation的x設置爲±90時改變z 、 y z、y z 、 y 即可嘗試。
我們看看萬向節死鎖的數學表達是什麼,z 、 x 、 y z、x、y z 、 x 、 y 軸轉動度數分別r 、 p 、 h r、p、h r 、 p 、 h 時爲進一步展開旋轉矩陣:
M r o t a t i o n = M r o t a t i o n Z M r o t a t i o n X M r o t a t i o n Y = [ cos r cosh − sin r sin p sinh − sin r cos p cos r sinh + sin r sin p cosh sin r cosh + cos r sin p sinh cos r cos p sin r sinh − cos r sin p cosh − cos p sinh sin p cos p cosh ] {{M}_{rotation}}={{M}_{rotationZ}}{{M}_{rotationX}}{{M}_{rotationY}}\\=\left[ \begin{matrix}
\cos r\cosh -\sin r\sin p\sinh & -\sin r\cos p & \cos r\sinh +\sin r\sin p\cosh \\
\sin r\cosh +\cos r\sin p\sinh & \cos r\cos p & \sin r\sinh -\cos r\sin p\cosh \\
-\cos p\sinh & \sin p & \cos p\cosh \\
\end{matrix} \right] M r o t a t i o n = M r o t a t i o n Z M r o t a t i o n X M r o t a t i o n Y = ⎣ ⎡ cos r cosh − sin r sin p sinh sin r cosh + cos r sin p sinh − cos p sinh − sin r cos p cos r cos p sin p cos r sinh + sin r sin p cosh sin r sinh − cos r sin p cosh cos p cosh ⎦ ⎤
p = ± 90 ° p=±90° p = ± 9 0 ° 時
M r o t a t i o n = [ cos r cosh ∓ sin r sinh 0 cos r sinh ± sin r cosh sin r cosh ± cos r sinh 0 sin r sinh ∓ cos r cosh 0 ± 1 0 ] = + 90 ∘ [ cos ( r + h ) 0 sin ( r + h ) sin ( r + h ) 0 − cos ( r + h ) 0 1 0 ] = − 90 ∘ [ cos ( r − h ) 0 − sin ( r − h ) sin ( r − h ) 0 cos ( r − h ) 0 − 1 0 ] {{M}_{rotation}}=\left[ \begin{matrix}
\cos r\cosh \mp \sin r\sinh & 0 & \cos r\sinh \pm \sin r\cosh \\
\sin r\cosh \pm \cos r\sinh & 0 & \sin r\sinh \mp \cos r\cosh \\
0 & \pm 1 & 0 \\
\end{matrix} \right] \\
\overset{+{{90}^{\circ }}}{\mathop{=}}\,\left[ \begin{matrix}
\cos (r+h) & 0 & \sin (r+h) \\
\sin (r+h) & 0 & -\cos (r+h) \\
0 & 1 & 0 \\
\end{matrix} \right] \\
\overset{-{{90}^{\circ }}}{\mathop{=}}\,\left[ \begin{matrix}
\cos (r-h) & 0 & -\sin (r-h) \\
\sin (r-h) & 0 & \cos (r-h) \\
0 & -1 & 0 \\
\end{matrix} \right] \\
M r o t a t i o n = ⎣ ⎡ cos r cosh ∓ sin r sinh sin r cosh ± cos r sinh 0 0 0 ± 1 cos r sinh ± sin r cosh sin r sinh ∓ cos r cosh 0 ⎦ ⎤ = + 9 0 ∘ ⎣ ⎡ cos ( r + h ) sin ( r + h ) 0 0 0 1 sin ( r + h ) − cos ( r + h ) 0 ⎦ ⎤ = − 9 0 ∘ ⎣ ⎡ cos ( r − h ) sin ( r − h ) 0 0 0 − 1 − sin ( r − h ) cos ( r − h ) 0 ⎦ ⎤
可以看出r 、 h r、h r 、 h 出現共線的轉動的原因。
四元數與旋轉
對旋轉而言,四元數要比歐拉角和矩陣更好用,對於給定軸與角度的旋轉非常直觀,易於進行球面插值,很多運算過程不需要計算三角函數。關於四元數的數學意義這裏不贅述了,四元數q ^ p ^ q ^ − 1 \hat{q}\hat{p}{{\hat{q}}^{-1}} q ^ p ^ q ^ − 1 表示四元數q ^ \hat{q} q ^ 對向量p ^ \hat{p} p ^ 的變換,Unity中被封裝爲乘法。雖然Transform使用四元數存儲計算旋轉信息,但對於需要顯示的圖形,還是會將其轉換爲矩陣傳入Shader。
四元數是區別於矩陣的一種旋轉的表達方式,但旋轉結果是相同的,所以通過歐拉角來構造四元數也需要遵行約定好順序z 、 x 、 y z、x、y z 、 x 、 y 。
旋轉順序的驗證
在Unity中對旋轉順序進行驗證。
using UnityEngine;
public class Rotation : MonoBehaviour
{
public Vector3 angle;
public Transform[ ] transforms;
public void Rotate ( )
{
transforms[ 0 ] . Rotate ( angle, Space. Self) ;
ZXY_OriginalCoordinate ( transforms[ 1 ] , angle) ;
YXZ_FollowCoordinate ( transforms[ 2 ] , angle) ;
transforms[ 3 ] . rotation * = Quaternion. Euler ( angle) ;
QuaternionZXY_OriginalCoordinate ( transforms[ 4 ] , angle) ;
QuaternionYXZ_FollowCoordinate ( transforms[ 5 ] , angle) ;
}
public void ResetRotation ( )
{
for ( int i = 0 ; i < transforms. Length; i++ )
{
transforms[ i] . rotation = Quaternion. identity;
}
}
private void ZXY_OriginalCoordinate ( Transform transform, Vector3 angle)
{
Vector3 x = transform. right;
Vector3 y = transform. up;
Vector3 z = transform. forward;
transform. Rotate ( z, angle. z, Space. World) ;
transform. Rotate ( x, angle. x, Space. World) ;
transform. Rotate ( y, angle. y, Space. World) ;
}
private void YXZ_FollowCoordinate ( Transform transform, Vector3 angle)
{
transform. Rotate ( Vector3. up, angle. y, Space. Self) ;
transform. Rotate ( Vector3. right, angle. x, Space. Self) ;
transform. Rotate ( Vector3. forward, angle. z, Space. Self) ;
}
private void QuaternionZXY_OriginalCoordinate ( Transform transform, Vector3 angle)
{
Quaternion x = Quaternion. AngleAxis ( angle. x, transform. right) ;
Quaternion y = Quaternion. AngleAxis ( angle. y, transform. up) ;
Quaternion z = Quaternion. AngleAxis ( angle. z, transform. forward) ;
transform. rotation = ( y * ( x * ( z * transform. rotation) ) ) ;
}
private void QuaternionYXZ_FollowCoordinate ( Transform transform, Vector3 angle)
{
transform. rotation * = Quaternion. AngleAxis ( angle. y, Vector3. up) ;
transform. rotation * = Quaternion. AngleAxis ( angle. x, Vector3. right) ;
transform. rotation * = Quaternion. AngleAxis ( angle. z, Vector3. forward) ;
}
#if UNITY_EDITOR
private void OnDrawGizmos ( )
{
for ( int i = 0 ; i < transforms. Length; i++ )
{
UnityEditor. Handles. PositionHandle ( transforms[ i] . position, transforms[ i] . rotation) ;
}
}
#endif
}
對於不同歐拉角,六種旋轉方式的結果是相同的。
Shader中的方向變換
Shader中常用到的方向變換包括直線光方向、視線方向、統一縮放下的法線等。變換表示線性3X3變換矩陣M 與三維向量p 相乘,但Mp 與pM 的形式都有使用,前者把向量作爲行向量,後者是列向量。我們具體選行向量還是列向量呢?下面是變換矩陣的總結,x a x_a x a 表示空間A的基x x x 在空間B中的值:
P B = M A → B P A
{{P}_{B}}={{M}_{A\to B}}{{P}_{A}}
P B = M A → B P A P A = M B → A P B
{{P}_{A}}={{M}_{B\to A}}{{P}_{B}}
P A = M B → A P B P B T = P A T M B → A
{{P}_{B}}^T={{P}_{A}}^T{{M}_{B\to A}}
P B T = P A T M B → A P A T = P B T M A → B
{{P}_{A}}^T={{P}_{B}}^T{{M}_{A\to B}}
P A T = P B T M A → B M A → B = [ ∣ ∣ ∣ x a y a z a ∣ ∣ ∣ ] = [ − x b − − y b − − z b − ] = M B → A T
{{M}_{A\to B}}=\left[ \begin{matrix}
| & | & | \\
{{x}_{a}} & {{y}_{a}} & {{z}_{a}} \\
| & | & | \\
\end{matrix} \right]=\left[ \begin{matrix}
-& {{x}_{b}} & - \\
-& {{y}_{b}} & - \\
-& {{z}_{b}} & - \\
\end{matrix} \right]={{M}_{B\to A}}^T
M A → B = ⎣ ⎡ ∣ x a ∣ ∣ y a ∣ ∣ z a ∣ ⎦ ⎤ = ⎣ ⎡ − − − x b y b z b − − − ⎦ ⎤ = M B → A T M B → A = [ ∣ ∣ ∣ x b y b z b ∣ ∣ ∣ ] = [ − x a − − y a − − z a − ] = M A → B T
{{M}_{B\to A}}=\left[ \begin{matrix}
| & | & | \\
{{x}_{b}} & {{y}_{b}} & {{z}_{b}} \\
| & | & | \\
\end{matrix} \right]=\left[ \begin{matrix}
-& {{x}_{a}} & - \\
-& {{y}_{a}} & - \\
-& {{z}_{a}} & - \\
\end{matrix} \right]={{M}_{A\to B}}^T
M B → A = ⎣ ⎡ ∣ x b ∣ ∣ y b ∣ ∣ z b ∣ ⎦ ⎤ = ⎣ ⎡ − − − x a y a z a − − − ⎦ ⎤ = M A → B T
例如把法線貼圖(N T a n g e n t N_{Tangent} N T a n g e n t )從切線空間變換到世界空間(N W o r l d N_{World} N W o r l d ),已知由法線、切線、副法線,構成的矩陣:
M W o r l d → T a n g e n t = [ − n − − t − − b − ]
{{M}_{World\to Tangent}}=\left[ \begin{matrix}
-& {n} & - \\
-& {t} & - \\
-& {b} & - \\
\end{matrix} \right]
M W o r l d → T a n g e n t = ⎣ ⎡ − − − n t b − − − ⎦ ⎤ N W o r l d T = N T a n g e n t T M W o r l d → T a n g e n t
{N_{World}}^T = {N_{Tangent}}^T {{M}_{World\to Tangent}}
N W o r l d T = N T a n g e n t T M W o r l d → T a n g e n t
使用行向量在左,矩陣在右的乘法。
法線變換
如上圖所示,在存在縮放時,法線的變換矩陣與模型頂點的變換矩陣不同。切線可以用模型上兩個點的差值表示,所以模型頂點的變換公式適用於切線。當使用3X3性變換矩陣M A → B {M}_{A\to B} M A → B 表示座標空間A到座標空間B的頂點變換時,切線變換可以表示爲
T B = M A → B T A
{T_{B}} = {{M}_{A\to B}}{T_{A}}
T B = M A → B T A
此時法線變換可以表示爲
N B = ( M A → B T ) − 1 N A
{N_{B}} = ({{M}_{A\to B}}^T)^{-1}{N_{A}}
N B = ( M A → B T ) − 1 N A
注意,這個變換不保證法線的長度是歸一化的,需要再歸一化操作一次。
證明:
T A ⋅ N A = T A T N A = T A T ( M A → B − 1 M A → B ) T N A = T A T M A → B T ( M A → B − 1 ) T N A = ( M A → B T A ) T ( M A → B − 1 ) T N A = T B T N B = 0
{T_{A}}\cdot {N_{A}}
={T_{A}}^{T}{N_{A}}
\\= {T_{A}}^{T}({{{M}_{A\to B}}}^{-1}{{{M}_{A\to B}}})^{T}{N_{A}}
\\={T_{A}}^{T}{{{M}_{A\to B}}}^{T}({{{M}_{A\to B}}}^{-1})^{T}{N_{A}}
\\=({{M}_{A\to B}}{T_{A}})^{T}({{{M}_{A\to B}}}^{-1})^{T}{N_{A}}
\\={T_{B}}^{T}{N_B}
=0
T A ⋅ N A = T A T N A = T A T ( M A → B − 1 M A → B ) T N A = T A T M A → B T ( M A → B − 1 ) T N A = ( M A → B T A ) T ( M A → B − 1 ) T N A = T B T N B = 0 M N = ( M A → B − 1 ) T = ( M A → B T ) − 1
{M_N} =({{{M}_{A\to B}}}^{-1})^{T}=({{{M}_{A\to B}}}^{T})^{-1}
M N = ( M A → B − 1 ) T = ( M A → B T ) − 1
在不存在縮放時M A → B {{{M}_{A\to B}}} M A → B 是正交矩陣M N = ( M A → B − 1 ) T = M A → B {M_N} =({{{M}_{A\to B}}}^{-1})^{T}={{{M}_{A\to B}}} M N = ( M A → B − 1 ) T = M A → B ;係數爲k k k 的統一縮放變換時,M N = 1 k M A → B {M_N} =\frac{1}{k}{{{M}_{A\to B}}} M N = k 1 M A → B 。實際操作過程中,會把不存在縮放與統一縮放合併處理,最後都進行歸一化操作。