Cg 程式設計/Unity/旋轉

本教程討論了 Unity 中區域性旋轉的不同表示方法。它基於“頂點變換”章節。
如果建立一個遊戲物件並選中它,“檢查器視窗”將在“變換 > 旋轉”下顯示三個值 X、Y、Z。這些是三個以度為單位測量的尤拉角。(更準確地說,它們是一組可能的三個泰特-布萊恩角,或航海角或卡爾丹角,因為它們描述了繞三個不同軸的旋轉,而“正確的尤拉角”將描述一組三個角度,這些角度描述了三個旋轉,其中兩個旋轉是繞同一軸的。)
在 C# 中,您可以將這三個角度訪問為 Vector3 變數 Transform.localEulerAngles。文件指出,這些角度按以下順序表示——繞 z 軸旋轉 Z 度,繞 x 軸旋轉 X 度,以及繞 y 軸旋轉 Y 度。更準確地說,這些是繞父物件的固定(!)軸的旋轉(如果沒有父物件,則為世界軸)。由於這些旋轉使用固定軸,因此它們也被稱為“外旋”。
這三個角度可以描述物體在三維空間中的任何旋轉。在 Transform.localEulerAngles 的情況下,它們實際上描述了物體相對於父座標系(如果沒有父物件,則為世界座標系)的方向。旋轉(作為旋轉運動的意義)將在下面討論。
在航空中,當飛機的方向相對於地面的固定軸(例如機場的塔臺)指定時,會使用尤拉角,如上圖所示。在這種情況下,它們被稱為“滾轉”(對應於 Z)、“俯仰”(對應於 X)和“航向”(對應於 Y)。以下名為“Extrinsic_Rotations”的 C# 指令碼可以附加到物件上以根據這些名稱設定尤拉角。(在“專案視窗”中選擇“建立 > C# 指令碼”,將其重新命名為“Extrinsic_Rotations”,雙擊開啟它,將下面的程式碼複製貼上到指令碼中,然後將指令碼從“專案視窗”拖到“層次結構視窗”中的遊戲物件上,然後選擇遊戲物件並在“檢查器視窗”中找到指令碼的公共變數。)
using UnityEngine;
[ExecuteInEditMode]
public class Extrinsic_Rotations : MonoBehaviour {
public float bankZ, elevationX, headingY;
void Update () {
transform.localEulerAngles =
new Vector3(elevationX, headingY, bankZ);
}
}
與“檢查器”中的三個變數互動,以熟悉它們的含義。您可以從所有三個角度都設定為 0 開始。然後依次更改滾轉、俯仰和航向,並觀察當更改滾轉時物件如何繞父(或世界)的 z 軸(Unity 中的藍色軸)旋轉,當更改俯仰時如何繞父的 x 軸(紅色)旋轉,以及當更改航向時如何繞父的 y 軸(綠色)旋轉。

為了建立與“頂點變換”章節中的基本模型矩陣的聯絡,本小節介紹如何根據三個角度 X、Y、Z(或 θ、ψ 和 φ)計算旋轉矩陣。
繞 z 軸旋轉 φ(= Z)角度的 4×4 旋轉矩陣 為
最左側列的前兩個座標可以理解為向量 (1,0) 繞角度 旋轉的結果,旋轉後的向量為 。(圖示中使用角度 t;因此,結果為 。)類似地,下一列的前兩個分量可以理解為向量 (0,1) 繞角度 旋轉的結果,結果為 。
類似地,繞 x 軸旋轉 θ(= X)角度的 4×4 旋轉矩陣 為
繞 y 軸旋轉 ψ(= Y)角度的 4×4 旋轉矩陣 為
請注意,正弦項的符號取決於某些約定。這裡我們使用 Unity 的約定。
這三個矩陣必須組合成一個矩陣乘積以形成總旋轉。由於(列)向量從右側乘以變換矩陣,因此第一個旋轉 必須位於最右側,最後一個旋轉 必須位於最左側。因此,正確排序的矩陣乘積為
然後,矩陣 可以與其他基本變換矩陣相乘,以形成模型矩陣,如 “頂點變換”部分 中所述。
在 Unity 中,您可以透過以下方式為尤拉角 X、Y、Z 計算一個 4×4 矩陣 m(下面將更詳細地討論四元數)
Matrix4x4 m = Matrix4x4.TRS(Vector3.zero,
Quaternion.Euler(X, Y, Z), Vector3.one);

在某些情況下,例如機器人學,使用移動旋轉軸(即內稟旋轉)而不是迄今為止討論的固定旋轉軸(即外稟旋轉)更有趣。通常,相對於機器人底座的旋轉被稱為“偏航”(yaw),手臂的上下旋轉被稱為“俯仰”(pitch),下一個旋轉被稱為“滾轉”(roll)。由於每個旋轉都會導致關節旋轉,因此需要對具有移動旋轉軸的旋轉進行描述。
幸運的是,存在一個簡單的等價關係:由 Z、X 和 Y 指定的外稟旋轉對應於以下三個內稟旋轉(按此順序):繞 y 軸旋轉 Y (“偏航”),繞旋轉後的 x 軸旋轉 X (“俯仰”),以及繞旋轉後的 z 軸旋轉 Z (“滾轉”)。換句話說,如果以相反的順序應用移動旋轉軸的旋轉角(Y、X、Z 而不是 Z、X、Y),則它們與固定旋轉軸的旋轉角相同。對於大多數人來說,這種等價關係並不直觀;但是,透過使用以下允許您指定偏航、俯仰和滾轉的 C# 指令碼進行練習,您可能會獲得更好的理解。
using UnityEngine;
[ExecuteInEditMode]
public class Intrinsic_Rotations : MonoBehaviour {
public float yawY, pitchX, rollZ;
void Update() {
transform.localEulerAngles =
new Vector3(pitchX, yawY, rollZ);
}
}
在數學上,可以透過計算內稟旋轉的旋轉矩陣來證明這種等價關係。我們從繞 y 軸的旋轉開始,即矩陣。如果我們將此矩陣視為從區域性座標到全域性座標的變換,那麼很明顯,任何在區域性座標中的額外變換都應該從右邊相乘(其中向量在區域性座標中指定),而任何在全域性座標中的額外變換都應該從左邊相乘(其中結果向量在全域性座標中)。因此,繞區域性(即旋轉後的)x 軸的旋轉應該從右邊相乘,並且積變為。使用相同的邏輯,繞區域性(即旋轉後的)z 軸的後續旋轉也應該從右邊相乘,並且完整的積變為,這與以相反順序進行外稟旋轉的結果相同,即內稟旋轉等價於以相反順序進行的外稟旋轉。


尤拉角的一個有趣特徵(以及 Unity 和大多數其他圖形應用程式在內部不使用它們的原因之一)是它們可能導致一種稱為“萬向節鎖”的情況。在這種情況下,兩個旋轉軸平行,因此只使用了兩個不同的旋轉軸。換句話說,一個自由度丟失了。如果第一個旋轉(繞 x 軸的仰角)為 X = ±90°,則 Unity 的尤拉角會出現這種情況。在這種情況下,z 軸旋轉到 y 軸上;因此,(旋轉後的)第一個旋轉軸與(固定的)第三個旋轉軸相同。
如果您將仰角設定為 90° 或 -90°,則可以使用上面的指令碼輕鬆構建這種情況。

滾轉、俯仰和偏航也用於描述車輛的方向;請參閱“軸約定”的維基百科文章。此外,飛機的主軸也稱為滾轉(Unity 中的 z 軸)、俯仰(Unity 中的 x 軸)和偏航(Unity 中的 y 軸)。這些軸始終固定在飛機上;因此,滾轉、俯仰和偏航始終指的是圍繞這些飛機主軸的旋轉,而不管飛機的方向如何。因此,這些旋轉的數學方法與同名的尤拉角不同。事實上,圍繞飛機主軸的旋轉更類似於接下來討論的圍繞任意軸的旋轉。
如“頂點變換”章節中所述,繞歸一化軸旋轉 α 角的旋轉矩陣為
幸運的是,在使用 Unity 時,幾乎永遠不需要這個矩陣。相反,可以透過以下方式將物件的區域性“旋轉”設定為圍繞axis旋轉angle度:
using UnityEngine;
[ExecuteInEditMode]
public class Angle_Axis_Rotations : MonoBehaviour {
public float angle;
public Vector3 axis;
void Update() {
transform.localRotation =
Quaternion.AngleAxis(angle, axis);
}
}
實際上,這會設定物件相對於其父物件(如果沒有父物件,則相對於世界)的方向。為了圍繞給定軸以給定的角速度(度/秒)旋轉,可以使用函式Transform.Rotate
using UnityEngine;
[ExecuteInEditMode]
public class Angle_Axis_Rotating : MonoBehaviour {
public float degreesPerSecond;
public Vector3 axis;
void Update() {
transform.Rotate(axis,
degreesPerSecond * Time.deltaTime,
Space.Self);
}
}
degreesPerSecond是角速度,它指定物件旋轉的速度。但是,Transform.Rotate需要以度為單位的旋轉角度。要計算此角度,必須將角速度乘以自上次呼叫Update以來經過的時間(以秒為單位),即Time.deltaTime。
此外,Transform.Rotate接受第三個引數,該引數指定旋轉軸所指定的座標系:Space.Self表示物件的區域性座標系,Space.World表示世界座標系。使用Space.Self,我們可以透過指定俯仰軸(1,0,0)、偏航軸(0,1,0)和滾轉軸(0,0,1)輕鬆模擬滾轉、俯仰和偏航。
您可能已經在上面的程式碼中注意到,Unity 使用四元數(實際上是歸一化的四元數)來表示旋轉。例如,變數Transform.localRotation的型別為Quaternion。
從某種意義上說,四元數只是具有某些特殊函式的四維向量。長度為 1 的歸一化四元數對應於旋轉,並且當您知道歸一化的旋轉軸(x、y、z)和旋轉角 α 時,很容易構建。相應的四元數 q 只是
但是,在 Unity 中,您可以使用建構函式Quaternion.AngleAxis(alpha, new Vector3(x, y, z))(其中 alpha 以度為單位)來構造歸一化四元數。(請參閱上一小節中的示例。)
反之,具有分量的歸一化四元數 q 對應於角度的旋轉。可以透過歸一化 3D 向量來確定旋轉軸的方向。在 Unity 中,您可以使用函式Quaternion.ToAngleAxis來計算相應的軸和角度。
由於歸一化四元數對應於旋轉,因此它們也對應於旋轉矩陣。實際上,兩個歸一化四元數的乘積對應於以相同順序對應的旋轉矩陣的乘積。計算兩個四元數的乘積實際上有點棘手,但在 Unity 中,您可以只使用*運算子。因此,在四元數乘積(以及逆四元數)的情況下,將四元數視為“類似於旋轉矩陣”而不是四維向量是有用的。
增量旋轉在即時圖形中非常常見。關於某個軸**在物體區域性座標系中**進行給定角速度旋轉的程式碼需要將先前的旋轉矩陣(指定先前的方向)與新的增量旋轉矩陣組合。如果增量旋轉矩陣在區域性座標中應用,則應首先應用它(當點仍在區域性座標中時,即在任何其他變換之前)。由於矩陣乘積應從右到左讀取,這意味著我們必須按此順序將先前的旋轉矩陣與增量旋轉矩陣相乘。先前方向的四元數為transform.localRotation,此程式碼中新的增量四元數稱為q
using UnityEngine;
[ExecuteInEditMode]
public class Quaternion_Rotating : MonoBehaviour {
public float degreesPerSecond; // angular speed
public Vector3 axis;
void Update() {
Quaternion q = Quaternion.AngleAxis(
degreesPerSecond * Time.deltaTime, axis);
transform.localRotation = transform.localRotation * q;
}
}
如果最後一行中的四元數乘積更改為
transform.localRotation =
q * transform.localRotation;
它對應於在**父座標系**中應用增量旋轉(如果不存在父級,則為世界座標系),就像在將頂點變換到父座標系後應用旋轉矩陣一樣。(始終記住矩陣乘積和四元數乘積應從右到左讀取,因為列向量是從右側乘以的。)
在許多情況下,將四元數視為旋轉矩陣,然後將旋轉矩陣視為座標系之間的(被動)變換是有用的。例如,從物體到世界座標的變換矩陣可以認為是從父座標系到世界座標的變換矩陣乘以從物體座標到父座標系的變換矩陣的乘積(記住從右到左讀取矩陣乘積),即
物體的全域性和區域性旋轉以及其父級的旋轉的對應四元數按相同的順序相乘
我們可以針對任何一個矩陣或四元數求解這些方程。例如,我們可能有一個全域性旋轉(例如,來自Quaternion.LookRotation),但我們需要知道區域性旋轉,因為某些函式需要它,例如Animator.SetBoneLocalRotation。為此,我們可以透過從左側乘以的逆矩陣並將左側與右側交換來求解矩陣方程以獲得
同樣,將這些矩陣視為四元數的表示,我們可以為相應的四元數編寫一個方程
因此,如果我們有一個四元數 qGlobal 表示遊戲物件 gameObject 的全域性旋轉,那麼我們可以使用以下程式碼計算區域性旋轉 qLocal
Quaternion qLocal =
Quaternion.Inverse(gameObject.transform.parent.rotation)
* qGlobal;
在虛擬現實 (VR) 應用程式中,通常需要根據跟蹤器的方向設定物件的方位。例如,可能需要根據連線到使用者腳部的跟蹤器方向設定化身虛擬腳部的方位。或者可能需要根據頭戴式顯示器的方向設定虛擬攝像機的方位。
在這些情況下,跟蹤器的初始方向通常是未知的,並且不應該重要。因此,通常使用校準,即要求使用者假設某個姿勢(校準姿勢),並記錄校準姿勢下跟蹤器的方向。校準後,使用跟蹤器的當前方向來確定物件的方向。如果跟蹤器的當前方向與其校準姿勢的方向相同,則物件的方向應對應於校準姿勢。否則,使用跟蹤器的當前方向與其校準姿勢的方向之間的偏差來旋轉校準姿勢。
此操作的數學原理如下:我們用四元數 表示校準時跟蹤器的方向,用四元數 表示跟蹤器的當前方向;兩者都相對於世界座標系。當前跟蹤器方向與校準時跟蹤器方向之間的偏差由乘積 給出。從右到左讀取乘積:我們撤消校準時跟蹤器的記錄旋轉(因此是逆運算),然後根據當前跟蹤器方向旋轉。(由於 是相對於世界座標系的旋轉,因此必須從左側進行乘法。)如果這兩個四元數相等,則兩個方向之間沒有偏差。在這種情況下,四元數與其逆的乘積抵消了任何旋轉,得到的乘積表示 0 度的旋轉,即沒有旋轉,這與兩個方向之間沒有偏差時的情況一致。
對於物件的實際方向,我們需要用此偏差旋轉校準姿勢。由於此偏差以世界座標給出,因此我們只需將此偏差從左側乘以表示校準姿勢的四元數
.
此乘積表示基於校準跟蹤器的物件的最終方向。
至少在計算機圖形學中,(歸一化)四元數相當無害,並且永遠不會比旋轉矩陣或軸角表示更復雜(取決於上下文)。它們的具體優勢在於它們沒有萬向節死鎖(與尤拉角相反),它們可以透過乘法輕鬆組合(與尤拉角和旋轉的軸角表示相反),並且它們易於歸一化(與旋轉矩陣的正交化相反)。因此,大多數圖形應用程式在內部使用四元數來表示旋轉——即使在使用者介面中使用尤拉角。
在這個相當理論的教程中,我們已經瞭解了
- Unity 用於外旋的尤拉角(即俯仰、航向和橫滾)。
- Unity 用於內旋的尤拉角(即俯仰、偏航和滾轉)。
- 旋轉的矩陣表示。
- 旋轉的軸角表示。
- 旋轉的四元數表示。
如果您想了解更多資訊
- 關於模型矩陣,您可以閱讀 “頂點變換” 部分的描述。
- 關於尤拉角,您可以閱讀 關於尤拉角的維基百科文章。
- 關於使用尤拉角指定車輛方向,您可以閱讀 關於座標系約定的維基百科文章。
- 關於 Unity 中的相關類,您應該閱讀
Transform、Quaternion和Matrix4x4的官方文件。