四元數(shù)介紹
旋轉(zhuǎn)辅辩,應(yīng)該是三種坐標(biāo)變換——縮放炒事、旋轉(zhuǎn)和平移臀栈,中最復(fù)雜的一種了。大家應(yīng)該都聽過挠乳,有一種旋轉(zhuǎn)的表示方法叫四元數(shù)权薯。按照我們的習(xí)慣,我們更加熟悉的是另外兩種旋轉(zhuǎn)的表示方法——矩陣旋轉(zhuǎn)和歐拉旋轉(zhuǎn)睡扬。矩陣旋轉(zhuǎn)使用了一個(gè)4*4大小的矩陣來表示繞任意軸旋轉(zhuǎn)的變換矩陣盟蚣,而歐拉選擇則是按照一定的坐標(biāo)軸順序(例如先x、再y卖怜、最后z)屎开、每個(gè)軸旋轉(zhuǎn)一定角度來變換坐標(biāo)或向量,它實(shí)際上是一系列坐標(biāo)軸旋轉(zhuǎn)的組合马靠。
那么奄抽,四元數(shù)又是什么呢?簡(jiǎn)單來說甩鳄,四元數(shù)本質(zhì)上是一種高階復(fù)數(shù)(聽不懂了吧逞度。。妙啃。)档泽,是一個(gè)四維空間,相對(duì)于復(fù)數(shù)的二維空間。我們高中的時(shí)候應(yīng)該都學(xué)過復(fù)數(shù)馆匿,一個(gè)復(fù)數(shù)由實(shí)部和虛部組成抑胎,即x = a + bi,i是虛數(shù)單位甜熔,如果你還記得的話應(yīng)該知道i^2 = -1圆恤。而四元數(shù)其實(shí)和我們學(xué)到的這種是類似的,不同的是腔稀,它的虛部包含了三個(gè)虛數(shù)單位盆昙,i、j焊虏、k淡喜,即一個(gè)四元數(shù)可以表示為x = a + bi + cj + dk。那么诵闭,它和旋轉(zhuǎn)為什么會(huì)有關(guān)系呢炼团?
在Unity里,tranform組件有一個(gè)變量名為rotation疏尿,它的類型就是四元數(shù)瘟芝。很多初學(xué)者會(huì)直接取rotation的x、y褥琐、z锌俱,認(rèn)為它們分別對(duì)應(yīng)了Transform面板里R的各個(gè)分量。當(dāng)然很快我們就會(huì)發(fā)現(xiàn)這是完全不對(duì)的敌呈。實(shí)際上贸宏,四元數(shù)的x、y磕洪、z和R的那三個(gè)值從直觀上來講沒什么關(guān)系吭练,當(dāng)然會(huì)存在一個(gè)表達(dá)式可以轉(zhuǎn)換,在后面會(huì)講析显。
大家應(yīng)該和我一樣都有很多疑問鲫咽,既然已經(jīng)存在了這兩種旋轉(zhuǎn)表示方式,為什么還要使用四元數(shù)這種聽起來很難懂的東西呢叫榕?我們先要了解這三種旋轉(zhuǎn)方式的優(yōu)缺點(diǎn):
矩陣旋轉(zhuǎn)
優(yōu)點(diǎn):
旋轉(zhuǎn)軸可以是任意向量浑侥;
缺點(diǎn):
旋轉(zhuǎn)其實(shí)只需要知道一個(gè)向量+一個(gè)角度,一共4個(gè)值的信息晰绎,但矩陣法卻使用了16個(gè)元素;
而且在做乘法操作時(shí)也會(huì)增加計(jì)算量括丁,造成了空間和時(shí)間上的一些浪費(fèi)荞下;
歐拉旋轉(zhuǎn)
優(yōu)點(diǎn):
很容易理解,形象直觀;
表示更方便尖昏,只需要3個(gè)值(分別對(duì)應(yīng)x仰税、y、z軸的旋轉(zhuǎn)角度)抽诉;但按我的理解陨簇,它還是轉(zhuǎn)換到了3個(gè)3*3的矩陣做變換,效率不如四元數(shù)迹淌;
缺點(diǎn):
之前提到過這種方法是要按照一個(gè)固定的坐標(biāo)軸的順序旋轉(zhuǎn)的河绽,因此不同的順序會(huì)造成不同的結(jié)果;
會(huì)造成萬向節(jié)鎖(Gimbal Lock)的現(xiàn)象唉窃。這種現(xiàn)象的發(fā)生就是由于上述固定坐標(biāo)軸旋轉(zhuǎn)順序造成的耙饰。理論上,歐拉旋轉(zhuǎn)可以靠這種順序讓一個(gè)物體指到任何一個(gè)想要的方向纹份,但如果在旋轉(zhuǎn)中不幸讓某些坐標(biāo)軸重合了就會(huì)發(fā)生萬向節(jié)鎖苟跪,這時(shí)就會(huì)丟失一個(gè)方向上的旋轉(zhuǎn)能力,也就是說在這種狀態(tài)下我們無論怎么旋轉(zhuǎn)(當(dāng)然還是要原先的順序)都不可能得到某些想要的旋轉(zhuǎn)效果蔓涧,除非我們打破原先的旋轉(zhuǎn)順序或者同時(shí)旋轉(zhuǎn)3個(gè)坐標(biāo)軸件已。這里有個(gè)視頻可以直觀的理解下;
由于萬向節(jié)鎖的存在元暴,歐拉旋轉(zhuǎn)無法實(shí)現(xiàn)球面平滑插值篷扩;
四元數(shù)旋轉(zhuǎn)
優(yōu)點(diǎn):
可以避免萬向節(jié)鎖現(xiàn)象;
只需要一個(gè)4維的四元數(shù)就可以執(zhí)行繞任意過原點(diǎn)的向量的旋轉(zhuǎn)昨寞,方便快捷瞻惋,在某些實(shí)現(xiàn)下比旋轉(zhuǎn)矩陣效率更高;
可以提供平滑插值援岩;
缺點(diǎn):
比歐拉旋轉(zhuǎn)稍微復(fù)雜了一點(diǎn)點(diǎn)歼狼,因?yàn)槎嗔艘粋€(gè)維度;
理解更困難享怀,不直觀羽峰;
四元數(shù)和歐拉角
基礎(chǔ)知識(shí)
前面說過,一個(gè)四元數(shù)可以表示為q = w + xi + yj + zk添瓷,現(xiàn)在就來回答這樣一個(gè)簡(jiǎn)單的式子是怎么和三維旋轉(zhuǎn)結(jié)合在一起的梅屉。為了方便,我們下面使用q = ((x, y, z)鳞贷,w) = (v, w)坯汤,其中v是向量,w是實(shí)數(shù)搀愧,這樣的式子來表示一個(gè)四元數(shù)惰聂。
我們先來看問題的答案疆偿。我們可以使用一個(gè)四元數(shù)q=((x,y,z)sinθ2, cosθ2) 來執(zhí)行一個(gè)旋轉(zhuǎn)。具體來說搓幌,如果我們想要把空間的一個(gè)點(diǎn)P繞著單位向量軸u = (x, y, z)表示的旋轉(zhuǎn)軸旋轉(zhuǎn)θ角度杆故,我們首先把點(diǎn)P擴(kuò)展到四元數(shù)空間,即四元數(shù)p = (P, 0)溉愁。那么处铛,旋轉(zhuǎn)后新的點(diǎn)對(duì)應(yīng)的四元數(shù)(當(dāng)然這個(gè)計(jì)算而得的四元數(shù)的實(shí)部為0,虛部系數(shù)就是新的坐標(biāo))為:
p′=qpq?1
其中拐揭,q=(cosθ2, (x,y,z)sinθ2) 撤蟆,q?1=q?N(q),由于u是單位向量投队,因此 N(q)=1枫疆,即q?1=q?。右邊表達(dá)式包含了四元數(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ù)到原點(diǎn)的距離
四元數(shù)的逆:q?1=q?N(q)
它的證明這里不再贅述,有興趣的可以參見這篇文章扒披。主要思想是構(gòu)建了一個(gè)輔助向量k值依,它是將p繞旋轉(zhuǎn)軸旋轉(zhuǎn)θ/2得到的。證明過程嘗試證明wk?=kv?碟案,以此證明w與v愿险、k在同一平面內(nèi),且與v夾角為θ价说。
我們舉個(gè)最簡(jiǎn)單的例子:把點(diǎn)P(1, 0, 1)繞旋轉(zhuǎn)軸u = (0, 1, 0)旋轉(zhuǎn)90°辆亏,求旋轉(zhuǎn)后的頂點(diǎn)坐標(biāo)。首先將P擴(kuò)充到四元數(shù)鳖目,即p = (P, 0)扮叨。而q = (u*sin45°, cos45°)。求p′=qpq?1的值领迈。建議大家一定要在紙上計(jì)算一邊彻磁,這樣才能加深印象,連筆都懶得動(dòng)的人還是不要往下看了狸捅。最后的結(jié)果p` = ((1, 0, -1), 0)衷蜓,即旋轉(zhuǎn)后的頂點(diǎn)位置是(1, 0, -1)。
如果想要得到復(fù)合旋轉(zhuǎn)尘喝,只需類似復(fù)合矩陣那樣左乘新的四元數(shù)磁浇,再進(jìn)行運(yùn)算即可。
我們來總結(jié)下四元數(shù)旋轉(zhuǎn)的幾個(gè)需要注意的地方:
- 用于旋轉(zhuǎn)的四元數(shù)朽褪,每個(gè)分量的范圍都在(-1扯夭,1)鳍贾;
- 每一次旋轉(zhuǎn)實(shí)際上需要兩個(gè)四元數(shù)的參與鞍匾,即q和q*交洗;
- 所有用于旋轉(zhuǎn)的四元數(shù)都是單位四元數(shù),即它們的模是1橡淑;
下面是幾點(diǎn)建議:
- 實(shí)際上构拳,在Unity里即便你不知道上述公式和變換也絲毫不妨礙我們使用四元數(shù),但是有一點(diǎn)要提醒你梁棠,除非你對(duì)四元數(shù)非常了解置森,那么不要直接對(duì)它們進(jìn)行賦值。
- 如果你不想知道原理符糊,只想在Unity里找到對(duì)應(yīng)的函數(shù)來進(jìn)行四元數(shù)變換凫海,那么你可以使用這兩個(gè)函數(shù):Quaternion.Euler和Quaternion.eulerAngles。它們基本可以滿足絕大多數(shù)的四元數(shù)旋轉(zhuǎn)變換男娄。
和其他類型的轉(zhuǎn)換
首先是軸角到四元數(shù):
給定一個(gè)單位長(zhǎng)度的旋轉(zhuǎn)軸(x, y, z)和一個(gè)角度θ行贪。對(duì)應(yīng)的四元數(shù)為:
q=((x,y,z)sinθ2, cosθ2)
這個(gè)公式的推導(dǎo)過程上面已經(jīng)給出。
歐拉角到四元數(shù):
給定一個(gè)歐拉旋轉(zhuǎn)(X, Y, Z)(即分別繞x軸模闲、y軸和z軸旋轉(zhuǎn)X建瘫、Y、Z度)尸折,則對(duì)應(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ù)的公式進(jìn)行推導(dǎo)啰脚。
其他參考鏈接:
四元數(shù)的插值
這里的插值指的是球面線性插值。
設(shè)t是一個(gè)在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í)后亮航,我們就不需要那么懼怕四元數(shù)了荸实,實(shí)際上它和矩陣類似,不同的只是它的表示方式以及運(yùn)算方式塞赂。那么在Unity里如何利用四元數(shù)進(jìn)行旋轉(zhuǎn)呢泪勒?Unity里提供了非常多的方式來創(chuàng)建一個(gè)四元數(shù)。例如Quaternion.AngleAxis(float angle, Vector3 axis)宴猾,它可以返回一個(gè)繞軸線axis旋轉(zhuǎn)angle角度的四元數(shù)變換圆存。我們可以一個(gè)Vector3和它進(jìn)行左乘,就將得到旋轉(zhuǎn)后的Vector3仇哆。在Unity里只需要用一個(gè)“ * ”操作符就可以進(jìn)行四元數(shù)對(duì)向量的變換操作沦辙,相當(dāng)于我們上述講到的p′=qpq?1操作。如果我們想要進(jìn)行多個(gè)旋轉(zhuǎn)變換讹剔,只需要左乘其他四元數(shù)變換即可油讯。例如下面這樣:
Vector3 newVector = Quaternion.AngleAxis(90, Vector3.up) * Quaternion.LookRotation(someDirection) * someVector;
盡管歐拉角更容易我們理解详民,但四元數(shù)比歐拉角要強(qiáng)大很多。Unity提供了這兩種方式供我們選擇陌兑,我們可以選擇最合適的變換沈跨。
例如,如果我們需要對(duì)旋轉(zhuǎn)進(jìn)行插值兔综,我們可以首先使用Quaternion.eulerAngles來得到歐拉角度饿凛,然后使用Mathf.Clamp對(duì)其進(jìn)行插值運(yùn)算。
最后更新Quaternion.eulerAngles或者使用Quaternion.Euler(yourAngles)來創(chuàng)建一個(gè)新的四元數(shù)软驰。
又例如涧窒,如果你想要組合旋轉(zhuǎn),比如讓人物的腦袋向下看或者旋轉(zhuǎn)身體锭亏,兩種方法其實(shí)都可以纠吴,但一旦這些旋轉(zhuǎn)不是以世界坐標(biāo)軸為旋轉(zhuǎn)軸,比如人物扭動(dòng)脖子向下看等慧瘤,那么四元數(shù)是一個(gè)更合適的選擇戴已。Unity還提供了transform.forward, transform.right and transform.up 這些非常有用的軸,這些軸可以和Quaternion.AngleAxis組合起來碑隆,來創(chuàng)建非常有用的旋轉(zhuǎn)組合恭陡。例如,下面的代碼讓物體執(zhí)行低頭的動(dòng)作:
transform.rotation = Quaternion.AngleAxis(degrees, transform.right) * transform.rotation;
關(guān)于Quaternion的其他函數(shù)上煤,后面再補(bǔ)充吧休玩,原理類似~