本文釋出於遊戲程式設計師劉宇的個人部落格,長期更新,轉載請註明源地址https://www.cnblogs.com/xiaohutu/p/10979936.html
數學,是人類對客觀世界中數量關係和空間形式本質特徵進行研究的科學。對同樣的某一特徵或者關係,可以根據需求用不同的數學符號、定義和過程來表達。在遊戲引擎中,我們也有很多這樣的例子,比如本文說到的旋轉。
尤拉角
旋轉是一個過程,一個物體圍繞周或者點角度變化的過程。為了描述這個過程我們必須有參照物,於是我們先定義一個世界座標系,笛卡爾座標系。
尤拉角用(x, y, z) 分別來表示這個物體相對三個座標系的夾角,這是由數學家尤拉首先提出而得名的。
然而僅僅有(x, y, z) 來表示旋轉是不夠的,還有兩個因素:
首先是旋轉順序,從各個軸上進行角度旋轉時xyz先後的不同會得到不同的結果。我們稱這個順序定義為順規,下面一段是維基百科的定義:
在經典力學裡,時常用zxz順規來設定尤拉角;照著第二個轉動軸的軸名,簡稱為x順規。另外,還有別的尤拉角組。合法的尤拉角組中,唯一的限制是,任何兩個連續的旋轉,必須繞著不同的轉動軸旋轉。因此,一共有12種順規。例如,y順規,第二個轉動軸是y-軸,時常用在量子力學、核子物理學、粒子物理學。另外,還有一種順規,xyz順規,是用在航空航天工程學;
按(z-x-z, x-y-x, y-z-y, z-y-z, x-z-x, y-x-y)軸序列旋轉,即第一個旋轉軸和最後一個旋轉軸相同,我們稱之為經典尤拉角(Proper Euler Angle)。
按(x-y-z, y-z-x, z-x-y, x-z-y, z-y-x, y-x-z)軸序列旋轉,即三個不同的軸,我們稱之為泰特布萊恩角(Tait–Bryan angles)。
其次是旋轉的參照座標系,尤拉角按旋轉的座標系分為:
內旋(intrinsic rotation)即按照物體本身的座標系進行旋轉,座標系會跟隨旋轉與世界座標系產生偏移。
外旋(extrinsic rotation)即根據世界座標系進行旋轉。
這是我們看看Unity3d中Transform的Rotate,最後一個引數即座標系:
public void Rotate(Vector3 eulerAngles, Space relativeTo = Space.Self);
注意:Unity3d使用的是zxy的順規,且進行一次尤拉旋轉的zxy依次執行過程中,相對軸始終是運算開始之前的軸向。
萬向節死鎖(Gimbal Lock)
注意我們前面提到的旋轉的規則 [ 進行一次尤拉旋轉的zxy依次執行過程中,相對軸始終是運算開始之前的軸向 ]
在這個規則下,zxy順規時,如果x的旋轉為90度,那麼任意的(z,90,y)都與(z-y,90,0)得到的結果都是相同的,此時我們稱z軸失去了自由度,並稱這種情況為萬向節死鎖。之所以叫Gimbal Lock,是因為這種情況正好和Gimbal,即陀螺儀的情況是完美匹配的,死鎖情況也相同。具體情況這篇文章寫的很好,大家可以看一下:https://blog.csdn.net/andrewfan/article/details/60981437
這種情況下,物體的某一個旋轉,會有多個尤拉角數值與其多對一的對應,那麼對尤拉角進行插值是沒有意義的。如果只旋轉一個軸,其他軸不動,那麼直接設定尤拉角的數值倒是沒有什麼問題。
同時我們也不難得出死鎖的原因並不是由順規決定的,而是由於尤拉角的原理和計算方式,一個軸的旋轉必然帶來另外軸的旋轉所導致的。
軸向角
這時候,又有了一個思路,我們可以使用基於一個單位向量來表示方向,再用一個標量來定義繞這個向量來旋轉多少角度theta。
即[x,y,z,theta],前面的xyz表示這個向量,最後一個表示角度。這個表示方法很簡潔明瞭,容易理解。
缺點有兩個:
1.不同的軸角之間不能進行簡單的插值。
2.不好基於一個向量加上一個軸角形式的旋轉來進行運算。
為了解決這些缺點,先人們又發明了使用四元數和矩陣來表達旋轉。
四元數
這裡先說一下基本原理:
參考連結:https://www.3dgep.com/understanding-quaternions/
譯文:https://blog.csdn.net/lhs322/article/details/80066960
四元數的概念是由愛爾蘭數學家漢密爾頓發明的,他當時正和老婆一起前往愛爾蘭皇家研究院,一邊走一邊想,路過一座橋時,他頓悟了公式,並立刻把它刻在橋上的石頭上:
那麼,為什麼四元數能表示三維空間的旋轉呢?首先學過高數我們都知道複數的定義以及幾何意義,複數可以對映到複數平面上,並且對這複數乘以i,得到的複數就相當於複數空間裡旋轉了90度。
例如下圖,p = 2 + i,乘以i後: q = pi = (2+i)*i = 2i + i*i = 2i - 1 = -1 + 2i。可以看出q逆時針旋轉了90度。同理乘以-i即為正時針旋轉90度。
此時將複數的虛部擴充套件為三個,並根據漢密爾頓的著名錶達式以及推論
四元數的定義可以用來表達笛卡爾座標系的旋轉,其中i,j,k分別代表笛卡爾座標系裡xyz三個軸的單位向量。這些表示式裡 ij = k 是不是很眼熟?兩個互相垂直的單位向量的叉乘等於垂直於兩個向量的單位向量。
經過一系列的推導和運算(略),大家感興趣可以看上面的連結,假設一個旋轉的基準向量是(A,B,C),角度是 θ (theta),那麼表達這個旋轉過程的四元數如下:
注意ABC本質上就是基準向量在3個笛卡爾軸上的分量,用來準確描述向量,上面的公式也就是
四元數 q = [cos(θ/2), sin(θ/2)* v‘];
即:
w = cos(θ/2) x = A * sin(θ/2) y = B * sin(θ/2) z = C * sin(θ/2)
假設有一個向量v要進行旋轉,這個旋轉描述為q,那麼結果是 v' = qvq-1, 如果要進行多次旋轉,則表示為:
四元數的乘法是:
q1 * q2 =
(w1*w2 - x1*x2 - y1*y2 - z1*z2) +
(w1*x2 + x1*w2 + y1*z2 - z1*y2) i +
(w1*y2 - x1*z2 + y1*w2 + z1*x2) j +
(w1*z2 + x1*y2 - y1*x2 + z1*w2) k
可以看到,通過一系列的數學推導和定義,可以只用4個浮點數就來表達一個旋轉過程,並且可以方便簡單的快速計算旋轉的疊加。這對於遊戲引擎來說是非常有意義的,可以加快運算速度。
四元數還有很多具體的特性,計算規則等,感興趣的可以去研究,本文主要討論旋轉,這裡不再贅訴。
【球形插值】
四元數還可以實現球形插值,制定兩個旋轉qa到qb,時間間隔為t,那麼此刻的旋轉插值為:
其中θ為兩個旋轉之間的夾角:
球面插值可以在遊戲裡實現很平滑的轉向和球面運動。
四元數與尤拉角之間的轉換
已知尤拉角,求四元數:
已知四元素,求尤拉角
用矩陣來計算旋轉
學過矩陣乘法我們都知道,如果把向量看成一個列矩陣,那麼與向量維度相同的列數的矩陣乘以它,得到的結果也是一個列矩陣,即:
所以可以充分利用左邊矩陣的內容,對右邊的向量進行各種變換(包括平移,縮放,旋轉等等),這裡我們只討論旋轉。
具體推導過程參考這裡:https://www.cnblogs.com/xpvincent/archive/2013/02/15/2912836.html
假設一個向量V(Vx,Vy, Vz) 繞另外一個軸角(nx, ny, nz, θ)進行旋轉,那麼旋轉結果V'是:
這個公式我們稱之為羅德里格旋轉公式(Rodrigues' rotation formula),用矩陣計算旋轉在遊戲圖形渲染裡非常普遍,不過引擎裡一般為了相容更多的向量變換,都使用了其次矩陣,即多一個維度的矩陣,本文不表。
小結
總結完這幾種在常見的表達旋轉的數學方式,可以看到遊戲引擎裡也都使用到這些表達方法,感覺收益匪淺,後面準備討論一下齊次矩陣和矩陣變換的原理。