爲何使用四元數

轉載:http://www.game798.com/html/2007-05/3689.htmhtml

好吧,我必須認可到目前爲止我尚未徹底理解四元數,我一度把四元數理解爲軸、角表示的4維向量,也就在下午我才從和同事的爭辯中理解了四元數不徹底是角、軸這麼簡單,爲此寫點心得給那些同我同樣搞了2年3D遊戲的還不清楚四元數的朋友。



爲何使用四元數

爲 了回答這個問題,先來看看通常關於旋轉(面向)的描述方法-歐拉描述法。它使用最簡單的x,y,z值來分別表示在x,y,z軸上的旋轉角度,其取值爲 0-360(或者0-2pi),通常使用roll,pitch,yaw來表示這些份量的旋轉值。須要注意的是,這裏的旋轉是針對世界座標系說的,這意味着 第一次的旋轉不會影響第2、三次的轉軸,簡單的說,三角度系統沒法表現任意軸的旋轉,只要一開始旋轉,物體自己就失去了任意軸的自主性,這也就致使了萬向 軸鎖(Gimbal Lock)的問題。

還有一種是軸角的描述方法(即我一直覺得的四元數的表示法),這種方法比歐拉描述要好,它避免了 Gimbal Lock,它使用一個3維向量表示轉軸和一個角度份量表示繞此轉軸的旋轉角度,即(x,y,z,angle),通常表示爲(x,y,z,w)或者 (v,w)。但這種描述法卻不適合插值。

那到底什麼是Gimbal Lock呢?正如前面所說,由於歐拉描述中針對x,y,z的旋轉描述是世界座標系下的值,因此當任意一軸旋轉90°的時候會致使該軸同其餘軸重合,此時旋 轉被重合的軸可能沒有任何效果,這就是Gimbal Lock,這裏有個例子演示了Gimbal Lock,點擊這裏下載。運行這個例子,使用左右箭頭改變yaw爲90°,此時不論是使用上下箭頭仍是Insert、Page Up鍵都沒法改變Pitch,而都是改變了模型的roll。

那麼軸、角的描述方法又有什麼問題呢?雖然軸、角的描述解決了Gimbal Lock,但這樣的描述方法會致使差值不平滑,差值結果可能跳躍,歐拉描述一樣有這樣的問題。



什麼是四元數

四元數通常定義以下:

q=w+xi+yj+zk

其中w是實數,x,y,z是虛數,其中:

i*i=-1

j*j=-1

k*k=-1

也能夠表示爲:

q=[w,v]

其中v=(x,y,z)是矢量,w是標量,雖然v是矢量,但不能簡單的理解爲3D空間的矢量,它是4維空間中的的矢量,也是很是不容易想像的。

四元數也是能夠歸一化的,而且只有單位化的四元數才用來描述旋轉(面向),四元數的單位化與Vector相似,

首先||q|| = Norm(q)=sqrt(w2 + x2 + y2 + z2)

由於w2 + x2 + y2 + z2=1

因此Normlize(q)=q/Norm(q)=q / sqrt(w2 + x2 + y2 + z2)

說了這麼多,那麼四元數與旋轉到底有什麼關係?我之前一直認爲軸、角的描述就是四元數,若是是那樣其與旋轉的關係也不言而喻,但並非這麼簡單,軸、角描述到四元數的轉化:

w = cos(theta/2)

x = ax * sin(theta/2)

y = ay * sin(theta/2)

z = az * sin(theta/2)

其 中(ax,ay,az)表示軸的矢量,theta表示繞此軸的旋轉角度,爲何是這樣?和軸、角描述到底有什麼不一樣?這是由於軸角描述的「四元組」並非 一個空間下的東西,首先(ax,ay,az)是一個3維座標下的矢量,而theta則是級座標下的角度,簡單的將他們組合到一塊兒並不能保證他們插值結果的 穩定性,由於他們沒法歸一化,因此不能保證最終插值後獲得的矢量長度(通過旋轉變換後兩點之間的距離)相等,而四元數在是在一個統一的4維空間中,方便歸 一化來插值,又能方便的獲得軸、角這樣用於3D圖像的信息數據,因此用四元數再合適不過了。



關於四元數的運算法則和推導這裏有篇詳細的文章介紹,重要的是一點,相似與Matrix的四元數的乘法是不可交換的,四元數的乘法的意義也相似於Matrix的乘法-能夠將兩個旋轉合併,例如:

Q=Q1*Q2

表示Q的是先作Q2的旋轉,再作Q1的旋轉的結果,而多個四元數的旋轉也是能夠合併的,根據四元數乘法的定義,能夠算出兩個四元數作一次乘法須要16次乘法和加法,而3x3的矩陣則須要27運算,因此當有屢次旋轉操做時,使用四元數能夠得到更高的計算效率。



爲何四元數能夠避免Gimbal Lock

在歐拉描述中,之因此會產生Gimbal Lock是由於使用的三角度系統是依次、順序變換的,若是在OGL中,代碼可能這樣:

glRotatef( angleX, 1, 0, 0)

glRotatef( angleY, 0, 1, 0)

glRotatef( angleZ, 0, 0, 1)



注意:以上代碼是順序執行,而使用的又是統一的世界座標,這樣當首先旋轉了Y軸後,Z軸將再也不是原來的Z軸,而可能變成X軸,這樣針對Z的變化可能失效。

而四元數描述的旋轉代碼多是這樣:

TempQ = From Eula(x,y,z)

FinalQ =CameraQ * NewQ

theta, ax, ay, az = From (FinalQ)

glRotatef(theta, ax, ay, az);

其中(ax,ay,az)描述一條任意軸,theta描述了繞此任意軸旋轉的角度,而全部的參數都來自於全部描述旋轉的四元數作乘法以後獲得的值,能夠看出這樣一次性的旋轉不會帶來問題。這裏有個例子演示了使用四元數不會產生Gimbal Lock的問題。



關於插值

使用四元數的緣由就是在於它很是適合插值,這是由於他是一個能夠規格化的4維向量,最簡單的插值算法就是線性插值,公式如:

q(t)=(1-t)q1+t q2

但這個結果是須要規格化的,不然q(t)的單位長度會發生變化,因此

q(t)=(1-t)q1+t q2 / || (1-t)q1+t q2 ||

如圖:



儘管線性插值頗有效,但不能以恆定的速率描述q1到q2之間的曲線,這也是其弊端,咱們須要找到一種插值方法使得q1->q(t)之間的夾角θ是線性的,即θ(t)=(1-t)θ1+t*θ2,這樣咱們獲得了球形線性插值函數q(t),以下:

q(t)=q1 * sinθ(1-t)/sinθ + q2 * sinθt/sineθ

若是使用D3D,能夠直接使用D3DXQuaternionSlerp函數就能夠完成這個插值過程。算法

相關文章
相關標籤/搜索