1. 程式人生 > >unity--------------------四元數的旋轉與原理

unity--------------------四元數的旋轉與原理

變換 平移 旋轉角度 eve 歐拉角 repl 學習筆記 detail 關卡

【Unity技巧】四元數(Quaternion)和旋轉

四元數介紹

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

那麽,四元數又是什麽呢?簡單來說,四元數本質上是一種高階復數(聽不懂了吧。。。),是一個四維空間,相對於復數的二維空間。我們高中的時候應該都學過復數,一個復數由實部和虛部組成,即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的那三個值從直觀上來講沒什麽關系,當然會存在一個表達式可以轉換,在後面會講。

大家應該和我一樣都有很多疑問,既然已經存在了這兩種旋轉表示方式,為什麽還要使用四元數這種聽起來很難懂的東西呢?我們先要了解這三種旋轉方式的優缺點:

  • 矩陣旋轉
    • 優點:
      • 旋轉軸可以是任意向量;
    • 缺點:
      • 旋轉其實只需要知道一個向量+一個角度,一共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θ2, cosθ2) 來執行一個旋轉。具體來說,如果我們想要把空間的一個點P繞著單位向量軸u = (x, y, z)表示的旋轉軸旋轉θ角度,我們首先把點P擴展到四元數空間,即四元數p = (P, 0)。那麽,旋轉後新的點對應的四元數(當然這個計算而得的四元數的實部為0,虛部系數就是新的坐標)為:

p=qpq?1
其中,q=(cosθ2, (x,y,z)sinθ2) ,q?1=q?N(q),由於u是單位向量,因此
N(q)=1,即q?1=q?。右邊表達式包含了四元數乘法。相關的定義如下:
  • 四元數乘法:q1q2=(v1×v2+w1v2+w2v1,w1w2?v1?v2)
  • 共軛四元數:q?=(?v? ,w)

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

  • 四元數的逆:q?1=q?N(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=qpq?1的值。建議大家一定要在紙上計算一邊,這樣才能加深印象,連筆都懶得動的人還是不要往下看了。最後的結果p` = ((1, 0, -1), 0),即旋轉後的頂點位置是(1, 0, -1)。 如果想要得到復合旋轉,只需類似復合矩陣那樣左乘新的四元數,再進行運算即可。 我們來總結下四元數旋轉的幾個需要註意的地方
  • 用於旋轉的四元數,每個分量的範圍都在(-1,1);

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

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

  • 如果你不想知道原理,只想在Unity裏找到對應的函數來進行四元數變換,那麽你可以使用這兩個函數:Quaternion.Euler和Quaternion.eulerAngles。它們基本可以滿足絕大多數的四元數旋轉變換。

和其他類型的轉換

首先是軸角到四元數: 給定一個單位長度的旋轉軸(x, y, z)和一個角度θ。對應的四元數為: q=((x,y,z)sinθ2, cosθ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) 它的證明過程可以依靠軸角到四元數的公式進行推導。 其他參考鏈接: 1. Euler to Quaternion
2. Quaternion To Euler
3. AngleAxis to Quaternion
4. Quaternion to AngleAxis

四元數的插值

這裏的插值指的是球面線性插值。 設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=qpq?1操作。如果我們想要進行多個旋轉變換,只需要左乘其他四元數變換即可。例如下面這樣: [csharp] view plain copy print?
  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?
  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?
  1. transform.Rotate(new Vector3(0, 30, 90));

原模型的方向和執行結果如下: 技術分享 技術分享 而我們可以再分別執行下面的代碼: [csharp] view plain copy print?
  1. // First case
  2. transform.Rotate(new Vector3(0, 30, 0));
  3. transform.Rotate(new Vector3(0, 0, 90));
  4. // Second case
  5. // transform.Rotate(new Vector3(0, 0, 90));
  6. // 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?
  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數學基礎:圖形與遊戲開發》一書中有一話說的很有道理,“如果您從來沒有遇到過萬向鎖情況,你可能會對此感到困惑,而且不幸的是,很難在本書中講清楚這個問題,你需要親身經歷才能明白。”因此,大家也不要糾結啦,等到遇到的時候可以想到是因為萬向節鎖的原因就好。

71
7

相關文章推薦
  • ? Unity3D中的Quaternion(四元數)
  • ? Presto的服務治理與架構在京東的實踐與應用--王哲涵
  • ? 四元數—Quaternion
  • ? 深入掌握Kubernetes應用實踐--王淵命
  • ? unity入門教程之使用四元數旋轉向量
  • ? Python基礎知識匯總
  • ? unity 關於Rotation和Quaternion的一些問題(歐拉角與四元數,lerp與slerp)
  • ? Android核心技術詳解
  • ? Unity3d之Quaternion 學習與應用
  • ? Retrofit 從入門封裝到源碼解析
  • ? Unity手遊之路<四>3d旋轉-四元數,歐拉角和變幻矩陣
  • ? 自然語言處理工具Word2Vec
  • ? Unity3D中四元數
  • ? Unity學習筆記10——旋轉(四元數和歐拉角)
  • ? 來泰課學習第一天
  • ? Unity3D遊戲開發之快速打造流行的關卡系統
查看評論
41樓 qq7140777762017-08-23 20:30發表 [回復]
技術分享
厲害,我是個渣渣,學習學習,哎
40樓 qq_318077112017-08-12 14:55發表 [回復]
技術分享
歐拉角到四元數的轉換 sin 和cos不是正弦和余弦嗎 那是求角度的正余弦值 你上面用sinY/2 是啥意思呀?X Y Z是代表軸向 你要正弦一個軸向?
39樓 Cozak2017-06-10 11:00發表 [回復]
技術分享
博主您好,您在萬向節鎖一節開頭給出的文章中有一句話“靜態歐拉角不存在萬向節鎖問題”,對此本人有以下疑問

根據那篇文章中對“靜態歐拉角”的定義:“即繞世界坐標系三個軸的旋轉,由於物體旋轉過程中坐標軸保持靜止,所以稱為靜態“

如果設x-y-z是參考系坐標(不隨模型轉動)而X-Y-Z是模型坐標系(跟隨模型轉動),模型頂點坐標始終在參考系坐標系下,那麽靜態歐拉角就是定義在x-y-z上的旋轉
按照xyz順規,旋轉矩陣為R=R_x(α)R_y(β)R_z(γ),此時如果β=±90°,那麽α和γ取任何值模型也只是在同一個水平面轉動,即出現萬向節鎖
按照zxz順規,旋轉矩陣為R=R_z(α)R_x(β)R_z(γ),此時如果β=0°,那麽同樣會出現萬向節鎖

而英文Wiki中關於“靜態歐拉角”(根據定義描述)是寫作“extrinsic rotations”,然而並沒有說不存在萬向節鎖問題
翻過去Google也沒有找到任何相關信息(除了沒有任何來源說明的中文Wiki)

希望學識淵博的博主能指一下方向,O(∩_∩)O謝謝
38樓 avi91112017-05-08 00:10發表 [回復]
技術分享
想知道4元數的乘法做什麽用的?
和矢量乘法作用差不多?
37樓 HelenXR2017-04-11 08:03發表 [回復]
技術分享
"p′=qpq?1"
這裏應該是p′ = qp或 p′ = pq-1吧?
36樓 親愛的老爸2017-02-16 13:07發表 [回復]
技術分享
看了半天,才註意到博主原來是樂樂,//汗牛滿面

unity--------------------四元數的旋轉與原理