四元數和歐拉角

來源:http://blog.csdn.net/candycat1992/article/details/41254799html

四元數介紹

 

旋轉,應該是三種座標變換——縮放、旋轉和平移,中最複雜的一種了。你們應該都聽過,有一種旋轉的表示方法叫四元數。按照咱們的習慣,咱們更加熟悉的是另外兩種旋轉的表示方法——矩陣旋轉和歐拉旋轉。矩陣旋轉使用了一個4*4大小的矩陣來表示繞任意軸旋轉的變換矩陣,而歐拉選擇則是按照必定的座標軸順序(例如先x、再y、最後z)、每一個軸旋轉必定角度來變換座標或向量,它其實是一系列座標軸旋轉的組合。ide

 

那麼,四元數又是什麼呢?簡單來講,四元數本質上是一種高階複數(聽不懂了吧。。。),是一個四維空間,相對於複數的二維空間。咱們高中的時候應該都學過複數,一個複數由實部和虛部組成,即x = a + bi,i是虛數單位,若是你還記得的話應該知道i^2 = -1。而四元數其實和咱們學到的這種是相似的,不一樣的是,它的虛部包含了三個虛數單位,i、j、k,即一個四元數能夠表示爲x = a + bi + cj + dk。那麼,它和旋轉爲何會有關係呢?函數

 

在Unity裏,tranform組件有一個變量名爲rotation,它的類型就是四元數。不少初學者會直接取rotation的x、y、z,認爲它們分別對應了Transform面板裏R的各個份量。固然很快咱們就會發現這是徹底不對的。實際上,四元數的x、y、z和R的那三個值從直觀上來說沒什麼關係,固然會存在一個表達式能夠轉換,在後面會講。atom

 

你們應該和我同樣都有不少疑問,既然已經存在了這兩種旋轉表示方式,爲何還要使用四元數這種聽起來很難懂的東西呢?咱們先要了解這三種旋轉方式的優缺點:idea

 

  • 矩陣旋轉
    • 優勢:
      • 旋轉軸能夠是任意向量;
    • 缺點:
      • 旋轉其實只須要知道一個向量+一個角度,一共4個值的信息,但矩陣法卻使用了16個元素;
      • 並且在作乘法操做時也會增長計算量,形成了空間和時間上的一些浪費;

  • 歐拉旋轉
    • 優勢:
      • 很容易理解,形象直觀;
      • 表示更方便,只須要3個值(分別對應x、y、z軸的旋轉角度);但按個人理解,它仍是轉換到了3個3*3的矩陣作變換,效率不如四元數;
    • 缺點:
      • 以前提到過這種方法是要按照一個固定的座標軸的順序旋轉的,所以不一樣的順序會形成不一樣的結果;
      • 會形成萬向節鎖(Gimbal Lock)的現象。這種現象的發生就是因爲上述固定座標軸旋轉順序形成的。理論上,歐拉旋轉能夠靠這種順序讓一個物體指到任何一個想要的方向,但若是在旋轉中不幸讓某些座標軸重合了就會發生萬向節鎖,這時就會丟失一個方向上的旋轉能力,也就是說在這種狀態下咱們不管怎麼旋轉(固然仍是要原先的順序)都不可能獲得某些想要的旋轉效果,除非咱們打破原先的旋轉順序或者同時旋轉3個座標軸。這裏有個視頻能夠直觀的理解下;
      • 因爲萬向節鎖的存在,歐拉旋轉沒法實現球面平滑插值;

  • 四元數旋轉
    • 優勢:
      • 能夠避免萬向節鎖現象;
      • 只須要一個4維的四元數就能夠執行繞任意過原點的向量的旋轉,方便快捷,在某些實現下比旋轉矩陣效率更高;
      • 能夠提供平滑插值;
    • 缺點:
      • 比歐拉旋轉稍微複雜了一點點,由於多了一個維度;
      • 理解更困難,不直觀;
 
 

四元數和歐拉角

 
 

基礎知識

 
 
前面說過,一個四元數能夠表示爲q = w + xi + yj + zk,如今就來回答這樣一個簡單的式子是怎麼和三維旋轉結合在一塊兒的。爲了方便,咱們下面使用q = ((x, y, z),w) = (v, w),其中v是向量,w是實數,這樣的式子來表示一個四元數。
 
咱們先來看問題的答案。咱們可使用一個四元數 q=((x,y,z)sinθ2cosθ2) 來執行一個旋轉。具體來講,若是咱們想要把空間的一個點P繞着單位向量軸u = (x, y, z)表示的旋轉軸旋轉θ角度,咱們首先把點P擴展到四元數空間,即四元數p = (P, 0)。那麼,旋轉後新的點對應的四元數(固然這個計算而得的四元數的實部爲0,虛部係數就是新的座標)爲:

p=qpq1
 
其中, q=(cosθ2, (x,y,z)sinθ2) ,q1=qN(q),因爲u是單位向量,所以
N(q)=1,即q1=q∗。右邊表達式包含了四元數乘法。相關的定義以下:
  • 四元數乘法:q1q2=(v1×v2+w1v2+w2v1,w1w2v1v2)
     
  • 共軛四元數:q=(v⃗ ,w)

  • 四元數的模:N(q) = √(x^2 + y^2 + z^2 +w^2),即四元數到原點的距離

  • 四元數的逆:q1=qN(q)

 
它的證實這裏再也不贅述,有興趣的能夠參見 這篇文章。主要思想是構建了一個輔助向量k,它是將p繞旋轉軸旋轉θ/2獲得的。證實過程嘗試證實 wk=kv∗,以此證實w與v、k在同一平面內,且與v夾角爲θ。
 
咱們舉個最簡單的例子:把點P(1, 0, 1)繞旋轉軸u = (0, 1, 0)旋轉90°,求旋轉後的頂點座標。首先將P擴充到四元數,即p = (P, 0)。而q = (u*sin45°, cos45°)。求 p=qpq1的值。建議你們必定要在紙上計算一邊,這樣才能加深印象,連筆都懶得動的人仍是不要往下看了。最後的結果p` = ((1, 0, -1), 0),即旋轉後的頂點位置是(1, 0, -1)。
 
若是想要獲得複合旋轉,只需相似複合矩陣那樣左乘新的四元數,再進行運算便可。
 
咱們來總結下四元數旋轉的 幾個須要注意的地方
 
  • 用於旋轉的四元數,每一個份量的範圍都在(-1,1);

  • 每一次旋轉實際上須要兩個四元數的參與,即q和q*;

  • 全部用於旋轉的四元數都是單位四元數,即它們的模是1;
 
 
下面是幾點建議:
 
  • 實際上,在Unity裏即使你不知道上述公式和變換也絲絕不妨礙咱們使用四元數,可是有一點要提醒你,除非你對四元數很是瞭解,那麼不要直接對它們進行賦值

  • 若是你不想知道原理,只想在Unity裏找到對應的函數來進行四元數變換,那麼你可使用這兩個函數:Quaternion.EulerQuaternion.eulerAngles。它們基本能夠知足絕大多數的四元數旋轉變換。
 
 

和其餘類型的轉換

 
首先是 軸角到四元數
 
給定一個單位長度的旋轉軸(x, y, z)和一個角度θ。對應的四元數爲:
q=((x,y,z)sinθ2cosθ2
 
 
這個公式的推導過程上面已經給出。
 
歐拉角到四元數
 
給定一個歐拉旋轉(X, Y, Z)(即分別繞x軸、y軸和z軸旋轉X、Y、Z度),則對應的四元數爲:
 
x = sin(Y/2)sin(Z/2)cos(X/2)+cos(Y/2)cos(Z/2)sin(X/2)
y = sin(Y/2)cos(Z/2)cos(X/2)+cos(Y/2)sin(Z/2)sin(X/2)
z = cos(Y/2)sin(Z/2)cos(X/2)-sin(Y/2)cos(Z/2)sin(X/2)
w = cos(Y/2)cos(Z/2)cos(X/2)-sin(Y/2)sin(Z/2)sin(X/2)
q = ((x, y, z), w)
 
它的證實過程能夠依靠軸角到四元數的公式進行推導。
 
其餘 參考連接
 
 
 

四元數的插值

 
這裏的插值指的是球面線性插值。
 
設t是一個在0到1之間的變量。咱們想要基於t求Q1到Q2之間插值後四元數Q。它的公式是:

Q3  = (sin((1-t)A)/sin(A))*Q1 + (sin((tA)/sin(A))*Q2)
Q = Q3/|Q3|,即單位化
 
 
 

四元數的建立

 
在瞭解了上述知識後,咱們就不須要那麼害怕四元數了,實際上它和矩陣相似,不一樣的只是它的表示方式以及運算方式。那麼在Unity裏如何利用四元數進行旋轉呢?Unity裏提供了很是多的方式來建立一個四元數。例如Quaternion.AngleAxis(float angle, Vector3 axis),它能夠返回一個繞軸線axis旋轉angle角度的四元數變換。咱們能夠一個Vector3和它進行左乘,就將獲得旋轉後的Vector3。在Unity裏只須要用一個「 * 」操做符就能夠進行四元數對向量的變換操做,至關於咱們上述講到的 p=qpq1操做。若是咱們想要進行多個旋轉變換,只須要左乘其餘四元數變換便可。例以下面這樣:
[csharp]  view plain  copy
 
 print?在CODE上查看代碼片派生到個人代碼片
  1. Vector3 newVector = Quaternion.AngleAxis(90, Vector3.up) * Quaternion.LookRotation(someDirection) * someVector;  
儘管歐拉角更容易咱們理解,但四元數比歐拉角要強大不少。Unity提供了這兩種方式供咱們選擇,咱們能夠選擇最合適的變換。
例如,若是咱們須要對旋轉進行插值,咱們能夠首先使用Quaternion.eulerAngles來獲得歐拉角度,而後使用Mathf.Clamp對其進行插值運算。
最後更新Quaternion.eulerAngles或者使用Quaternion.Euler(yourAngles)來建立一個新的四元數。
 
又例如,若是你想要組合旋轉,好比讓人物的腦殼向下看或者旋轉身體,兩種方法其實均可以,但一旦這些旋轉不是以世界座標軸爲旋轉軸,好比人物扭動脖子向下看等,那麼四元數是一個更合適的選擇。Unity還提供了transform.forward, transform.right and transform.up 這些很是有用的軸,這些軸能夠和Quaternion.AngleAxis組合起來,來建立很是有用的旋轉組合。例如,下面的代碼讓物體執行低頭的動做:
[csharp]  view plain  copy
 
 print?在CODE上查看代碼片派生到個人代碼片
  1. transform.rotation = Quaternion.AngleAxis(degrees, transform.right) * transform.rotation;  


關於Quaternion的其餘函數,後面再補充吧,原理相似~
 
 
 

補充:歐拉旋轉

 
在文章開頭關於歐拉旋轉的細節沒有解釋的太清楚,而又有很多人詢問相關問題,我儘可能把本身的理解寫到這裏,若有不對還望指出。
 
 

歐拉旋轉是怎麼運做的

 
 
歐拉旋轉是咱們最容易理解的一種旋轉方式。以咱們生活中爲例,一個舞蹈老師告訴咱們,完成某個舞蹈動做須要先向你的左邊轉30°,再向左側彎腰60°,再起身向後彎腰90°(若是你能辦到的話)。上面這樣一個旋轉的過程其實和咱們在三維中進行歐拉旋轉很相似,即咱們是經過指明繞三個軸旋轉的角度來進行旋轉的,不一樣的是,平常生活中咱們更願意叫這些軸爲先後左右上下。而這也意味着咱們須要指明一個旋轉順序。這是由於,先繞X軸旋轉90°、再繞Y軸30°和先繞Y軸旋轉90°、再繞X軸30°獲得的是不一樣的結果。
 
在Unity裏,歐拉旋轉的旋轉順序是Z、X、Y,這在相關的API文檔中都有說明,例如 Transform.Rotate。其實文檔中說得不是很是詳細,還有一個細節咱們須要明白。若是你仔細想一想,就會發現有一個很是重要的東西咱們沒有說明白,那就是旋轉時使用的座標系。給定一個旋轉順序(例如這裏的Z、X、Y),以及它們對應的旋轉角度(α,β,r),有兩種座標系能夠選擇:
  1. 繞座標系E下的Z軸旋轉α,繞座標系E下的Y軸旋轉β,繞座標系E下的X軸旋轉r,即進行一次旋轉時不一塊兒旋轉當前座標系;
  2. 繞座標系E下的Z軸旋轉α,繞座標系E在繞Z軸旋轉α後的新座標系E'下的Y軸旋轉β,繞座標系E'在繞Y軸旋轉β後的新座標系E''下的X軸旋轉r, 即在旋轉時,把座標系一塊兒轉動;
 
很容易知道,這兩種選擇的結果是不同的。但若是把它們的旋轉順序顛倒一下,其實結果就會同樣。說得明白點,在第一種狀況下、按ZXY順序旋轉和在第二種狀況下、按YXZ順序旋轉是同樣的。證實方法能夠看下 這篇文章。而Unity文檔中說明的旋轉順序指的是在第一種狀況下的順序。
 
若是你仍是不懂這意味着什麼,能夠試着調用下這個函數。例如,你認爲下面代碼的結果是什麼:
[csharp]  view plain  copy
 
 print?在CODE上查看代碼片派生到個人代碼片
  1. transform.Rotate(new Vector3(0, 30, 90));  

原模型的方向和執行結果以下:
 
 
 
 
而咱們能夠再分別執行下面的代碼:
[csharp]  view plain  copy
 
 print?在CODE上查看代碼片派生到個人代碼片
  1.         // First case  
  2.         transform.Rotate(new Vector3(0, 30, 0));  
  3.         transform.Rotate(new Vector3(0, 0, 90));  
  4.   
  5.         // Second case  
  6. //      transform.Rotate(new Vector3(0, 0, 90));  
  7. //      transform.Rotate(new Vector3(0, 30, 0));  

兩種狀況的結果分別是:
 
 
能夠發現,調用transform.Rotate(new Vector3(0, 30, 90));是和第一種狀況中的代碼是同樣的結果,即先旋轉Y、再旋轉Z。進一步實驗,咱們會發現transform.Rotate(new Vector3(30, 90, -40));的結果是和transform.Rotate(new Vector3(0, 90, 0));transform.Rotate(new Vector3(30, 0, 0));transform.Rotate(new Vector3(0, 0, -40));的結果同樣的。你會問了,文檔中不是明明說了旋轉順序是Z、X、Y嗎?怎麼如今徹底反過來了呢?緣由就是咱們以前說的兩種座標系的選擇。在一次調用transform.Rotate的過程當中,座標軸是不隨每次單個座標軸的旋轉而旋轉的。而在調用transform.Rotate後,這個旋轉座標系纔會變化。也就是說,transform.Rotate(new Vector3(30, 90, -40));執行時使用的是第一種狀況,而transform.Rotate(new Vector3(0, 90, 0));transform.Rotate(new Vector3(30, 0, 0));transform.Rotate(new Vector3(0, 0, -40));每一句則是分別使用了上一句執行後的座標系,即第二種座標系狀況。所以,咱們看起來順序好像是徹底是反了,但結果是同樣的。
 
上面只是說了一些容易混淆的地方,更多的內容你們能夠搜搜wiki之類的。
 
 

數學模型

歐拉旋轉的數學實現就是使用矩陣。而最多見的表示方法就是3*3的矩陣。在 Wiki裏咱們能夠找到這種矩陣的表示形式,如下以按XYZ的旋轉順序爲例,三個矩陣分別表示了:
 
 
 
在計算時,咱們將原來的旋轉矩陣右乘(這裏使用的是列向量)上面的矩陣。從這裏咱們也能夠證實上面所說的兩種座標系選擇是同樣的結果,它們之間的不一樣從這裏來看其實就是矩陣相乘時的順序不一樣。第一種座標系狀況,指的是在計算時,先從左到右直接計算R中3個矩陣的結果矩陣,最後再和原旋轉矩陣相乘,所以順序是XYZ;而第二種座標系狀況,指的是在計算時,從右往左依次相乘,所以順序是反過來的,ZYX。你能夠驗證R左乘和右乘的結果表達式,就能夠相信這個結論了!
 
 

萬向節鎖

 
 
雖然歐拉旋轉很是容易理解,但它會形成臭名昭著的萬向節鎖問題。我以前給出了連接你們可能都看了,但仍是不明白這是怎麼回事。這裏 有一篇文章是我目前找到說得最容易懂的中文文章,你們能夠看看。
 
若是你仍是不明白,咱們來作個試驗。仍是使用以前的模型,此次咱們直接在面板中把它的歐拉角中的X值設爲90°,其餘先保持不變:
 
此時模型是臉朝下(下圖你看到的只是一個頭頂):
 
如今,若是我讓你不動X軸,只設置Y和Z的值,把這個模型的臉轉上來,讓它向側面看,你能夠辦到嗎?你能夠發現,這時候不管你怎麼設置Y和Z的值,模型始終是臉朝下、在同一平面旋轉,看起來就是Y和Z控制的是同一個軸的旋轉,下面是我截取的任意兩種狀況:
 
 
這就是一種萬向節鎖的狀況。這裏咱們先設置X軸爲90°也是有緣由的,這是由於Unity中歐拉角的旋轉順序是ZXY,即X軸是第二個旋轉軸。當咱們在面板中設置任意旋轉值時,Unity實際是按照固定的ZXY順序依次旋轉特定角度的。
 
在代碼裏,咱們一樣能夠重現萬向節鎖現象。
[csharp]  view plain  copy
 
 print?在CODE上查看代碼片派生到個人代碼片
  1. transform.Rotate(new Vector3(0, 0, 40));  
  2. transform.Rotate(new Vector3(0, 90, 0));  
  3. transform.Rotate(new Vector3(80, 0, 0));  

咱們只須要固定中間一句代碼,即便Y軸的旋轉角度始終爲90°,那麼你會發現不管你怎麼調整第一句和最後一句中的X或Z值,它會像一個鐘錶的錶針同樣老是在同一個平面上運動。
 
萬向節鎖中的「鎖」,實際上是給人一種誤導,這可能也是讓不少人以爲難以理解的一個緣由。實際上,實際上它並無鎖住任何一個旋轉軸,只是說咱們會在這種旋轉狀況下會感受喪失了一個維度。以上面的例子來講,儘管固定了第二個旋轉軸的角度爲90°,但咱們原覺得依靠改變其餘兩個軸的旋轉角度是能夠獲得任意旋轉位置的(由於按咱們理解,兩個軸應該控制的是兩個空間維度),而事實是它被「鎖」在了一個平面,即只有一個維度了,缺失了一個維度。而只要第二個旋轉軸不是±90°,咱們就能夠依靠改變其餘兩個軸的旋轉角度來獲得任意旋轉位置。
 
 

數學解釋

 
咱們從最簡單的矩陣來理解。仍是使用XYZ的旋轉順序。當Y軸的旋轉角度爲90°時,咱們會獲得下面的旋轉矩陣:
 
 
咱們對上述矩陣進行左乘能夠獲得下面的結果:
 
能夠發現,此時當咱們改變第一次和第三次的旋轉角度時,是一樣的效果,而不會改變第一行和第三列的任何數值,從而缺失了一個維度。
 
咱們再嘗試着理解下它的本質。 Wiki上寫,萬向節鎖出現的本質緣由,是由於從歐拉角到旋轉的映射並非一個覆蓋映射,即它並非在每一個點處都是局部同胚的。不懂吧。。。恩,咱們再來通俗一下解釋,這意味着,從歐拉角到旋轉是一個多對一的映射(即不一樣的歐拉角能夠表示同一個旋轉方向),並且並非每個旋轉變化均可以用歐拉角來表示。其餘更多的你們去參考wiki吧。
 
 
建議仍是多看看視頻,尤爲是後面的部分。固然,若是仍是以爲懵懵懂懂的話,在《3D數學基礎:圖形與遊戲開發》一書中有一話說的頗有道理,「若是您歷來沒有遇到過萬向鎖狀況,你可能會對此感到困惑,並且不幸的是,很難在本書中講清楚這個問題,你須要親身經歷才能明白。」所以,你們也不要糾結啦,等到遇到的時候能夠想到是由於萬向節鎖的緣由就好。
相關文章
相關標籤/搜索