四元數(shù)應(yīng)用——轉(zhuǎn)矩陣、Slerp插值與萬向節(jié)
四元數(shù)應(yīng)用——順序無關(guān)的旋轉(zhuǎn)混合
—————————————————————————————————————————
今天說一些關(guān)于四元數(shù)的相關(guān)實例,較前兩篇可能有些零碎,算是對于前兩章的補充說明。具體來說,我們接下來會討論三個問題,第一個是四元數(shù)與矩陣的轉(zhuǎn)換,其次是四元數(shù)的插值問題,最后說一下萬向節(jié)死鎖問題。話不多說,開始正題。
1.四元數(shù)的矩陣形式目前對于大多數(shù)底層的圖形API,其對于空間坐標(biāo)的轉(zhuǎn)換還是基于矩陣。所以當(dāng)我們使用四元數(shù)對旋轉(zhuǎn)進行操作,最終傳遞給頂點著色器的數(shù)據(jù)還是要以矩陣的形式。不過,從應(yīng)用層的角度來說,我們只要滿足如下的公式就可以實現(xiàn)轉(zhuǎn)換。
我相信大多數(shù)人并不滿足于背公式,推導(dǎo)該公式的方法主要有兩種。其中一種就是從代數(shù)的角度進行求解,說白了就是硬算。我們在《四元數(shù)和旋轉(zhuǎn)(二)》中提到一個公式:
我們分別求出各項,可以得到三個矩陣,然后相加就是上面那個轉(zhuǎn)換公式。這里不做推導(dǎo),有興趣的可以自己算一算。下面介紹一種比較靈性的一種證明方法。當(dāng)給定下面兩個四元數(shù):
我們令他們相乘,其實可以寫成矩陣與向量相乘的形式,如下圖:
可以這么說,兩個四元數(shù)相乘轉(zhuǎn)變成矩陣L右乘四元數(shù)q。發(fā)散一下思維,我們還可以將該行為寫成矩陣R左乘四元數(shù)q,具體形式如下圖:
最體看L和R括號里面的四元數(shù)下標(biāo),由于四元數(shù)不滿足交換律,所以順序很重要,而括號里面的下標(biāo)其實是不一樣的,這個地方一定要注意?;A(chǔ)知識講完了,下面就直接進行應(yīng)用。根據(jù)旋轉(zhuǎn)公式和以上的定義,我們很容易得到如下公式:
然后將L和R帶入上述的矩陣中,我們可以得到:
證畢!
2.四元數(shù)的Slerp插值其實插值問題一直都算是圖形學(xué)中比較經(jīng)典的問題,一般來說線性插值可以滿足大多數(shù)的情況,但是對于旋轉(zhuǎn)來說,線性插值肯定效果不好,我們來看下圖:
從圖中很明顯看出,左圖(線插)要遜于右圖(球插)。一種從動畫的角度解釋是,為了保證每幀的旋轉(zhuǎn)都是均勻變化的,線性插值得到的旋轉(zhuǎn)結(jié)果一定是不均勻的,這主要考慮的是旋轉(zhuǎn)角度。那我們從代數(shù)的角度去思考,如果兩個單位四元數(shù)之間進行插值,如左圖的線性插值,得到的四元數(shù)一定不是單位四元數(shù),我們期望對于旋轉(zhuǎn)的插值應(yīng)該是不改變長度的,所以顯然右圖球面(Slerp)插值更為合理。
關(guān)于四元數(shù)的球面插值證明就很多了,Wiki上面就有很詳細的證明以及實現(xiàn)的Code。主體思想其實就是施密特正交,先根據(jù) 和 解算出兩個正 交的四元數(shù),然后通過加權(quán)算出最終的 。下圖能很好的說明白這個事。
下面主要討論的是這個問題,上圖中給出了球面插值公式的一種形式,我們暫且稱為加法形式。由于四元數(shù)旋轉(zhuǎn)是相乘的形式,我們上述的公式亦可以寫成:
鑒于刨根問底的精神,首先先說說四元數(shù)的差(Difference),這個比較好理解,類似于矩陣,A和B的差,可以理解成先旋轉(zhuǎn)A逆然后再旋轉(zhuǎn)B,得到A和B之間的差值,表示為 。然后我們?nèi)绻倍的差或者直接說差乘以一個t因子,這里就要引用了我們在《四元數(shù)與旋轉(zhuǎn)(一)》中所述的四元數(shù)指數(shù)形式。對于 ,我們來看看 和 的情況,具體如下:
最后我們令 以及 ,根據(jù)下面公式重新審視一下球面插值:
可以看出,兩種表現(xiàn)形式之間是可以互相轉(zhuǎn)化的。
3.萬向節(jié)死鎖其實這個問題不能算的上四元數(shù)的應(yīng)用,萬向節(jié)死鎖早期是用來處理機械臂在旋轉(zhuǎn)時自由度缺失的問題,由于當(dāng)時機械臂的關(guān)節(jié)都是單自由度的,所以在模擬人體某些球形關(guān)節(jié)(自由度為3的關(guān)節(jié))的時候,會采用三組相互正交的機械關(guān)節(jié)進行模擬。
如圖所示,機械關(guān)節(jié)1-3共同模擬了手腕的活動,這里會導(dǎo)致一個問題,當(dāng)關(guān)節(jié)2旋轉(zhuǎn)90°后,關(guān)節(jié)1和關(guān)節(jié)3會重合,這樣無論旋轉(zhuǎn)關(guān)節(jié)1還是旋轉(zhuǎn)關(guān)節(jié)3都只會沿著 軸進行旋轉(zhuǎn),這就是萬向節(jié)死鎖。值得一提的是,當(dāng)你旋轉(zhuǎn)關(guān)節(jié)1,關(guān)節(jié)2和關(guān)節(jié)3都會隨著一起動,而當(dāng)你旋轉(zhuǎn)關(guān)節(jié)3,無論怎么旋轉(zhuǎn)也不會影響關(guān)節(jié)2和關(guān)節(jié)1,所以只有關(guān)節(jié)2旋轉(zhuǎn)90°會產(chǎn)生萬向節(jié)死鎖。
當(dāng)早期計算機動畫將機器人那套東西搬過來的時候,最簡單的做法就是用歐拉角直接模擬旋轉(zhuǎn)。這就導(dǎo)致了萬向節(jié)死鎖。那怎么去理解計算機動畫里面的這個問題呢,畢竟動畫里面也沒有機械關(guān)節(jié)。
我們假定三個歐拉角的旋轉(zhuǎn)順序如下
當(dāng) 的時候, 和 就會重合,如右圖,這樣就會產(chǎn)生萬向節(jié)死鎖。
所以為了避免這個問題,圖形學(xué)旋轉(zhuǎn)開始使用軸角,因為軸角可以講三個歐拉角等效成一個繞著特定軸旋轉(zhuǎn)的角度。低版本的OpenGL有個函數(shù)glRotate函數(shù),實現(xiàn)的方法就是使用軸角。當(dāng)然,并不是軸角不存在萬向節(jié)死鎖,當(dāng)存在三個相互正交的軸角按一定順序進行旋轉(zhuǎn),依然會產(chǎn)生萬向節(jié)死鎖,不過這種情況很難發(fā)生。
那么軸角已經(jīng)解決這個問題,為什么我們要用四元數(shù)?
4.四元數(shù)總結(jié)最后點個題,呼應(yīng)一下前文,具體說說四元數(shù)的好處:
解決萬向節(jié)死鎖(Gimbal Lock)問題。(不要去用四元數(shù)模擬歐拉角?。﹥H需存儲4個浮點數(shù),相比矩陣更加輕量。比如矩陣至少要用9個float進行表示旋轉(zhuǎn)信息,即便加上旋轉(zhuǎn)縮放,也會比矩陣少存2-6個float,對于在PC上開發(fā)的游戲可能并不顯著,畢竟現(xiàn)在內(nèi)存都大,但在家用機(特別是上世代)上,內(nèi)存比較吃緊的情況下就相當(dāng)有利了。四元數(shù)無論是求逆、串聯(lián)等操作,相比矩陣更加高效。比如取反就等同于求逆,即便是正交矩陣,轉(zhuǎn)置的操作代價也比取反要高得多,更何況大多數(shù)情況由于存在縮放,求逆的操作會更加復(fù)雜。但是,使用四元數(shù)需要考慮將四元數(shù)與矩陣之間轉(zhuǎn)換的成本,不過綜合考慮,還是四元數(shù)操作成本比較低。基本上四元數(shù)在我這也就告一段落了,之后會有請人 @Obsver Anonym 續(xù)一篇具體應(yīng)用的文章,請大家敬請期待!