寫在前面
前面幾節分別介紹了模型變換,視變換,本節繼續學習OpenGL座標變換過程中的投影變換。這裏主要是從數學角度推導投影矩陣。對數學不感興趣的,可以稍微瞭解下,或者跳過本節內容。
本文主要翻譯並整理自 songho OpenGL Projection Matrix一文,這裏對他的推導思路稍微進行了整理。
通過本節可以瞭解到
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中+z軸向內,爲左手系。
求出投影矩陣的目標就是要找到一個透視投影矩陣P使得下式成立:
投影時原先位於相機座標系中的點p=(xe,ye,ze)p=(xe,ye,ze)投影到投影平面後,得到點p′=(xp,yp,−nearVal)p′=(xp,yp,−nearVal)。具體過程如下圖所示:
需要空間想象一下,可以得出左邊的圖是俯視圖,右邊是側視圖。
利用三角形的相似性,通過俯視圖可以計算得到:
xpxe=−nzexpxe=−nze
即:xp=xen−ze(1.1)(1.1)xp=xen−ze
同理通過側視圖可以得到:
yp=yen−ze(1.2)(1.2)yp=yen−ze
由(1)(2)這個式子可以發現,他們都除以了−ze−ze這個量,並且與之成反比。這可以作爲透視除法的一個線索,因此我們的矩陣P的形式如下:
其中xpxp的映射關係如下圖所示:
則可以得到xpxp的線性關係:
xn=2r−lxp+β(1.3)(1.3)xn=2r−lxp+β
將(r,1)帶入上式得到:
β=−r+lr−lβ=−r+lr−l
帶入式子3得到:
xn=2r−lxp−r+lr−l(1.4)(1.4)xn=2r−lxp−r+lr−l
將式子1帶入式子5得到:
由式子6可以得到:
xc=2nr−lxe+r+lr−l∗ze(1.6)(1.6)xc=2nr−lxe+r+lr−l∗ze
對於ypyp的映射關係如下:
同理也可以計算得到:
由式子7和9可以得到矩陣P的前兩行和第四行爲:
由於zeze投影到平面時結果都爲−n−n,因此尋找znzn與之前的x,y分量不太一樣。我們知道znzn與x,y分量無關,因此上述矩陣P可以書寫爲:
則有:zn=Aze+Bwe−zezn=Aze+Bwe−ze,由於相機座標系中we=1we=1,則可以進一步書寫爲:
zn=Aze+B−ze(1.9)(1.9)zn=Aze+B−ze
要求出係數A,B則,利用znzn與zeze的映射關係爲:(-n,-1)和(-f,1),代入式子10得到:
A=−f+nf−nA=−f+nf−n和B=−2fnf−nB=−2fnf−n,
則znzn與zeze的關係式表示爲:
zn=−f+nf−nze−2fnf−n−ze(1.10)(1.10)zn=−f+nf−nze−2fnf−n−ze
將A,B代入矩陣P得到:
上述矩陣時一般的視見體矩陣,如果視見體是對稱的,即滿足r=−l,t=−br=−l,t=−b,則矩陣P可以簡化爲:
另外一種經常使用 的方式是通過視角(Fov),寬高比(Aspect)來指定透視投影,例如舊版中函數gluPerspective,參數形式爲:
API void gluPerspective(GLdouble fovy, GLdouble aspect, GLdouble zNear, GLdouble zFar);
其中指定fovy指定視角,aspect指定寬高比,zNear和zFar指定剪裁平面。fovy的理解如下圖所示(來自opengl 投影):
這些參數指定的是一個對稱的視見體,如下圖所示(圖片來自Working with 3D Environment):
由這些參數,可以得到:
h=near∗tan(θ2)h=near∗tan(θ2)
w=h∗aspectw=h∗aspect
對應上述透視投影矩陣中:
r=−l,r=wr=−l,r=w
t=−b,t=ht=−b,t=h
則得到透視投影矩陣爲:
相比於透視投影,正交投影矩陣的推導要簡單些,如下圖所示:
對於正交投影,有xp=xe,yp=yexp=xe,yp=ye,因而可以直接利用xexe與xnxn的映射關係:[l,−1],[r,1][l,−1],[r,1],利用yeye和ynyn的映射關係:[b,−1],[t,1][b,−1],[t,1],以及zeze和znzn的映射關係:[−n,−1],[−f,1][−n,−1],[−f,1]。例如xexe與xnxn的映射關係表示爲如下圖所示:
利用[l,−1],[r,1][l,−1],[r,1]得到:
xn=2r−lxe−r+lr−l(2.1)(2.1)xn=2r−lxe−r+lr−l
同理可得到y,z分量的關係式爲:
yn=2t−bye−t+bt−b(2.2)(2.2)yn=2t−bye−t+bt−b
zn=−2f−nze−f+nf−n(2.3)(2.3)zn=−2f−nze−f+nf−n
對於正交投影而言,w成分是不必要的,保持爲1即可,則所求投影矩陣第四行爲(0,0,0,1),w保持爲1,則NDC座標和剪裁座標相同,從而得到正交投影矩陣爲:
如果視見體是對稱的,即滿足r=−l,t=−br=−l,t=−b,則矩陣O可以簡化爲:
還可以看做把視見體的中心移動到規範視見體的中心即原點處,然後縮放視見體使得它的每條邊長度都爲2,進行這一過程的變換表示爲:
視變換是將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=fs−ns2zn+ns+fs2(3.3)(3.3)zs=fs−ns2zn+ns+fs2
則由上述式子得到視口變換矩陣爲: