我曾經在Shader山下(十六)座標空間與轉換矩陣中介紹過,一個物體要顯示在平面上,需要經過四步空間變換(實際上是五步):
物體空間->世界空間->觀察空間->裁剪空間(->歸一化設備空間)->屏幕空間
(差不多就是這個意思)
實際上,在可編程渲染管線中,我們往往只需要操作從物體空間到裁剪空間的轉換,可以用一個矩陣鏈乘M(odel)V(iew)P(rojection)表示。本文就介紹一下這三個矩陣。
索性全介紹了吧。
先簡單介紹一下變換矩陣。
常用的變換矩陣有三種:平移矩陣、旋轉矩陣和縮放矩陣。
平移矩陣:
如果一個物體在世界座標系中的位置爲
(px,py,pz)
,那麼它的平移矩陣爲:
T(p)=⎡⎣⎢⎢⎢⎢100001000010pxpypz1⎤⎦⎥⎥⎥⎥
旋轉矩陣:
繞x軸旋轉
θx
,旋轉矩陣爲:
Rx(θx)=⎡⎣⎢⎢⎢10000cosθxsinθx00−sinθxcosθx00001⎤⎦⎥⎥⎥
繞y軸旋轉
θy
,旋轉矩陣爲:
Ry(θy)=⎡⎣⎢⎢⎢⎢cosθy0−sinθy00100sinθy0cosθy00001⎤⎦⎥⎥⎥⎥
繞z軸旋轉
θz
,旋轉矩陣爲:
Rz(θz)=⎡⎣⎢⎢⎢cosθzsinθz00−sinθzcosθz0000100001⎤⎦⎥⎥⎥
縮放矩陣:
一個向量沿x、y、z軸分別縮放
(qx,qy,qz)
,那麼縮放矩陣爲:
S(q)=⎡⎣⎢⎢⎢⎢qx0000qy0000qz00001⎤⎦⎥⎥⎥⎥
組合成變換矩陣
只需要將這些矩陣鏈乘即可。但是需要注意順序。這裏按照T(ranslate)R(otate)S(cale)和H(eading)P(itch)B(ank)的順序進行鏈乘。
Q=T(p)Ry(θy)Rx(θx)Rz(θz)S(q)
等等!爲什麼是4*4的矩陣?參見百度百科齊次座標
簡單來講,3*3的矩陣只能表示旋轉和縮放矩陣,爲了表示平移,便擴充爲4*4的矩陣。
當然點或向量要與矩陣運算時也要擴充爲4維(x,y,z,w)。其中w=1表示點,w=0表示向量。因爲向量只有方向沒有位置,所以w=0便不會與矩陣第4行進行運算。
模型變換矩陣
這個本身沒什麼,其實就是相對位置(模型空間)到絕對位置(世界座標)的一個變換。
已知模型的位置、旋轉角度和縮放值,帶入之前的公式,便可以得到相應的模型變換矩陣。
當要計算模型內部某個點的世界座標時,只需要將這個點與變換矩陣相乘即可。
觀察矩陣
攝像機的方位可以由四個向量表示,
p
表示位置,
d
表示觀察方向,
u
表示上方向,
r
表示右方向。
在空間變換時,我們希望將攝像機移動到世界座標原點,並且觀察方向與z軸重合,上方向與y軸重合,右方向與x軸重合。當然世界中的物體也要隨之變換。
2D平面演示:
設V爲觀察矩陣,那麼就有:
Vp=(0,0,0)
Vr=(1,0,0)
Vu=(0,1,0)
Vd=(0,0,1)
因爲攝像機沒有大小,只有位置和方位,那麼就表示,我們通過平移和旋轉來完成這個變換。
首先是平移矩陣
沒什麼好說的:
T=⎡⎣⎢⎢⎢⎢100001000010−px−py−pz1⎤⎦⎥⎥⎥⎥
旋轉
我們只考慮矩陣的前三行和前三列即可,設爲A:
Ar=⎡⎣⎢a0a3a6a1a4a7a2a5a8⎤⎦⎥⎡⎣⎢rxryrz⎤⎦⎥=⎡⎣⎢100⎤⎦⎥
Au=⎡⎣⎢a0a3a6a1a4a7a2a5a8⎤⎦⎥⎡⎣⎢uxuyuz⎤⎦⎥=⎡⎣⎢010⎤⎦⎥
Ad=⎡⎣⎢a0a3a6a1a4a7a2a5a8⎤⎦⎥⎡⎣⎢dxdydz⎤⎦⎥=⎡⎣⎢001⎤⎦⎥
聯立可得:
AB=⎡⎣⎢a0a3a6a1a4a7a2a5a8⎤⎦⎥⎡⎣⎢rxryrzuxuyuzdxdydz⎤⎦⎥=⎡⎣⎢100010001⎤⎦⎥
那麼可以看出
A
是
B
的逆矩陣,由於
B
是標準正交矩陣(
r⊥u⊥d
,併爲單位向量),所以其逆矩陣與其轉置矩陣相等。即:
A=B−1=BT=⎡⎣⎢rxuxdxryuydyrzuzdz⎤⎦⎥
最後整合一下:
V=TA=⎡⎣⎢⎢⎢⎢rxuxdx0ryuydy0rzuzdz0−p⋅r−p⋅u−p⋅d1⎤⎦⎥⎥⎥⎥
投影矩陣
這個要比前兩個要複雜一些。先說一下這個變換時做什麼的。我們的屏幕實際上是一個2D平面,我們要顯示3D物體,就要將3D座標系投影到2D平面上。如果是正交攝像機,那麼相對簡單一點,使用降維打擊便可以解決問題,但是如果是透視攝像機,那麼就要考慮到透視問題,也就是近大遠小。
著名的「鴿子爲什麼這麼大」問題:
貼吧鏈接
透視投影
這裏我們就要引入視錐體的概念,之前土圭垚㙓數學課(二)視錐體八個頂點的計算方法簡單提到過這個視錐體。我們繼續引用文章中的圖片:
我們有下面這個等式:
⎡⎣⎢⎢⎢⎢xclipyclipzclipwclip⎤⎦⎥⎥⎥⎥=Mprojection⎡⎣⎢⎢⎢⎢xeyeyeyezeyeweye⎤⎦⎥⎥⎥⎥
投影變換的任務就是要把物體根據近大遠小的規則轉換到一個長方體。
然後轉換到設備歸一化(NDC)空間中:
⎡⎣⎢xndcyndczndc⎤⎦⎥=⎡⎣⎢⎢xclip/wclipyclip/wclipzclip/wclip⎤⎦⎥⎥
這是一個立方體空間:
從觀察空間到NDC空間,會將x軸的範圍從[l,r]壓縮到[-1,1],y軸的範圍從[b,t]壓縮到[-1,1],z軸的範圍從[n,f]壓縮到[-1,1]。所以可以認爲裁剪空間是從觀察空間到NDC空間的一箇中間狀態(除此之外,會在這個空間內做裁剪操作)。
首先考慮如何將一個點映射到視錐體近平面上:
頂視圖和右視圖
從兩張圖,我們就可以得到計算
xp
和
yp
的公式:
xpxe=−nze
xp=−n⋅xeze=n⋅xe−ze
ypye=−nze
yp=−n⋅yeze=n⋅ye−ze
因爲視圖座標系是右手座標系,而裁剪空間是左手座標系,所以才產生了這個負號。
因爲在裁剪空間到NDC空間裏會把向量的x,y,z分別處以w分量,所以投影矩陣的第四行爲(0,0,-1,0)。
⎡⎣⎢⎢⎢xcyczcwc⎤⎦⎥⎥⎥=⎡⎣⎢⎢⎢...0...0...−1...0⎤⎦⎥⎥⎥⎡⎣⎢⎢⎢xeyezewe⎤⎦⎥⎥⎥
即:
wclip=−zclip
接着,我們利用線性關係將
xp
和
yp
映射給NDC空間的
xn
和
yn
。
xn=2xpr−l−r+lr−l
yn=2ypt−b−t+bt−b
帶入
xp
和
yp
得到:
xn=(2nr−l⋅xe+r+lr−l⋅ze)/−ze
yn=(2nt−b⋅ye+t+bt−b⋅ze)/−ze
即:
xc=(2nr−l⋅xe+r+lr−l⋅ze)
yc=(2nt−b⋅ye+t+bt−b⋅ze)
於是我們得到矩陣:
⎡⎣⎢⎢⎢xcyczcwc⎤⎦⎥⎥⎥=⎡⎣⎢⎢⎢⎢⎢⎢⎢2nr−l0.002nt−b.0r+lr−lt+bt−b.−100.0⎤⎦⎥⎥⎥⎥⎥⎥⎥⎡⎣⎢⎢⎢xeyezewe⎤⎦⎥⎥⎥
雖然投影后,z值貌似沒有意義,但是爲了裁剪和深度測試,我們需要歸一化z值,另外這樣做也方便我們逆向轉換。因爲z值不依賴於x和y,所以我們可以得出下面的方程式:
zn=zc/wc=Aze+Bwe−ze
因爲在觀察空間中,
we
等於1,所以:
zn=Aze+B−ze
帶入近平面和遠平面,可得:
⎧⎩⎨−An+bn=−1−Af+bf=1→{−An+B=−n−Af+B=f→⎧⎩⎨A=−f+nf−nB=−2fnf−n
最終我們得到投影矩陣:
Mprojection=⎡⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢2nr−l00002nt−b00r+lr−lt+bt−b−f+nf−n−100−2fnf−n0⎤⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥
因爲視錐體是上下左右對稱的,也就是說r=-l且t=-b,藉此,我們可以整理一下投影矩陣:
Mprojection=⎡⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢nr0000nt0000−f+nf−n−100−2fnf−n0⎤⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥
正交投影
因爲沒有近大遠小的效果,所以
xc
和
yc
的值也就與近切面無關,而且正交投影的觀察空間本身就是長方體,所以在裁剪空間裏,我們可以直接將空間壓縮到[-1,1]的立方體內,也就不用再對w分量進行計算。於是我們就可以比較容易的推導出投影矩陣:
Morthographicprojection=⎡⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢1r00001t0000−2f−n000−f+nf−n1⎤⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥
視口矩陣
也就是從NDC空間到屏幕空間的變換,有些時候我們可以給屏幕做一個偏移量(同屏顯示)
(xoffset,yoffset)
,並設置寬度
Width
和高度
Height
,z軸還是從近平面
n
到遠平面
f
。那麼,x軸
[−1,1]→[xoffset,xoffset+Width]
,y軸
[−1,1]→[yoffset,yoffset+Height]
,z軸
[−1,1]→[n,f]
。
易得:
Mviewport=⎡⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢Width20000Height20000f−n20xoffset+Width2yoffset+Height2f+n21⎤⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥
注:
wndc=1
參考文獻:
- DirectX 9.0 3D遊戲開發編程基礎
- 3D數學基礎:圖形與遊戲開發
- OpenGL Transformation
PS:寫了這麼多累死了,早知道拆成兩三篇寫了。第一次用markdown編輯器,真TM好用,早就該用這個了。