1. 程式人生 > >四元數和尤拉角

四元數和尤拉角


轉載自 【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中使用左手座標系,假如把世界座標系跟東南西北進行結合起來看,大致如下圖所示:

image

預設的方向對應如下表:

座標軸 對應方向
+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)所有方向的向量都是相同的結果。

image

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

驗證前方上方矢量表示法

為了驗證前方上方矢量表示法的實際上方會重新計算,我設計了以下小實驗。

image

在場景中設定三個物體,它們的朝向是打亂的,從左到右分別對應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方法中執行上述程式碼後,如下:

image

三個物體朝向是一致的,也就說明了上方向量確實是進行了重新計算。

總結幾種表示方法

下面使用程式碼總結幾種表示法,對應同樣的四元數,大致有四種表示方法。

//旋轉量的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);

它們的輸出結果是:

image

也就是說,這幾種形式表示的四元數結果完全相同。

將四元數旋轉應用於子彈射擊示例

當槍管轉動起來,子彈仍然沿著正確的朝向發射出去,可以使用很簡單的幾句話,修改之前的程式碼後如下:

Bullet_2 bullet = m_compPool.takeUnit<Bullet_2>();
//發射時,將子彈的初始位置為槍口的當前位置
bullet.m_transform.position = m_transform.position;
//將子彈的初始化旋轉設定為指向當前槍口前方
bullet.m_transform.rotation = Quaternion.LookRotation(
                                                        m_transform.forward);

image