四元數(Quaternions)-ok

爲什麼使用四元數

爲了回答這個問題,先來看看一般關於旋轉(面向)的描述方法-歐拉描述法。它使用最簡單的x,y,z值來分別表示在x,y,z軸上的旋轉角度,其取值爲0-360(或者0-2pi),一般使用roll,pitch,yaw來表示這些分量的旋轉值。需要注意的是,這裏的旋轉是針對世界座標系說的,這意味着第一次的旋轉不會影響第二、三次的轉軸,簡單的說,三角度系統無法表現任意軸的旋轉,只要一開始旋轉,物體本身就失去了任意軸的自主性,這也就導致了萬向軸鎖(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函數就可以完成這個插值過程。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章