四元數和歐拉角
轉載自 【Unity編程】Unity中關於四元數的API詳解 - CSDN博客 ,有簡單刪改
Quaternion類
Quaternion(四元數)用於計算Unity旋轉。它們計算緊湊高效,不受萬向節鎖的困擾,並且可以很方便快速地進行球面插值。 Unity內部使用四元數來表示所有的旋轉。
Quaternion是基於復數,並不容易直觀地理解。 不過你幾乎不需要訪問或修改單個四元數參數(x,y,z,w); 大多數情況下,你只需要獲取和使用現有的旋轉(例如來自“Transform”),或者用四元數來構造新的旋轉(例如,在兩次旋轉之間平滑插入)。
大部分情況下,你可能會使用到這些函數:
Quaternion.LookRotation, Quaternion.Angle Quaternion.Euler Quaternion.Slerp Quaternion.FromToRotation Quaternion.identity。
Quaternion 是一個結構體,本身成員變量相對簡單,可以作為函數參數高效傳遞。
方向的概念
Unity默認方向
在深入了解API之前,我們需要先明確一些基本的概念,就是方向、旋轉究竟是如何表示的。
Unity中使用左手坐標系,假如把世界坐標系跟東南西北進行結合起來看,大致如下圖所示:
默認的方向對應如下表:
坐標軸 | 對應方向 |
---|---|
+x | 右(東) |
-x | 左(西) |
+y | 上 |
-y | 下 |
+z | 前(北) |
-z | 後(南) |
假設以你自己身體為例,你站立在地面上,面朝北方,此時就是默認方向,也就是Unity中的方向就是面向+Z軸方向,那麽此時+X軸在東方,+Y軸對應正上方。此時對應的歐拉角是(0,0,0),此時對應的前方矢量是(0,0,1),上方矢量是(0,1,0)。
這裏我區分了左右上下前後的概念,因為這些概念同時也對應了Vector3類、Transform類中的相應的方向函數。
方向的表示法
1.歐拉角表示法
假如你使用一組歐拉角表示旋轉,XYZ三個參數代表相應軸向按照順歸YZX的旋轉,因此(0、90、90)代表先進行+Z軸旋轉90度,再沿著+Y軸進行90度旋轉,更多詳細內容可以參考前述文章【Unity編程】Unity中的歐拉旋轉 - CSDN博客。
2.前方上方矢量界定法
編程過程中,大部分需要明確指定方位的時候就需要使用這個方法。要確定一個朝向,我們可以使用兩個向量來確定:即前方矢量和上方矢量。當一個朝向的前方和上方確定之後,這個朝向也就完全確定了。
舉例來說,如果現在只提供一個朝向,就是你現在面朝北方,那麽這個方向已經完全確定了嗎?顯然沒有。因為你右側躺在地上,看向北方,還是在面朝北方,這時候就需要另外一個矢量,也就是上方。當給出上方之後,這個朝向就完全確定了。
上方需要嚴格給出嗎?
在Unity中,我們很多時候,不需要給出嚴格的上方朝向。比如,仍然是上面那個例子,如果我面朝北方,先給出(0,0,1)代表我的前方矢量。那麽,如果我給出的方向不是嚴格的上方矢量,比如是(0,0.5,0.5),是否可以?答案也是可以的,因為這兩個矢量顯然已經確定了一個方向,前方是嚴格的,而實際的上方可以通過前方朝著你給出的上方矢量旋轉90度得出。也就是說,你給(0,1,0)作為上方矢量,和給出在下圖中弧度範圍內(不包含+Z和-Z)所有方向的矢量都是相同的結果。
3.繞軸旋轉界定法
第三種定義旋轉的方法就是圍繞某個指定的軸向旋轉一定的角度。這個方法也可以確定一個相對旋轉,它以從默認方向(此時前方+Z,上方+Y)出發,沿著指定的軸向進行指定角度的旋轉,旋轉後的前方和上方是確定的。因此這個方法也可以用來確定朝向。
4.A向到B向相對旋轉表示法
還有一種方法就是從A向到B向的相對旋轉,這種表示了一個旋轉的相對變化。比如A為(0,1,0),B為(0,0,1),也就是相對旋轉量代表原來的上方被旋轉到了前方,這樣的一個四元數也可以用歐拉角表示成(90,0,0),也就是沿著+X軸旋轉了90度。
註意上面四中表示方法中,有的明確表明了上方矢量,有的好像只明確了前方矢量,要明確的一點就是,它們都是從默認矢量出發的,如果沒有明確指定上方朝向,那麽就是使用默認的上方,也就是+Y方向。
使用文檔
成員變量
- eulerAngles 歐拉角,返回當前四元數所對應的歐拉角
- this[int] 可以使用類似數組和下標的形式從四元數中獲取四個四元數參數
- x、y、z、w 分別代表x、y、z、w 參數,具體代表的內容可以參考前文【Unity編程】四元數(Quaternion)與歐拉角 - CSDN博客,你最好不要通過修改四個參數來改變四元數,除非你真的非常了解它們的含義。
靜態成員
identity 單位四元數,也就是默認的無旋轉狀態,此時與世界坐標相同,前方指向+Z,上方指向+Y
成員函數
函數形式 | 解釋 |
---|---|
void Set(float new_x, float new_y, float new_z, float new_w) | 設置x、y、z、w 分量,與this[]功能相同 |
void SetFromToRotation(Vector3 fromDirection, Vector3 toDirection) | 設置成靜態函數FromToRotation的結果 |
void SetLookRotation(Vector3 view, Vector3 up = Vector3.up) | 設置成靜態函數LookRotation的結果 |
void ToAngleAxis(out float angle, out Vector3 axis) | 設置成靜態函數AngleAxis的結果 |
說明:成員函數幾個set方法多用於將當前四元數設置成目標四元數,目標四元數的構建方法與對應名稱的靜態函數相同。
靜態函數
函數形式 | 解釋 |
---|---|
static float Angle(Quaternion a, Quaternion b) | 計算兩個四元數前方矢量之間的夾角度數 |
static Quaternion AngleAxis(float angle, Vector3 axis) | 構建一個四元數,它表示沿著一個軸旋轉固定角度,即上述表示法 3 |
static float Dot(Quaternion a, Quaternion b) | 計算兩個四元數之間的點積,返回一個標量,這個函數一般用不到,它的點積不代表什麽具體的物理含義,具體定義方法見我的前述文章 |
static Quaternion Euler(float x, float y, float z) | 構建一個四元數,它用歐拉旋轉表示,即上述表示法 1 |
static Quaternion FromToRotation(Vector3 fromDirection, Vector3 toDirection) | 構建一個四元數,它表示從指向fromDirection方向到指向toDirection方向的相對旋轉量,見上述表示法 4 |
static Quaternion Inverse(Quaternion rotation) | 構建一個四元數,它是指定的四元數的逆,也就是逆向旋轉,比如原四元數表示相對+X軸旋轉了90度,那麽此函數結果就是相對+X軸旋轉了-90度 |
static Quaternion Lerp(Quaternion a, Quaternion b, float t) | 構建一個四元數,表示從四元數a到b的球面插值,所謂的插值也就是中間旋轉量,從a作為起點,此時對應t為0,到b為終點,此時對應t為1。當t取0-1之間的小數時,就代表了中間的插值結果。這個方法與Slerp相同,計算速度快,但是精度低,如果相對旋轉變化量很小,則效果不理想 |
static Quaternion LerpUnclamped(Quaternion a, Quaternion b, float t) | 與Lerp相同,區別是,Lerp的t值會被鉗制在[0,1]之間,而此方法則不會,t允許超出計算 |
static Quaternion LookRotation(Vector3 forward, Vector3 upwards = Vector3.up) | 構建一個四元數,使用前方上方矢量確定朝向,也就是上述表示法 2 |
static Quaternion RotateTowards(Quaternion from, Quaternion to, float maxDegreesDelta) | 構建一個四元數,表示從一個四元數from(的前方)向著另外一個四元數(的前方)旋轉,但不能超出指定的角度,也就是如果兩個前方矢量夾角超過指定角度,則旋轉到達指定角度時就停止,若是夾角本身不足的話,則結果直接為目標四元數to,與上述表示法 4 的意思很接近 |
static Quaternion Slerp(Quaternion a, Quaternion b, float t) | 球面插值,與Lerp功能相同,t值也被鉗制,計算精度高,但是速度相對較慢 |
static Quaternion SlerpUnclamped(Quaternion a, Quaternion b, float t) | 與Slerp功能相同,只是t值不被鉗制,允許超出計算 |
static Quaternion operator * (Quaternion lhs, Quaternion rhs) | 乘法運算符重載,當表示兩個連續的旋轉時,可以使用lhs * rhs的形式得出連續旋轉的結果,lhs為左值,rhs為右值。註意左值是先進行的旋轉,疊加右值旋轉。用法示例:lhs = lhs * rhs; |
static Vector3 operator *(Quaternion rotation, Vector3 point) | 乘法運算符重載,表示對一個矢量point施加旋轉rotation,得出旋轉後的結果矢量。用法示例:Vector3 result=rotation * point |
驗證前方上方矢量表示法
為了驗證前方上方矢量表示法的實際上方會重新計算,我設計了以下小實驗。
在場景中設置三個物體,它們的朝向是打亂的,從左到右分別對應1、2、3。可以使用以下代碼將三個物體朝向調整為一致。
//前方上方矢量界定法的實際上方會重新計算
m_t1.transform.rotation = Quaternion.LookRotation(
Vector3.forward, Vector3.up);
m_t2.transform.rotation = Quaternion.LookRotation(
Vector3.forward, new Vector3(0,0.5f,-0.5f));
m_t3.transform.rotation = Quaternion.LookRotation(
Vector3.forward, new Vector3(0,0.5f,0.5f));
在start方法中執行上述代碼後,如下:
三個物體朝向是一致的,也就說明了上方矢量確實是進行了重新計算。
總結幾種表示方法
下面使用代碼總結幾種表示法,對應同樣的四元數,大致有四種表示方法。
//旋轉量的4種表示形式
Quaternion q1=Quaternion.Euler(90, 0, 0);
Quaternion q2 = Quaternion.LookRotation(Vector3.down ,
Vector3.forward);
Quaternion q3 = Quaternion.AngleAxis(90,Vector3.right);
Quaternion q4 = Quaternion.FromToRotation(Vector3.up,
Vector3.forward);
showQ("q1",q1);
showQ("q2",q2);
showQ("q3",q3);
showQ("q4",q4);
它們的輸出結果是:
也就是說,這幾種形式表示的四元數結果完全相同。
將四元數旋轉應用於子彈射擊示例
當槍管轉動起來,子彈仍然沿著正確的朝向發射出去,可以使用很簡單的幾句話,修改之前的代碼後如下:
Bullet_2 bullet = m_compPool.takeUnit<Bullet_2>();
//發射時,將子彈的初始位置為槍口的當前位置
bullet.m_transform.position = m_transform.position;
//將子彈的初始化旋轉設置為指向當前槍口前方
bullet.m_transform.rotation = Quaternion.LookRotation(
m_transform.forward);
四元數和歐拉角