0x00 前言
首先要說明的是,本文的標(biāo)題事實(shí)上來自于知乎上的一個(gè)同名問題:為什么directX里表示三維坐標(biāo)要建一個(gè)4*4的矩陣? - 編程(https://www.zhihu.com/question/36296104) 。因此,正如Milo Yip大神所說的這個(gè)標(biāo)題事實(shí)上是存在問題的:矩陣是用于表示變換而不是坐標(biāo)的。再了解了矩陣的作用之后,我們就要繼續(xù)思考為什么變換要使用一個(gè)4×4的矩陣而不是3×3的矩陣呢?是不是多此一舉呢?下面我們就來聊聊這個(gè)話題。
0x01 怎么平移一個(gè)三維空間中的點(diǎn)?
我們應(yīng)該怎么平移一個(gè)三維空間中的點(diǎn)呢?答案很簡(jiǎn)單,我們只需要對(duì)這個(gè)點(diǎn)的坐標(biāo)中的每個(gè)分量(x,y,z)和對(duì)應(yīng)軸上的平移距離相加即可。
例如,點(diǎn)p1(x1,y1,z1)在X軸Y軸以及Z軸上分別平移Δx,Δy,Δz到新的點(diǎn)p2(x2,y2,z2),那么我們只需在坐標(biāo)對(duì)應(yīng)的分量上加上這些增量就可以確定點(diǎn)p2的坐標(biāo)了。
x2 = x1 + Δx
y2 = y1 + Δy
z2 = z1 + Δz
很簡(jiǎn)單是嗎?那么接下來再讓我們來看一看另一種變換:旋轉(zhuǎn)。
0x02 再來旋轉(zhuǎn)一個(gè)點(diǎn)
旋轉(zhuǎn)相比較平移來說,會(huì)略顯復(fù)雜一些。因?yàn)槲覀冃枰该饕韵聨讉€(gè)方面來描述一個(gè)旋轉(zhuǎn):
旋轉(zhuǎn)軸
旋轉(zhuǎn)方向
旋轉(zhuǎn)角度
在這里,我們假設(shè)點(diǎn)p需要繞Z軸順時(shí)針旋轉(zhuǎn)β度。
如這個(gè)很難看的圖所示,我們的點(diǎn)P1(x1,y1,z1)以Z軸位軸順時(shí)針旋轉(zhuǎn)β度之后來到了點(diǎn)P2(x2,y2,z2)。接下來,讓我們假設(shè)原點(diǎn)到P1的距離位L,且P1和Y軸之間的夾角位α,那么根據(jù)三角函數(shù)公式我們就可以計(jì)算出P1點(diǎn)的具體坐標(biāo)了:
x1 = L·sinα
y1 = L·cosα
由于是繞Z軸旋轉(zhuǎn),因此z坐標(biāo)不變,因此此處不考慮。
同樣根據(jù)三角函數(shù)公式,我們可以繼續(xù)計(jì)算出P2的具體坐標(biāo):
x2 = L·sin(α + β)
y2 = L·cos(α + β)
再讓我們回憶一下中學(xué)時(shí)代的幾何數(shù)學(xué)的內(nèi)容,對(duì)青春的回憶又把我們帶回了課堂上老師聲嘶力竭向我們傳授的內(nèi)容——兩角和與差:
cos(α+β)=cosα·cosβ-sinα·sinβ
cos(α-β)=cosα·cosβ+sinα·sinβ
sin(α+β)=sinα·cosβ+cosα·sinβ
sin(α-β)=sinα·cosβ-cosα·sinβ
回憶起老師傳授給我們的知識(shí)之后,接下來結(jié)合P1的坐標(biāo)表示形式我們就可以把P2的坐標(biāo)換一個(gè)表示形式了。
x2 = L·(sinα·cosβ+cosα·sinβ) = cosβ·x1 + sinβ·y1
y2 = L·(cosα·cosβ-sinα·sinβ) = cosβ·y1 - sinβ·x1
z2 = z1
因此,在已知P1點(diǎn)坐標(biāo)以及旋轉(zhuǎn)角度β的情況下,我們就可以根據(jù)以上公式分別求出P2點(diǎn)坐標(biāo)的各個(gè)分量。如果再結(jié)合上一小節(jié)中平移一個(gè)點(diǎn)的公式來看,我們可以發(fā)現(xiàn)似乎并不需要矩陣,而僅僅通過兩組表達(dá)式就能實(shí)現(xiàn)坐標(biāo)的變換。但是......
0x03 帶來便捷的矩陣
當(dāng)然,從理論上講我們的確可以只通過數(shù)學(xué)公式就能實(shí)現(xiàn)變換,但實(shí)際的情況卻是在變換十分復(fù)雜時(shí),直接使用數(shù)學(xué)表達(dá)式來進(jìn)行運(yùn)算也是相當(dāng)繁復(fù)的。因此,在現(xiàn)實(shí)中常常使用矩陣(由m × n個(gè)標(biāo)量組成的長(zhǎng)方形數(shù)組)來表示諸如平移、旋轉(zhuǎn)以及縮放等線性變換。而兩個(gè)變換矩陣A和B的積P=AB,則變換矩陣P相當(dāng)于A和B所代表的變換。舉一個(gè)例子,若A為旋轉(zhuǎn)矩陣,B為平移矩陣,則矩陣P就能夠?qū)崿F(xiàn)旋轉(zhuǎn)和平移變換。不過需要注意的是,矩陣乘法不符合交換律,因此AB和BA并不相等。
說了這么多,我們似乎還是沒有回答為什么三維空間需要一個(gè)4×4矩陣來實(shí)現(xiàn)變換呢?下面我們就帶著這個(gè)問題,分別看看3×3矩陣和4×4矩陣的使用吧。
3×3矩陣和旋轉(zhuǎn)
首先,我們先來看一個(gè)矩陣乘以一個(gè)三維矢量的算式:
可以看到該矩陣為一個(gè)3×3的矩陣,矩陣的右側(cè)是點(diǎn)P1的坐標(biāo),而矩陣的左側(cè)則是點(diǎn)P2的坐標(biāo)。根據(jù)這個(gè)表達(dá)式,我們可以求出x2、y2、z2的值:
x2 = a·x1 + b·y1 + c·z1
y2 = d·x1 + e·y1 + f·z1
z2 = g·x1 + h·y1 + i·z1
為了將矩陣等式和之前小節(jié)的數(shù)學(xué)表達(dá)式聯(lián)系起來,下面我們就將旋轉(zhuǎn)表達(dá)式和該矩陣等式做一個(gè)對(duì)比。
x2 = a·x1 + b·y1 + c·z1
x2 = cosβ·x1 + sinβ·y1
y2 = d·x1 + e·y1 + f·z1
y2 = cosβ·y1 - sinβ·x1
z2 = g·x1 + h·y1 + i·z1
z2 = z1
通過對(duì)比x2,我們可以發(fā)現(xiàn)a=cosβ,b=sinβ,c=0;
對(duì)比y2,也可以發(fā)現(xiàn)d=-sinβ,e=cosβ,f=0;
最后對(duì)比z2,可以確定g=0,h=0,i=1;
將這個(gè)結(jié)果帶入到之前的矩陣中,我們的等式就可以變成下面這個(gè)樣子:
也就是說,通過這個(gè)3×3的變換矩陣,我們就已經(jīng)實(shí)現(xiàn)了三維空間的旋轉(zhuǎn)變換。那么為什么還需要使用4×4矩陣呢?
搞不定的平移,4×4矩陣來救場(chǎng)
我們已經(jīng)通過一個(gè)3×3矩陣搞定了旋轉(zhuǎn)變換,顯然如果這個(gè)3×3矩陣真的是完美的解決變換的方案的話,那么它顯然也必須要適合于其他的變換,例如平移。但是它到底能否滿足平移的需求呢?下面我們還是通過對(duì)比矩陣等式和數(shù)學(xué)表達(dá)式的方式,來尋找答案。
x2 = a·x1 + b·y1 + c·z1
x2 = x1 + Δx
y2 = d·x1 + e·y1 + f·z1
y2 = y1 + Δy
z2 = g·x1 + h·y1 + i·z1
z2 = z1 + Δz
通過對(duì)比,我們發(fā)現(xiàn)平移和旋轉(zhuǎn)之間很有趣的一個(gè)區(qū)別,那就是平移的表達(dá)式中帶有常量Δx,而無(wú)論是旋轉(zhuǎn)的表達(dá)式還是矩陣等式中都不存在這樣一個(gè)常量能夠與之對(duì)應(yīng)。那么問題就來,我們沒有辦法使用3×3的矩陣來表示平移。答案其實(shí)很簡(jiǎn)單,那就是使用4×4矩陣來實(shí)現(xiàn)。但是隨之而來的一個(gè)問題就是如何一個(gè)三維坐標(biāo)如何能和一個(gè)4×4的矩陣相乘呢?
齊次坐標(biāo)
為了解決三維矢量和4×4矩陣相乘的問題,我們機(jī)智的為三維矢量添加了第四個(gè)分量,這樣之前的三維矢量(x,y,z)就變成了四維的(x,y,z,w),這樣由4個(gè)分量組成的矢量便被稱為齊次坐標(biāo)。需要說明的是,齊次坐標(biāo)(x,y,z,w)等價(jià)于三維坐標(biāo)(x/w,y/w,z/w),因此只要w分量的值是1,那么這個(gè)齊次坐標(biāo)就可以被當(dāng)作三維坐標(biāo)來使用,而且所表示的坐標(biāo)就是以x,y,z這3個(gè)值為坐標(biāo)值的點(diǎn)。
因此,為了和4×4矩陣相乘,我們的P1點(diǎn)坐標(biāo)就變成了(x1,y1,z1,1)。而矩陣等式也變成了下面這個(gè)樣子:
我們?cè)賹⑦@個(gè)新的矩陣等式和平移的數(shù)學(xué)表達(dá)式做一番對(duì)比:
x2 = a·x1 + b·y1 + c·z1 + d
x2 = x1 + Δx
y2 = e·x1 + f·y1 + g·z1 + h
y2 = y1 + Δy
z2 = i·x1 + j·y1 + k·z1 + l
z2 = z1 + Δz
1 = m·x1 + n·y1 + o·z1 + p
通過對(duì)比x2,我們可以發(fā)現(xiàn)a=1,b=0,c=0,d=Δx;
對(duì)比y2,也可以發(fā)現(xiàn)e=0,f=1,g=0,h=Δy;
再對(duì)比z2,可以確定i=0,j=0,k=1,l=Δz;
最后還可以根據(jù)表達(dá)式求出m=0,n=0,o=0,p=1;
這樣,我們就求出了我們的4×4的平移矩陣:
0x04 總結(jié)
寫到這里,不知各位是否還記得之前在介紹矩陣乘法的時(shí)候我有提到過兩個(gè)變換矩陣A和B的積P=AB,相當(dāng)于A和B所代表的變換。事實(shí)上在游戲編程中,常常需要把一連串的變換預(yù)先通過計(jì)算成為單一矩陣,所以就不能即存在3×3的矩陣又存在4×4的矩陣。而將3×3矩陣拓展成4×4矩陣還是相對(duì)更加容易的。這樣,就通過一個(gè)4×4矩陣整合了平移矩陣、旋轉(zhuǎn)矩陣。
原文鏈接?傳送門