小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

【Unity技巧】四元數(shù)(Quaternion)和旋轉(zhuǎn)

 昵稱31750011 2016-05-27


四元數(shù)介紹


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


那么,四元數(shù)又是什么呢?簡單來說,四元數(shù)本質(zhì)上是一種高階復(fù)數(shù)(聽不懂了吧。。。),是一個四維空間,相對于復(fù)數(shù)的二維空間。我們高中的時候應(yīng)該都學(xué)過復(fù)數(shù),一個復(fù)數(shù)由實部和虛部組成,即x = a bi,i是虛數(shù)單位,如果你還記得的話應(yīng)該知道i^2 = -1。而四元數(shù)其實和我們學(xué)到的這種是類似的,不同的是,它的虛部包含了三個虛數(shù)單位,i、j、k,即一個四元數(shù)可以表示為x = a bi cj dk。那么,它和旋轉(zhuǎn)為什么會有關(guān)系呢?


在Unity里,tranform組件有一個變量名為rotation,它的類型就是四元數(shù)。很多初學(xué)者會直接取rotation的x、y、z,認為它們分別對應(yīng)了Transform面板里R的各個分量。當(dāng)然很快我們就會發(fā)現(xiàn)這是完全不對的。實際上,四元數(shù)的x、y、z和R的那三個值從直觀上來講沒什么關(guān)系,當(dāng)然會存在一個表達式可以轉(zhuǎn)換,在后面會講。


大家應(yīng)該和我一樣都有很多疑問,既然已經(jīng)存在了這兩種旋轉(zhuǎn)表示方式,為什么還要使用四元數(shù)這種聽起來很難懂的東西呢?我們先要了解這三種旋轉(zhuǎn)方式的優(yōu)缺點:

  • 矩陣旋轉(zhuǎn)
    • 優(yōu)點:
      • 旋轉(zhuǎn)軸可以是任意向量;
    • 缺點:
      • 旋轉(zhuǎn)其實只需要知道一個向量 一個角度,一共4個值的信息,但矩陣法卻使用了16個元素;
      • 而且在做乘法操作時也會增加計算量,造成了空間和時間上的一些浪費;

  • 歐拉旋轉(zhuǎn)
    • 優(yōu)點:
      • 很容易理解,形象直觀;
      • 表示更方便,只需要3個值(分別對應(yīng)x、y、z軸的旋轉(zhuǎn)角度);但按我的理解,它還是轉(zhuǎn)換到了3個3*3的矩陣做變換,效率不如四元數(shù);
    • 缺點:
      • 之前提到過這種方法是要按照一個固定的坐標軸的順序旋轉(zhuǎn)的,因此不同的順序會造成不同的結(jié)果;
      • 會造成萬向節(jié)鎖(Gimbal Lock)的現(xiàn)象。這種現(xiàn)象的發(fā)生就是由于上述固定坐標軸旋轉(zhuǎn)順序造成的。理論上,歐拉旋轉(zhuǎn)可以靠這種順序讓一個物體指到任何一個想要的方向,但如果在旋轉(zhuǎn)中不幸讓某些坐標軸重合了就會發(fā)生萬向節(jié)鎖,這時就會丟失一個方向上的旋轉(zhuǎn)能力,也就是說在這種狀態(tài)下我們無論怎么旋轉(zhuǎn)(當(dāng)然還是要原先的順序)都不可能得到某些想要的旋轉(zhuǎn)效果,除非我們打破原先的旋轉(zhuǎn)順序或者同時旋轉(zhuǎn)3個坐標軸。這里有個視頻可以直觀的理解下;
      • 由于萬向節(jié)鎖的存在,歐拉旋轉(zhuǎn)無法實現(xiàn)球面平滑插值;

  • 四元數(shù)旋轉(zhuǎn)
    • 優(yōu)點:
      • 可以避免萬向節(jié)鎖現(xiàn)象;
      • 只需要一個4維的四元數(shù)就可以執(zhí)行繞任意過原點的向量的旋轉(zhuǎn),方便快捷,在某些實現(xiàn)下比旋轉(zhuǎn)矩陣效率更高;
      • 可以提供平滑插值;
    • 缺點:
      • 比歐拉旋轉(zhuǎn)稍微復(fù)雜了一點點,因為多了一個維度;
      • 理解更困難,不直觀;


四元數(shù)和歐拉角



基礎(chǔ)知識



前面說過,一個四元數(shù)可以表示為q = w xi yj zk,現(xiàn)在就來回答這樣一個簡單的式子是怎么和三維旋轉(zhuǎn)結(jié)合在一起的。為了方便,我們下面使用q = ((x, y, z),w) = (v, w),其中v是向量,w是實數(shù),這樣的式子來表示一個四元數(shù)。

我們先來看問題的答案。我們可以使用一個四元數(shù)q=((x,y,z)sinθ2, cosθ2) 來執(zhí)行一個旋轉(zhuǎn)。具體來說,如果我們想要把空間的一個點P繞著單位向量軸u = (x, y, z)表示的旋轉(zhuǎn)軸旋轉(zhuǎn)θ角度,我們首先把點P擴展到四元數(shù)空間,即四元數(shù)p = (P, 0)。那么,旋轉(zhuǎn)后新的點對應(yīng)的四元數(shù)(當(dāng)然這個計算而得的四元數(shù)的實部為0,虛部系數(shù)就是新的坐標)為:

p=qpq?1

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

  • 共軛四元數(shù):q?=(?v,w)

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

  • 四元數(shù)的逆q?1=q?N(q)


它的證明這里不再贅述,有興趣的可以參見這篇文章。主要思想是構(gòu)建了一個輔助向量k,它是將p繞旋轉(zhuǎn)軸旋轉(zhuǎn)θ/2得到的。證明過程嘗試證明wk?=kv?,以此證明w與v、k在同一平面內(nèi),且與v夾角為θ

我們舉個最簡單的例子:把點P(1, 0, 1)繞旋轉(zhuǎn)軸u = (0, 1, 0)旋轉(zhuǎn)90°,求旋轉(zhuǎn)后的頂點坐標。首先將P擴充到四元數(shù),即p = (P, 0)。而q = (u*sin45°, cos45°)。求p=qpq?1的值。建議大家一定要在紙上計算一邊,這樣才能加深印象,連筆都懶得動的人還是不要往下看了。最后的結(jié)果p` = ((1, 0, -1), 0),即旋轉(zhuǎn)后的頂點位置是(1, 0, -1)。

如果想要得到復(fù)合旋轉(zhuǎn),只需類似復(fù)合矩陣那樣左乘新的四元數(shù),再進行運算即可。

我們來總結(jié)下四元數(shù)旋轉(zhuǎn)的幾個需要注意的地方

  • 用于旋轉(zhuǎn)的四元數(shù),每個分量的范圍都在(-1,1);

  • 每一次旋轉(zhuǎn)實際上需要兩個四元數(shù)的參與,即q和q*;

  • 所有用于旋轉(zhuǎn)的四元數(shù)都是單位四元數(shù),即它們的模是1;


下面是幾點建議:

  • 實際上,在Unity里即便你不知道上述公式和變換也絲毫不妨礙我們使用四元數(shù),但是有一點要提醒你,除非你對四元數(shù)非常了解,那么不要直接對它們進行賦值。

  • 如果你不想知道原理,只想在Unity里找到對應(yīng)的函數(shù)來進行四元數(shù)變換,那么你可以使用這兩個函數(shù):Quaternion.EulerQuaternion.eulerAngles。它們基本可以滿足絕大多數(shù)的四元數(shù)旋轉(zhuǎn)變換。


和其他類型的轉(zhuǎn)換


首先是軸角到四元數(shù)

給定一個單位長度的旋轉(zhuǎn)軸(x, y, z)和一個角度θ。對應(yīng)的四元數(shù)為:
q=((x,y,z)sinθ2cosθ2) 


這個公式的推導(dǎo)過程上面已經(jīng)給出。

歐拉角到四元數(shù)

給定一個歐拉旋轉(zhuǎn)(X, Y, Z)(即分別繞x軸、y軸和z軸旋轉(zhuǎn)X、Y、Z度),則對應(yīng)的四元數(shù)為:

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)

它的證明過程可以依靠軸角到四元數(shù)的公式進行推導(dǎo)。

其他參考鏈接



四元數(shù)的插值


這里的插值指的是球面線性插值。

設(shè)t是一個在0到1之間的變量。我們想要基于t求Q1到Q2之間插值后四元數(shù)Q。它的公式是:

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



四元數(shù)的創(chuàng)建


在了解了上述知識后,我們就不需要那么懼怕四元數(shù)了,實際上它和矩陣類似,不同的只是它的表示方式以及運算方式。那么在Unity里如何利用四元數(shù)進行旋轉(zhuǎn)呢?Unity里提供了非常多的方式來創(chuàng)建一個四元數(shù)。例如Quaternion.AngleAxis(float angle, Vector3 axis),它可以返回一個繞軸線axis旋轉(zhuǎn)angle角度的四元數(shù)變換。我們可以一個Vector3和它進行左乘,就將得到旋轉(zhuǎn)后的Vector3。在Unity里只需要用一個“ * ”操作符就可以進行四元數(shù)對向量的變換操作,相當(dāng)于我們上述講到的p=qpq?1操作。如果我們想要進行多個旋轉(zhuǎn)變換,只需要左乘其他四元數(shù)變換即可。例如下面這樣:
Vector3 newVector = Quaternion.AngleAxis(90, Vector3.up) * Quaternion.LookRotation(someDirection) * someVector;

盡管歐拉角更容易我們理解,但四元數(shù)比歐拉角要強大很多。Unity提供了這兩種方式供我們選擇,我們可以選擇最合適的變換。
例如,如果我們需要對旋轉(zhuǎn)進行插值,我們可以首先使用Quaternion.eulerAngles來得到歐拉角度,然后使用Mathf.Clamp對其進行插值運算。
最后更新Quaternion.eulerAngles或者使用Quaternion.Euler(yourAngles)來創(chuàng)建一個新的四元數(shù)。

又例如,如果你想要組合旋轉(zhuǎn),比如讓人物的腦袋向下看或者旋轉(zhuǎn)身體,兩種方法其實都可以,但一旦這些旋轉(zhuǎn)不是以世界坐標軸為旋轉(zhuǎn)軸,比如人物扭動脖子向下看等,那么四元數(shù)是一個更合適的選擇。Unity還提供了transform.forward, transform.right and transform.up 這些非常有用的軸,這些軸可以和Quaternion.AngleAxis組合起來,來創(chuàng)建非常有用的旋轉(zhuǎn)組合。例如,下面的代碼讓物體執(zhí)行低頭的動作:
transform.rotation = Quaternion.AngleAxis(degrees, transform.right) * transform.rotation;


關(guān)于Quaternion的其他函數(shù),后面再補充吧,原理類似~



補充:歐拉旋轉(zhuǎn)


在文章開頭關(guān)于歐拉旋轉(zhuǎn)的細節(jié)沒有解釋的太清楚,而又有不少人詢問相關(guān)問題,我盡量把自己的理解寫到這里,如有不對還望指出。


歐拉旋轉(zhuǎn)是怎么運作的



歐拉旋轉(zhuǎn)是我們最容易理解的一種旋轉(zhuǎn)方式。以我們生活中為例,一個舞蹈老師告訴我們,完成某個舞蹈動作需要先向你的左邊轉(zhuǎn)30°,再向左側(cè)彎腰60°,再起身向后彎腰90°(如果你能辦到的話)。上面這樣一個旋轉(zhuǎn)的過程其實和我們在三維中進行歐拉旋轉(zhuǎn)很類似,即我們是通過指明繞三個軸旋轉(zhuǎn)的角度來進行旋轉(zhuǎn)的,不同的是,日常生活中我們更愿意叫這些軸為前后左右上下。而這也意味著我們需要指明一個旋轉(zhuǎn)順序。這是因為,先繞X軸旋轉(zhuǎn)90°、再繞Y軸30°和先繞Y軸旋轉(zhuǎn)90°、再繞X軸30°得到的是不同的結(jié)果。

在Unity里,歐拉旋轉(zhuǎn)的旋轉(zhuǎn)順序是Z、X、Y,這在相關(guān)的API文檔中都有說明,例如Transform.Rotate。其實文檔中說得不是非常詳細,還有一個細節(jié)我們需要明白。如果你仔細想想,就會發(fā)現(xiàn)有一個非常重要的東西我們沒有說明白,那就是旋轉(zhuǎn)時使用的坐標系。給定一個旋轉(zhuǎn)順序(例如這里的Z、X、Y),以及它們對應(yīng)的旋轉(zhuǎn)角度(α,β,r),有兩種坐標系可以選擇:
  1. 繞坐標系E下的Z軸旋轉(zhuǎn)α,繞坐標系E下的Y軸旋轉(zhuǎn)β,繞坐標系E下的X軸旋轉(zhuǎn)r,即進行一次旋轉(zhuǎn)時不一起旋轉(zhuǎn)當(dāng)前坐標系;
  2. 繞坐標系E下的Z軸旋轉(zhuǎn)α,繞坐標系E在繞Z軸旋轉(zhuǎn)α后的新坐標系E'下的Y軸旋轉(zhuǎn)β,繞坐標系E'在繞Y軸旋轉(zhuǎn)β后的新坐標系E''下的X軸旋轉(zhuǎn)r, 即在旋轉(zhuǎn)時,把坐標系一起轉(zhuǎn)動;

很容易知道,這兩種選擇的結(jié)果是不一樣的。但如果把它們的旋轉(zhuǎn)順序顛倒一下,其實結(jié)果就會一樣。說得明白點,在第一種情況下、按ZXY順序旋轉(zhuǎn)和在第二種情況下、按YXZ順序旋轉(zhuǎn)是一樣的。證明方法可以看下這篇文章。而Unity文檔中說明的旋轉(zhuǎn)順序指的是在第一種情況下的順序。

如果你還是不懂這意味著什么,可以試著調(diào)用下這個函數(shù)。例如,你認為下面代碼的結(jié)果是什么:
transform.Rotate(new Vector3(0, 30, 90));

原模型的方向和執(zhí)行結(jié)果如下:
 



而我們可以再分別執(zhí)行下面的代碼:
// First case transform.Rotate(new Vector3(0, 30, 0)); transform.Rotate(new Vector3(0, 0, 90)); // Second case // transform.Rotate(new Vector3(0, 0, 90)); // transform.Rotate(new Vector3(0, 30, 0));

兩種情況的結(jié)果分別是:
 

可以發(fā)現(xiàn),調(diào)用transform.Rotate(new Vector3(03090));是和第一種情況中的代碼是一樣的結(jié)果,即先旋轉(zhuǎn)Y、再旋轉(zhuǎn)Z。進一步實驗,我們會發(fā)現(xiàn)transform.Rotate(new Vector3(3090, -40));的結(jié)果是和transform.Rotate(new Vector3(0900));transform.Rotate(new Vector3(3000));transform.Rotate(new Vector3(00, -40));的結(jié)果一樣的。你會問了,文檔中不是明明說了旋轉(zhuǎn)順序是Z、X、Y嗎?怎么現(xiàn)在完全反過來了呢?原因就是我們之前說的兩種坐標系的選擇。在一次調(diào)用transform.Rotate的過程中,坐標軸是不隨每次單個坐標軸的旋轉(zhuǎn)而旋轉(zhuǎn)的。而在調(diào)用transform.Rotate后,這個旋轉(zhuǎn)坐標系才會變化。也就是說,transform.Rotate(new Vector3(3090, -40));執(zhí)行時使用的是第一種情況,而transform.Rotate(new Vector3(0900));transform.Rotate(new Vector3(3000));transform.Rotate(new Vector3(00, -40));每一句則是分別使用了上一句執(zhí)行后的坐標系,即第二種坐標系情況。因此,我們看起來順序好像是完全是反了,但結(jié)果是一樣的。

上面只是說了一些容易混淆的地方,更多的內(nèi)容大家可以搜搜wiki之類的。


數(shù)學(xué)模型

歐拉旋轉(zhuǎn)的數(shù)學(xué)實現(xiàn)就是使用矩陣。而最常見的表示方法就是3*3的矩陣。在Wiki里我們可以找到這種矩陣的表示形式,以下以按XYZ的旋轉(zhuǎn)順序為例,三個矩陣分別表示了:




在計算時,我們將原來的旋轉(zhuǎn)矩陣右乘(這里使用的是列向量)上面的矩陣。從這里我們也可以證明上面所說的兩種坐標系選擇是一樣的結(jié)果,它們之間的不同從這里來看其實就是矩陣相乘時的順序不同。第一種坐標系情況,指的是在計算時,先從左到右直接計算R中3個矩陣的結(jié)果矩陣,最后再和原旋轉(zhuǎn)矩陣相乘,因此順序是XYZ;而第二種坐標系情況,指的是在計算時,從右往左依次相乘,因此順序是反過來的,ZYX。你可以驗證R左乘和右乘的結(jié)果表達式,就可以相信這個結(jié)論了!


萬向節(jié)鎖



雖然歐拉旋轉(zhuǎn)非常容易理解,但它會造成臭名昭著的萬向節(jié)鎖問題。我之前給出了鏈接大家可能都看了,但還是不明白這是怎么回事。這里有一篇文章是我目前找到說得最容易懂的中文文章,大家可以看看。

如果你還是不明白,我們來做個試驗。還是使用之前的模型,這次我們直接在面板中把它的歐拉角中的X值設(shè)為90°,其他先保持不變:


此時模型是臉朝下(下圖你看到的只是一個頭頂):


現(xiàn)在,如果我讓你不動X軸,只設(shè)置Y和Z的值,把這個模型的臉轉(zhuǎn)上來,讓它向側(cè)面看,你可以辦到嗎?你可以發(fā)現(xiàn),這時候無論你怎么設(shè)置Y和Z的值,模型始終是臉朝下、在同一平面旋轉(zhuǎn),看起來就是Y和Z控制的是同一個軸的旋轉(zhuǎn),下面是我截取的任意兩種情況:
 

這就是一種萬向節(jié)鎖的情況。這里我們先設(shè)置X軸為90°也是有原因的,這是因為Unity中歐拉角的旋轉(zhuǎn)順序是ZXY,即X軸是第二個旋轉(zhuǎn)軸。當(dāng)我們在面板中設(shè)置任意旋轉(zhuǎn)值時,Unity實際是按照固定的ZXY順序依次旋轉(zhuǎn)特定角度的。

在代碼里,我們同樣可以重現(xiàn)萬向節(jié)鎖現(xiàn)象。
transform.Rotate(new Vector3(0, 0, 40)); transform.Rotate(new Vector3(0, 90, 0)); transform.Rotate(new Vector3(80, 0, 0));

我們只需要固定中間一句代碼,即使Y軸的旋轉(zhuǎn)角度始終為90°,那么你會發(fā)現(xiàn)無論你怎么調(diào)整第一句和最后一句中的X或Z值,它會像一個鐘表的表針一樣總是在同一個平面上運動。

萬向節(jié)鎖中的“鎖”,其實是給人一種誤導(dǎo),這可能也是讓很多人覺得難以理解的一個原因。實際上,實際上它并沒有鎖住任何一個旋轉(zhuǎn)軸,只是說我們會在這種旋轉(zhuǎn)情況下會感覺喪失了一個維度。以上面的例子來說,盡管固定了第二個旋轉(zhuǎn)軸的角度為90°,但我們原以為依靠改變其他兩個軸的旋轉(zhuǎn)角度是可以得到任意旋轉(zhuǎn)位置的(因為按我們理解,兩個軸應(yīng)該控制的是兩個空間維度),而事實是它被“鎖”在了一個平面,即只有一個維度了,缺失了一個維度。而只要第二個旋轉(zhuǎn)軸不是±90°,我們就可以依靠改變其他兩個軸的旋轉(zhuǎn)角度來得到任意旋轉(zhuǎn)位置。


數(shù)學(xué)解釋


我們從最簡單的矩陣來理解。還是使用XYZ的旋轉(zhuǎn)順序。當(dāng)Y軸的旋轉(zhuǎn)角度為90°時,我們會得到下面的旋轉(zhuǎn)矩陣:



我們對上述矩陣進行左乘可以得到下面的結(jié)果:


可以發(fā)現(xiàn),此時當(dāng)我們改變第一次和第三次的旋轉(zhuǎn)角度時,是同樣的效果,而不會改變第一行和第三列的任何數(shù)值,從而缺失了一個維度。

我們再嘗試著理解下它的本質(zhì)。Wiki上寫,萬向節(jié)鎖出現(xiàn)的本質(zhì)原因,是因為從歐拉角到旋轉(zhuǎn)的映射并不是一個覆蓋映射,即它并不是在每個點處都是局部同胚的。不懂吧。。。恩,我們再來通俗一下解釋,這意味著,從歐拉角到旋轉(zhuǎn)是一個多對一的映射(即不同的歐拉角可以表示同一個旋轉(zhuǎn)方向),而且并不是每一個旋轉(zhuǎn)變化都可以用歐拉角來表示。其他更多的大家去參考wiki吧。


建議還是多看看視頻,尤其是后面的部分。當(dāng)然,如果還是覺得懵懵懂懂的話,在《3D數(shù)學(xué)基礎(chǔ):圖形與游戲開發(fā)》一書中有一話說的很有道理,“如果您從來沒有遇到過萬向鎖情況,你可能會對此感到困惑,而且不幸的是,很難在本書中講清楚這個問題,你需要親身經(jīng)歷才能明白?!币虼耍蠹乙膊灰m結(jié)啦,等到遇到的時候可以想到是因為萬向節(jié)鎖的原因就好。


    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多