演講標(biāo)題:
Practical Quaternions: An Easy Guide to 3D Rotations for Non-Mathematicians
演講者信息:
Patrick Martin是來自Google的Developer Relations Engineer,在游戲行業(yè)擁有超過十年的經(jīng)驗,從事移動游戲、PC 游戲和智能玩具方面的工作。他參與開發(fā)了Firebase游戲教程、Star Wars BB-8玩具、Sphero 智能球、Space Miner: Space Ore Bust 、iOS版《大富翁》等。
摘要
Patrick在開發(fā)工作中曾遇到多次只能用四元數(shù)解決的旋轉(zhuǎn)問題。這篇演講的主要目的是幫助游戲開發(fā)者們從實用而非數(shù)學(xué)的角度理解四元數(shù)(Quaternions),從而更好地實現(xiàn)游戲中的三維旋轉(zhuǎn)。Patrick以他參與開發(fā)的iOS版《大富翁》中的旋轉(zhuǎn)功能為例,從實際角度展現(xiàn)了四元數(shù)的實用性。演講中涉及到一些三角函數(shù)和簡單線性代數(shù),但別害怕,一旦理解四元數(shù),你會發(fā)現(xiàn)它也是很直觀的。
為什么要用四元數(shù)
要實現(xiàn)三維空間中的旋轉(zhuǎn),一種比較直觀的方式是動態(tài)歐拉角,也就是用(α, β, γ)三個值來表示某一朝向與物體坐標(biāo)系xyz三條坐標(biāo)軸的夾角。
用一架飛機來理解動態(tài)歐拉角,通常稱沿三軸的旋轉(zhuǎn)為“Yaw, Pitch & Roll”。Roll軸是貫穿機身的前后方向,Pitch軸是貫穿機翼的左右方向,Roll軸是垂直于機身所在平面的豎直方向。對應(yīng)地,Yaw作為動詞指是飛機左右旋轉(zhuǎn)飛行朝向,Pitch指機頭機尾上下俯仰,Roll指繞機翼圍繞機身左右傾斜。

動態(tài)歐拉角很好理解,那為什么我們還要用四元數(shù)呢?Patrick 在演講中展示了一段動畫,兩架飛機,分別用四元數(shù)和歐拉角實現(xiàn)旋轉(zhuǎn),在兩個相同的朝向之間來回擺動。用四元數(shù)的飛機擺動自然平穩(wěn),而用歐拉角的飛機卻不停抽風(fēng)。

這種現(xiàn)象的誘因被稱為萬向鎖(gimbal lock),是使用動態(tài)歐拉角表示三維物體的旋轉(zhuǎn)時會出現(xiàn)的問題。正常狀態(tài)下Yaw, Pitch & Roll 三個旋轉(zhuǎn)軸相互獨立,但一旦選擇±90°作為俯仰角,就會導(dǎo)致三個軸中的兩個所在平面重合,丟失了一個自由度。如圖,飛機繞綠色Pitch軸旋轉(zhuǎn)了90°,與粉色Yaw軸重合了,這時繞藍色Roll軸的旋轉(zhuǎn)就與繞粉色Yaw軸的旋轉(zhuǎn)就沒有任何區(qū)別。

萬向鎖的現(xiàn)象導(dǎo)致我們不能只依賴動態(tài)歐拉角,而需要新工具——四元數(shù)。
理解四元數(shù)的七條規(guī)則
Patrick向我們介紹了7條理解四元數(shù)的簡單規(guī)則:
- 四元數(shù)的虛數(shù)部分就是旋轉(zhuǎn)軸。
- 旋轉(zhuǎn)一半發(fā)生在實部,一半發(fā)生在虛部。
- 做乘法其實就是在應(yīng)用旋轉(zhuǎn)。
- 逆操作(inverse/conjugate)就是虛數(shù)部分取反。
- 與對向量做LERP(線性插值)相似,對四元數(shù)做SLERP。
- 從右手系轉(zhuǎn)換到左手系,對虛部中的一個取反。
- 相信你用的數(shù)學(xué)庫。
規(guī)則1&2:理解四元數(shù)的構(gòu)造
要表示一個旋轉(zhuǎn),要定義一個旋轉(zhuǎn)軸n,這是一個單位向量;以及繞旋轉(zhuǎn)軸的旋轉(zhuǎn)量θ。

一個四元數(shù)(s, i, j, k)由實數(shù)部分s和虛數(shù)部分i,j,k組成。
規(guī)則1:四元數(shù)的虛數(shù)部分就是旋轉(zhuǎn)軸。
向量n在x,y,z三個方向上的分量nx, ny, nz.
那么如何表示旋轉(zhuǎn)量θ?
規(guī)則2:旋轉(zhuǎn)一半發(fā)生在實部,一半發(fā)生在虛部。
實部 = cos(θ/2), 虛部 = sin(θ/2)·n(注意n是向量,虛部三個維度nx, ny, nz分別乘以 sin(θ/2))。記住這兩個表達式,下面將經(jīng)常用到。
嘗試用代碼創(chuàng)建幾個四元數(shù):
Tips: 在草稿紙上畫一張圖復(fù)習(xí)一下三角函數(shù):始終記得cos是在x軸上的投影,sin是在y軸上的投影。
1. 表示無旋轉(zhuǎn)的四元數(shù)identity = (1, 0, 0, 0).
2. 表示上仰的四元數(shù)pitch = (0, 1, 0, 0).
3.表示yaw的四元數(shù)(0, 0, 1, 0)和表示roll的四元數(shù)(0, 0, 0, 1)。
筆者按照上述代碼在Unity中進行實驗,效果如下:
注意Unity中的四元數(shù)表示為(x, y, z, w),演講中的四元數(shù)表示為(s, i, j, k),其中w與s對應(yīng)。x, y, z分別與i,j,k對應(yīng)。
在debug的過程中我們可能需要監(jiān)測游戲?qū)ο蟮膔otation數(shù)值來了解它的旋轉(zhuǎn)狀態(tài)是否符合預(yù)期,但很多人表示,看到一個四元數(shù)無法像看到一個歐拉角一樣直觀地理解。對此Patrick總結(jié)了一些直觀理解四元數(shù)的tips:
1. 記得實數(shù)部分=cos(θ/2),可以畫出單元位半圓,據(jù)此去估計實數(shù)部分所在的角度。
以四元數(shù)(0.9, 0, 0, 0.4)為例,從實數(shù)部分0.9可以估算出θ約為45°。
2. 相似地,虛數(shù)部分= 三個維度*sin(θ/2)。(0.9, 0, 0, 0.4)這個四元數(shù)中x, y軸對應(yīng)的虛數(shù)部分均為0,易知這個旋轉(zhuǎn)是繞z軸一定程度的roll,結(jié)合剛才估算θ約為45°,估算出這個四元數(shù)表示一個約45°的roll。
在Unity中進行實驗Quaternion.AngleAxis(45, Vector3.forward)得到的四元數(shù)數(shù)值剛好完全對應(yīng)。其實Unity中的這一數(shù)值經(jīng)過近似,并不準(zhǔn)確。但證明了這種方法足夠用于直觀估算這一旋轉(zhuǎn)的角度。
3. 再看一個x, y軸對應(yīng)的虛數(shù)部分非零的例子(0.9, 0.3, 0.3, 0)。實數(shù)部分仍是0.9,即約45°,可以想象為繞一個xy平面上的對角線進行45°的旋轉(zhuǎn)。在Unity中這剛好與Quaternion.AngleAxis(45, Vector3.right + Vector3.up)對應(yīng)。
規(guī)則3&4:四元數(shù)乘法&取逆
下面來看Patrick用四元數(shù)解決的實際問題。他曾參與《大富翁》(Monopoly)游戲iPad版本開發(fā),重寫了相機系統(tǒng)使得相機可以從四個玩家的角度進行拍攝。
在桌游中,相機視角通常都是從上方俯視全局(視角1),但我們希望可以讓相機可以沿水平方向繞著移動中的角色拍攝(視角2)。
從《大富翁》游戲的宣傳視頻中我們可以看到這樣視角變換的動畫。
Patrick在開發(fā)中用以下代碼表示相機在各個位置時的rotation:
如何讓相機降到另一條側(cè)邊上?把這個旋轉(zhuǎn)拆成兩步,第一步向右旋轉(zhuǎn)90°,第二步從空中降下來。
規(guī)則3:做乘法其實就是在應(yīng)用旋轉(zhuǎn)。
疊加兩個步驟就是從右向左將旋轉(zhuǎn)四元數(shù)相乘(注意順序不可交換,向量/矩陣乘法沒有交換性)。通過簡單的實驗我們也可以驗證順序的重要性:先pitch再roll和先roll再pitch的結(jié)果并不相同。
在Unity中要注意避免*=的寫法,這樣乘上的操作會被依次放在右邊,先寫的反而后執(zhí)行,與思路不匹配。(關(guān)于Unity中四元數(shù)旋轉(zhuǎn)執(zhí)行順序,詳見官方文檔中Quaternion的operator*的部分)。
規(guī)則4:逆操作(inverse/conjugate)就是虛數(shù)部分取反。
由乘法自然拓展到取逆,在數(shù)學(xué)上對四元數(shù)取逆比較復(fù)雜,但是在游戲中可以偷懶,回憶一下規(guī)則1,四元數(shù)的虛數(shù)部分就是旋轉(zhuǎn)軸。直接對虛數(shù)部分全部取反,即可得到圍繞相反旋轉(zhuǎn)軸進行的等量旋轉(zhuǎn),即得到逆操作四元數(shù)。
規(guī)則5:四元數(shù)插值
要制作旋轉(zhuǎn)的動畫,我們往往會使用到插值?;貞?strong>規(guī)則5:與對向量做LERP(線性插值)相似,對四元數(shù)做SLERP。你也可以對四元數(shù)做線性插值,類似于(1 – t)·q0 + t·q1,但是這樣會存在問題:大部分情況下,生成的四元數(shù)未經(jīng)歸一化(normalize),在旋轉(zhuǎn)時就會同時存在對物體的縮放,而這是我們不想看到的。
為了解決這一問題,我們對四元數(shù)做SLERP。這一公式比較復(fù)雜,不過重點就是可以使四元數(shù)的長度始終保持為1。具體的實現(xiàn)通常會由數(shù)學(xué)庫幫忙完成。
規(guī)則6:坐標(biāo)系轉(zhuǎn)換
三維笛卡爾坐標(biāo)系分為左手坐標(biāo)系和右手坐標(biāo)系,許多進行物理計算的設(shè)備和軟件傾向于使用右手系,而Unity等游戲開發(fā)軟件使用左手系。當(dāng)我們需要在兩者之間轉(zhuǎn)換一個坐標(biāo)(x, y,z)時,對xyz其中一者(通常是z)取反即可。在兩坐標(biāo)系之間轉(zhuǎn)換四元數(shù)也是這樣。
規(guī)則6:從右手系轉(zhuǎn)換到左手系,對虛部i,j,k中的一個取反。通常取(s, i, j, -k)即可。
規(guī)則7:相信計算機
雖然四元數(shù)背后的數(shù)學(xué)十分復(fù)雜,但大部分情況下數(shù)學(xué)庫已經(jīng)幫你完成了這些計算。規(guī)則7:相信你用的數(shù)學(xué)庫,理解抽象概念,然后直接使用四元數(shù)即可。
總結(jié)
四元數(shù)是游戲開發(fā)非常有用的三維旋轉(zhuǎn)工具,盡管存在看起來不夠直觀的缺點,但可以避免萬向鎖問題,從而實現(xiàn)理想的旋轉(zhuǎn)效果。希望通過分享這7條規(guī)則,能幫助你對四元數(shù)有一個直觀的理解,在開發(fā)中自由地應(yīng)用四元數(shù)吧!