OpenGL學習腳印: 投影矩陣和視口變換矩陣(math-projection and viewport matrix)

寫在前面 
前面幾節分別介紹了模型變換視變換,本節繼續學習OpenGL座標變換過程中的投影變換。這裏主要是從數學角度推導投影矩陣。對數學不感興趣的,可以稍微瞭解下,或者跳過本節內容。

本文主要翻譯並整理自 songho OpenGL Projection Matrix一文,這裏對他的推導思路稍微進行了整理。

通過本節可以瞭解到

  • 透視投影矩陣的推導
  • 正交投影矩陣的 推導
  • 視口變換矩陣的推導
  • zFighting問題

投影變換

OpenGL最終的渲染設備是2D的,我們需要將3D表示的場景轉換爲最終的2D形式,前面使用模型變換和視變換將物體座標轉換到照相機座標系後,需要進行投影變換,將座標從相機—》裁剪座標系,經過透視除法後,變換到規範化設備座標系(NDC),最後進行視口變換後,3D座標才變換到屏幕上的2D座標,這個過程如下圖所示:

座標變換

投影變換通過指定視見體(viewing frustum)來決定場景中哪些物體將可能會呈現在屏幕上。在視見體中的物體會出現在投影平面上,而在視見體之外的物體不會出現在投影平面上。投影包括很多類型,OpenGL中主要考慮透視投影(perspective projection)和正交投影( orthographic projection)。兩者之間存在很大的區別,如下圖所示(圖片來自Modern OpenGL):

投影類型

上面的圖中,紅色和黃色球在視見體內,因而呈現在投影平面上,而綠色球在視見體外,沒有在投影平面上成像。

指定視見體通過(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble nearVal, GLdouble farVal)6個參數來指定。注意在相機座標系下,相機指向-z軸,nearVal和farVal表示的剪裁平面分別爲:近裁剪平面z=nearValz=−nearVal,以及遠裁剪平面z=farValz=−farVal。推導投影矩陣,就要利用這6個參數。在OpenGL中成像是在近裁剪平面上完成。

透視投影矩陣的推導

透視投影中,相機座標系中點被映射到一個標準立方體中,即規範化設備座標系中,其中[l,r][1,1][l,r]映射到[−1,1][b,t][b,t]映射到[-1,1]中,以及[n,f][n,f]被映射到[1,1][−1,1],如下圖所示: 
視見體和NDC

注意到上面的相機座標系爲右手系,而NDC中+z軸向內,爲左手系。

我們的目標

求出投影矩陣的目標就是要找到一個透視投影矩陣P使得下式成立: 

xcyczcwc=Pxeyezewe[xcyczcwc]=P∗[xeyezewe]

xnynzn=xc/wcyc/wczc/wc[xnynzn]=[xc/wcyc/wczc/wc]

上面的除以 wclipwclip 過程被稱爲透視除法。要找到我們需要的矩陣P,我們需要利用兩個關係:

  • 投影位置xpxp,ypyp和相機座標系中點xexe,yezye之間關係。投影后對於z分量都是z_{p}=-nearVal$。
  • 利用xpxpypypxndcyndcxndc,yndc關係求出xclip,yclipxclip,yclip
  • 利用znznzeze關係得出zclipzclip

計算投影平面上的位置

投影時原先位於相機座標系中的點p=(xe,ye,ze)p=(xe,ye,ze)投影到投影平面後,得到點p=(xp,yp,nearVal)p′=(xp,yp,−nearVal)。具體過程如下圖所示: 
投影平面上的點

需要空間想象一下,可以得出左邊的圖是俯視圖,右邊是側視圖。 
利用三角形的相似性,通過俯視圖可以計算得到: 
xpxe=nzexpxe=−nze 
即:xp=xenze(1.1)(1.1)xp=xen−ze
同理通過側視圖可以得到: 
yp=yenze(1.2)(1.2)yp=yen−ze

由(1)(2)這個式子可以發現,他們都除以了ze−ze這個量,並且與之成反比。這可以作爲透視除法的一個線索,因此我們的矩陣P的形式如下: 

xcyczcwc=...0...0...1...0xeyezewe[xcyczcwc]=[............00−10]∗[xeyezewe]

也就是說 wc=zewc=−ze  
下面利用投影點和規範化設備座標的關係計算出矩陣P的前面兩行。 
對於投影平面上 xpxp 滿足 [l,r][l,r] 線性映射到 [1,1][−1,1] 對於 ypyp 滿足 [b,t][b,t] 線性映射到 [1,1][−1,1]

其中xpxp的映射關係如下圖所示:

投影點xp線性映射

則可以得到xpxp的線性關係: 
xn=2rlxp+β(1.3)(1.3)xn=2r−lxp+β
將(r,1)帶入上式得到: 
β=r+lrlβ=−r+lr−l 
帶入式子3得到: 
xn=2rlxpr+lrl(1.4)(1.4)xn=2r−lxp−r+lr−l
將式子1帶入式子5得到: 

xn=2xenrl1zer+lrl=(2xenrl+r+lrlze)ze(1.5)(1.5)xn=2xenr−l∗1−ze−r+lr−l=(2xenr−l+r+lr−l∗ze)−ze

由式子6可以得到: 
xc=2nrlxe+r+lrlze(1.6)(1.6)xc=2nr−lxe+r+lr−l∗ze

對於ypyp的映射關係如下: 
投影點yp線性映射 
同理也可以計算得到: 

yn=2yentb1zet+btb=(2yentb+t+btbze)ze(1.7)(1.7)yn=2yent−b∗1−ze−t+bt−b=(2yent−b+t+bt−b∗ze)−ze

yc=2ntbye+t+btbze(1.8)(1.8)yc=2nt−bye+t+bt−b∗ze

由式子7和9可以得到矩陣P的前兩行和第四行爲: 

xcyczcwc=2nrl0.002ntb.0r+lrlt+btb.100.0xeyezewe[xcyczcwc]=[2nr−l0r+lr−l002nt−bt+bt−b0....00−10]∗[xeyezewe]

由於zeze投影到平面時結果都爲n−n,因此尋找znzn與之前的x,y分量不太一樣。我們知道znzn與x,y分量無關,因此上述矩陣P可以書寫爲: 

xcyczcwc=2nrl00002ntb00r+lrlt+btbA100B0xeyezewe[xcyczcwc]=[2nr−l0r+lr−l002nt−bt+bt−b000AB00−10]∗[xeyezewe]

則有:zn=Aze+Bwezezn=Aze+Bwe−ze,由於相機座標系中we=1we=1,則可以進一步書寫爲: 
zn=Aze+Bze(1.9)(1.9)zn=Aze+B−ze

要求出係數A,B則,利用znznzeze的映射關係爲:(-n,-1)和(-f,1),代入式子10得到: 
A=f+nfnA=−f+nf−nB=2fnfnB=−2fnf−n 
znznzeze的關係式表示爲: 
zn=f+nfnze2fnfnze(1.10)(1.10)zn=−f+nf−nze−2fnf−n−ze
將A,B代入矩陣P得到:

P=2nrl00002ntb00r+lrlt+btb(f+n)fn1002fnfn0()(透視投影矩陣)P=[2nr−l0r+lr−l002nt−bt+bt−b000−(f+n)f−n−2fnf−n00−10]

上述矩陣時一般的視見體矩陣,如果視見體是對稱的,即滿足r=l,t=br=−l,t=−b,則矩陣P可以簡化爲: 

P=nr0000nt0000(f+n)fn1002fnfn0()(簡化的透視投影矩陣)P=[nr0000nt0000−(f+n)f−n−2fnf−n00−10]

使用Fov指定的透視投影

另外一種經常使用 的方式是通過視角(Fov),寬高比(Aspect)來指定透視投影,例如舊版中函數gluPerspective,參數形式爲:

API void gluPerspective(GLdouble fovy, GLdouble aspect, GLdouble zNear, GLdouble zFar);

其中指定fovy指定視角,aspect指定寬高比,zNear和zFar指定剪裁平面。fovy的理解如下圖所示(來自opengl 投影): 
fov

這些參數指定的是一個對稱的視見體,如下圖所示(圖片來自Working with 3D Environment): 
perspective

由這些參數,可以得到: 
h=neartan(θ2)h=near∗tan(θ2) 
w=haspectw=h∗aspect 
對應上述透視投影矩陣中: 
r=l,r=wr=−l,r=w 
t=b,t=ht=−b,t=h 
則得到透視投影矩陣爲: 

P=cot(θ2)aspect0000cot(θ2)0000(f+n)fn1002fnfn0(Fov)(Fov透視投影矩陣)P=[cot(θ2)aspect0000cot(θ2)0000−(f+n)f−n−2fnf−n00−10]

正交投影矩陣的推導

相比於透視投影,正交投影矩陣的推導要簡單些,如下圖所示: 
正交投影

對於正交投影,有xp=xe,yp=yexp=xe,yp=ye,因而可以直接利用xexexnxn的映射關係:[l,1],[r,1][l,−1],[r,1],利用yeyeynyn的映射關係:[b,1],[t,1][b,−1],[t,1],以及zezeznzn的映射關係:[n,1],[f,1][−n,−1],[−f,1]。例如xexexnxn的映射關係表示爲如下圖所示:

x分量的映射關係

利用[l,1],[r,1][l,−1],[r,1]得到:

xn=2rlxer+lrl(2.1)(2.1)xn=2r−lxe−r+lr−l
同理可得到y,z分量的關係式爲: 
yn=2tbyet+btb(2.2)(2.2)yn=2t−bye−t+bt−b
zn=2fnzef+nfn(2.3)(2.3)zn=−2f−nze−f+nf−n

對於正交投影而言,w成分是不必要的,保持爲1即可,則所求投影矩陣第四行爲(0,0,0,1),w保持爲1,則NDC座標和剪裁座標相同,從而得到正交投影矩陣爲: 

O=2rl00002tb00002fn0r+lrlt+btbf+nfn1()(正交投影矩陣)O=[2r−l00−r+lr−l02t−b0−t+bt−b00−2f−n−f+nf−n0001]

如果視見體是對稱的,即滿足r=l,t=br=−l,t=−b,則矩陣O可以簡化爲: 

O=1r00001t00002fn000f+nfn1()(簡化正交投影矩陣)O=[1r00001t0000−2f−n−f+nf−n0001]

利用平移和旋轉推導正交投影矩陣

還可以看做把視見體的中心移動到規範視見體的中心即原點處,然後縮放視見體使得它的每條邊長度都爲2,進行這一過程的變換表示爲: 

O=S(2/(rl),2/(tb),2/(nearfar))T((r+l)/2,(t+b)/2,(f+n)/2)=2rl00002tb00002nf00001100001000010r+l2t+b2f+n21=2rl00002tb00002fn0r+lrlt+btbf+nfn1O=S(2/(r−l),2/(t−b),2/(near−far))∗T(−(r+l)/2,−(t+b)/2,(f+n)/2)=[2r−l00002t−b00002n−f00001]∗[100−r+l2010−t+b2001f+n20001]=[2r−l00−r+lr−l02t−b0−t+bt−b00−2f−n−f+nf−n0001]

視口變換矩陣的推導

視變換是將NDC座標轉換爲顯示屏幕座標的過程,如下圖所示:

視口變換 
視口變化通過函數: 
glViewport(GLint sxsx , GLint sysy , GLsizei wsws , GLsizei hshs); 
glDepthRangef(GLclampf nsns , GLclampf fsfs );

兩個函數來指定。其中(sxsx,sysy)表示窗口的左下角,nsns fsfs指定遠近剪裁平面到屏幕座標的映射關係。 
使用線性映射關係如下:

(1,sx),(1,sx+ws)(x)(x分量映射關係)(−1,sx),(1,sx+ws)
(1,sy),(1,sy+hs)(y)(y分量映射關係)(−1,sy),(1,sy+hs)
(1,ns),(1,fs)(z)(z分量映射關係)(−1,ns),(1,fs)

求出線性映射函數爲: 
xs=ws2xn+sx+ws2(3.1)(3.1)xs=ws2xn+sx+ws2
ys=hs2yn+sy+hs2(3.2)(3.2)ys=hs2yn+sy+hs2
zs=fsns2zn+ns+fs2(3.3)(3.3)zs=fs−ns2zn+ns+fs2
則由上述式子得到視口變換矩陣爲: 

viewPort=ws20000hs20000fs0000fsns20
相關文章
相關標籤/搜索