Unity3D中的Quaternion(四元數)

  • 2019 年 12 月 2 日
  • 筆記

Unity3D中的Quaternion(四元數)

四元數的概念

四元數,這是一個圖形學的概念,一般沒怎麼見過,圖形學中比較常見的角位移的表示方法有「矩陣」、「歐拉角」、「四元數」這三種。可以說各有各的優點和不足,不同的場合用不同的方法。其中四元數的優點有:平滑插值、快速連接、角位移求逆、可以與矩陣形式快速轉換、僅用四個數表示。不過,它也有一些缺點:比歐拉角多一個數表示、可能不合法(如:壞的輸入數據或者浮點數累計都可能使四元數不合法,不過可以通過四元數標準化來解決這個問題)、晦澀難懂。

那為啥四元數是四個數呢?其實還是有個小故事的。話說當時十九世紀的時候,愛爾蘭的數學家Hamilton一直在研究如何將複數從2D擴展至3D,他一直以為擴展至3D應該有兩個虛部(可是他錯了,哈哈)。有一天他在路上突發奇想,我們搞搞三個虛部的試試!結果他就成功了,於是乎他就把答案刻在了Broome橋上。說到這裡,也就明白了,四元數其實就是定義了一個有三個虛部的複數w+xi+yj+zk。記法[w,(x,y,z)]。

好了,上面我們就基本清楚四元數的作用以及好處與坑了,下面開始正式講講Unity中我們如何使用一些常見的四元數操作。

Unity中的四元數

基本的旋轉,我們可以通過Transform.Rotate來實現,但是當我們希望對旋轉角度進行一些計算的時候,就要用到四元數Quaternion了。Quaternion的變數比較少也沒什麼可說的,大家一看都明白。唯一要說的就是xyzw的取值範圍是[-1,1],物體並不是旋轉一周就所有數值回歸初始值,而是兩周。 初始值: (0,0,0,1) 沿著y軸旋轉:180°(0,1,0,0) 360°(0,0,0,-1)540°(0,-1,0,0) 720°(0,0,0,1) 沿著x軸旋轉:180°(-1,0,0,0) 360°(0,0,0,-1)540°(1,0,0,0) 720°(0,0,0,1) 無旋轉的寫法是Quaternion.identify。

在unity3d中, quaternion 的乘法操作 (operator * ) 有兩種操作: (1) quaternion * quaternion , 例如 q = t * p; 這是將一個點先進行t 操作旋轉,然後進行p操作旋轉. (2) Quaternion * Vector3, 例如 p : Vector3, t : Quaternion , q : Quaternion; q = t * p; 這是將點p 進性t 操作旋轉; 我進行的是第2種操作,即對一個向量進行旋轉; 首先 ,Quaternion 的基本數學方程為 : Q = cos (a/2) + i (x * sin(a/2)) + j (y * sin(a/2)) + k(z * sin(a/2)) (a 為旋轉角度) Q.w = cos (angle / 2) Q.x = axis.x * sin (angle / 2) Q.y = axis.y * sin (angle / 2) Q.z = axis.z * sin (angle / 2) 我們只要有角度就可以給出四元數的四個部分值,例如我想要讓點M=Vector3(o,p,q) 繞x軸順時針旋轉90度;那麼對應的quaternion數值就應該為: Q : Quaternion; Q.x = 1 * sin(90度/2) = sin(45度) = 0.7071 Q.y = 0; Q.z = 0; Q.w = cos(90度/2) = cos (45度) = 0.7071 Q = (0.7071, 0 , 0 , 0.7071); m = Q * m; (將點m 繞 x軸(1,0,0) 順時針旋轉了90度)

下面我就按照Unity的API介紹下四元數相關的幾個基本函數。

一、LookRotation

聲明形式:public static Quaternion LookRotation ( Vector3 forward, Vector3 upwards=Vector3.up )

這個功能很實用,傳入的兩個參數分別代表前方盯著的方向以及自己的上方向。它可以讓一個GameObject轉動腦袋盯著另一個物體。如:

    public Transform target;      void Update() {          Vector3 relativePos = target.position - transform.position;          Quaternion rotation = Quaternion.LookRotation(relativePos);          transform.rotation = rotation;      }

這段程式碼就可以讓當前的object時時盯著target不放,當然,你也可以自定義up朝向,這裡默認是Vector3.up。

二、Angle

聲明形式:public static float Angle ( Quaternion a, Quaternion b )

這個就比較簡單了,它可以計算兩個旋轉之間的夾角。與Vector3.Angle()作用是一樣的。

三、Euler

聲明形式:public static Quaternion Euler ( float x, float y, float z )

或者: public static Quaternion Euler ( Vector3 euler )

這個函數可以將一個歐拉形式的旋轉轉換成四元數形式的旋轉。傳入的參數分別是歐拉軸上的轉動角度。

四、Slerp

聲明形式:public static Quaternion Slerp ( Quaternion from, Quaternion to, float t )

基本意思就是線性地從一個角度旋轉到另一個角度,其中,旋轉勻速增加t。

附加內容:很多時候from 和to都不是固定的,而且上一個腳本也不能保證所有角度下的旋轉速度一致。所以我寫了這個腳本來保證可以應付大多數情況。

Transform target; float rotateSpeed = 30.0f; Quaternion wantedRotation = Quaternion.FromToRotation(transform.position, target.position); float t = rotateSpeed/Quaternion.Angle(transform.rotation, wantedRotation)*Time.deltaTime; transform.rotation = Quaternion.Slerp(transform.rotation, target.rotation, t);

這個腳本可以保證物體的旋轉速度永遠是rotateSpeed。如果自身坐標和目標之間的夾角是X度,我們想以s=30度每秒的速度旋轉到目標的方向,則每秒旋轉的角度的比例為s/X。 再乘以每次旋轉的時間Time.deltaTime我們就得到了用來勻速旋轉的t。

五、FromToRotation

聲明形式:public static Quaternion FromToRotation ( Vector3 from, Vector3 to )

它是得到從一個方向到另一個方向的旋轉。就是轉一個方向,就這麼簡單。

六、identity

這個不是一個函數,它是一個只讀的變數。它代表世界坐標系或者父物體坐標系中的無旋轉方位。