土圭垚㙓數學課(四)空間變換

我曾經在Shader山下(十六)座標空間與轉換矩陣中介紹過,一個物體要顯示在平面上,需要經過四步空間變換(實際上是五步):
物體空間->世界空間->觀察空間->裁剪空間(->歸一化設備空間)->屏幕空間
這裏寫圖片描述
(差不多就是這個意思)
實際上,在可編程渲染管線中,我們往往只需要操作從物體空間到裁剪空間的轉換,可以用一個矩陣鏈乘M(odel)V(iew)P(rojection)表示。本文就介紹一下這三個矩陣。
索性全介紹了吧。

先簡單介紹一下變換矩陣。

常用的變換矩陣有三種:平移矩陣、旋轉矩陣和縮放矩陣。

平移矩陣:

如果一個物體在世界座標系中的位置爲 (px,py,pz) ,那麼它的平移矩陣爲:

T(p)=100001000010pxpypz1

旋轉矩陣:

繞x軸旋轉 θx ,旋轉矩陣爲:

Rx(θx)=10000cosθxsinθx00sinθxcosθx00001

繞y軸旋轉 θy ,旋轉矩陣爲:
Ry(θy)=cosθy0sinθy00100sinθy0cosθy00001

繞z軸旋轉 θz ,旋轉矩陣爲:
Rz(θz)=cosθzsinθz00sinθ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=100001000010pxpypz1

旋轉

我們只考慮矩陣的前三行和前三列即可,設爲A:

Ar=a0a3a6a1a4a7a2a5a8rxryrz=100

Au=a0a3a6a1a4a7a2a5a8uxuyuz=010

Ad=a0a3a6a1a4a7a2a5a8dxdydz=001

聯立可得:
AB=a0a3a6a1a4a7a2a5a8rxryrzuxuyuzdxdydz=100010001

那麼可以看出 A B 的逆矩陣,由於 B 是標準正交矩陣( rud ,併爲單位向量),所以其逆矩陣與其轉置矩陣相等。即:
A=B1=BT=rxuxdxryuydyrzuzdz

最後整合一下:

V=TA=rxuxdx0ryuydy0rzuzdz0prpupd1

投影矩陣

這個要比前兩個要複雜一些。先說一下這個變換時做什麼的。我們的屏幕實際上是一個2D平面,我們要顯示3D物體,就要將3D座標系投影到2D平面上。如果是正交攝像機,那麼相對簡單一點,使用降維打擊便可以解決問題,但是如果是透視攝像機,那麼就要考慮到透視問題,也就是近大遠小。
著名的「鴿子爲什麼這麼大」問題:

貼吧鏈接

透視投影

這裏我們就要引入視錐體的概念,之前土圭垚㙓數學課(二)視錐體八個頂點的計算方法簡單提到過這個視錐體。我們繼續引用文章中的圖片:
視錐體
我們有下面這個等式:

xclipyclipzclipwclip=Mprojectionxeyeyeyezeyeweye

投影變換的任務就是要把物體根據近大遠小的規則轉換到一個長方體。
然後轉換到設備歸一化(NDC)空間中:
xndcyndczndc=xclip/wclipyclip/wclipzclip/wclip

這是一個立方體空間:
ndc
從觀察空間到NDC空間,會將x軸的範圍從[l,r]壓縮到[-1,1],y軸的範圍從[b,t]壓縮到[-1,1],z軸的範圍從[n,f]壓縮到[-1,1]。所以可以認爲裁剪空間是從觀察空間到NDC空間的一箇中間狀態(除此之外,會在這個空間內做裁剪操作)。
首先考慮如何將一個點映射到視錐體近平面上:
projection projection
頂視圖和右視圖
從兩張圖,我們就可以得到計算 xp yp 的公式:
xpxe=nze
xp=nxeze=nxeze
ypye=nze
yp=nyeze=nyeze
因爲視圖座標系是右手座標系,而裁剪空間是左手座標系,所以才產生了這個負號。
因爲在裁剪空間到NDC空間裏會把向量的x,y,z分別處以w分量,所以投影矩陣的第四行爲(0,0,-1,0)。
xcyczcwc=...0...0...1...0xeyezewe

即: wclip=zclip
接着,我們利用線性關係將 xp yp 映射給NDC空間的 xn yn
xn=2xprlr+lrl
yn=2yptbt+btb
帶入 xp yp 得到:
xn=(2nrlxe+r+lrlze)/ze
yn=(2ntbye+t+btbze)/ze
即:
xc=(2nrlxe+r+lrlze)
yc=(2ntbye+t+btbze)
於是我們得到矩陣:
xcyczcwc=2nrl0.002ntb.0r+lrlt+btb.100.0xeyezewe

雖然投影后,z值貌似沒有意義,但是爲了裁剪和深度測試,我們需要歸一化z值,另外這樣做也方便我們逆向轉換。因爲z值不依賴於x和y,所以我們可以得出下面的方程式:
zn=zc/wc=Aze+Bweze
因爲在觀察空間中, we 等於1,所以:
zn=Aze+Bze
帶入近平面和遠平面,可得:
An+bn=1Af+bf=1{An+B=nAf+B=fA=f+nfnB=2fnfn

最終我們得到投影矩陣:
Mprojection=2nrl00002ntb00r+lrlt+btbf+nfn1002fnfn0

因爲視錐體是上下左右對稱的,也就是說r=-l且t=-b,藉此,我們可以整理一下投影矩陣:
Mprojection=nr0000nt0000f+nfn1002fnfn0

正交投影

因爲沒有近大遠小的效果,所以 xc yc 的值也就與近切面無關,而且正交投影的觀察空間本身就是長方體,所以在裁剪空間裏,我們可以直接將空間壓縮到[-1,1]的立方體內,也就不用再對w分量進行計算。於是我們就可以比較容易的推導出投影矩陣:

Morthographicprojection=1r00001t00002fn000f+nfn1

視口矩陣

也就是從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=Width20000Height20000fn20xoffset+Width2yoffset+Height2f+n21

注: wndc=1


參考文獻:

  1. DirectX 9.0 3D遊戲開發編程基礎
  2. 3D數學基礎:圖形與遊戲開發
  3. OpenGL Transformation

PS:寫了這麼多累死了,早知道拆成兩三篇寫了。第一次用markdown編輯器,真TM好用,早就該用這個了。