1. 程式人生 > >[OpenGL](翻譯+補充)投影矩陣的推導

[OpenGL](翻譯+補充)投影矩陣的推導

# 1.簡介 > 基本是翻譯和補充 http://www.songho.ca/opengl/gl_projectionmatrix.html 計算機顯示器是一個2D的平面,一個3D的場景要被OpenGL渲染必須被投影到2D平面上以生成2D的影象。在OpenGL中,`GL_PROJECTION`矩陣可以用來進行投影變換。首先,它將所有的頂點資料從相機座標系(eye coordinates)轉換到裁剪座標系(clip coordinates),然後通過除以裁剪空間座標的`w`值,將裁剪空間座標系轉換到歸一化裝置座標系(normalized device coordinates,NDC) 我們需要注意的一點就是,裁剪和NDC變換都通過`GL_PROJECTION`矩陣來完成。之後的文章,將會利用6個引數來構建投影矩陣,這六個引數是:left,right,bottom,top,near,far,分別為近裁剪面的左右下上邊界,近裁剪面,遠裁剪面。 視錐體剔除是在裁剪座標下進行的,在轉換到NDC座標系之前。已經變換到裁剪座標系的座標$x_c,y_c,z_c$會和$w_c$進行比較,如果裁剪座標大於$w_c$或小於$-w_c$,則頂點會被剔除,OpenGL會重建多邊形的邊。 ps.解釋一下為什麼要和$w_c$進行比較。因為NDC座標的範圍是$[-1,1]$,而裁剪座標和NDC座標之間的關係是$x_c/w_c = x_n$,所以$x_c$必須得在$[-w_c,w_c]$之間才可見,其他兩個軸同理。不是在NDC座標階段進行裁剪,是因為不可見的頂點,沒有必要在對其進行運算,會消耗資源。在作用完投影矩陣後,得到的是齊次座標,OpenGL會自動除以$w_c$,以得到笛卡爾座標,OpenGL應該是在除以$w_c$之前進行視錐體剔除工作。
# 2.透視投影 在透視投影中,1個3D的點在一個像被切了一刀的金字塔的視錐體中,此時的座標系是相機座標系,這個座標系會被對映正方體的NDC座標系中。 - $x:[l,r]->[-1,1]$ - $y:[b,t]->[-1,1]$ - $z:[-n,-f]->[-1,1]$ 相機座標系定義在右手座標系,NDC是左手座標系,所以相機朝著-Z的方向看去,而NDC朝著+Z的方向看去。因為`glFrustum()`裁剪面的引數必須為正數,所以在建立投影矩陣的時候,我們要對其進行去取反。 ps.glFrustum是opengl類庫中的函式,它是將當前矩陣與一個透視矩陣相乘,把當前矩陣轉變成透視矩陣,在使用它之前,通常會先呼叫glMatrixMode(GL_PROJECTION). `void glFrustum(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble nearVal, GLdouble farVal)`,left,right指明相對於垂直平面的左右座標位置,bottom,top指明相對於水平剪切面的下上位置,nearVal,farVal指明相對於深度剪切面的遠近的距離,兩個必須為正數。
在OpenGL中,1個3D的點將會被投影到近裁剪平面上,下圖展示了點$(x_e,y_e,z_e)$如何投影到$(x_p,y_p,z_p)$。 在視錐體的頂檢視,我們可以利用相似三角形計算$x_p$的值 $$ \frac{x_p}{x_e} = \frac{-n}{z_e}\\ x_p = \frac{-nx_e}{z_e}=\frac{nx_e}{-z_e} $$ 同理,在側檢視中,利用相似三角形計算$y_p$的值 $$ \frac{y_p}{y_e} = \frac{-n}{z_e}\\ y_p = \frac{-ny_e}{z_e}=\frac{ny_e}{-z_e} $$ 我們觀察到$x_p,y_p$都依賴於$z_e$,他們都除以$z_e$,這是第一個線索,來幫助我們構建透視投影矩陣。當相機座標系經過透視投影矩陣變換後,得到的是裁剪座標系的齊次座標,最後通過除以齊次座標的$w_c$,來得到NDC $$ \begin{bmatrix} x_c\\ y_c\\ z_c\\ w_c \end{bmatrix} =M_{projection} \begin{bmatrix} x_e\\ y_e\\ z_e\\ w_e \end{bmatrix} , \begin{bmatrix} x_n\\ y_n\\ z_n\\ \end{bmatrix} = \begin{bmatrix} x_c/w_c\\ y_c/w_c\\ z_c/w_c\\ \end{bmatrix} $$ 因此我們可以設定$w_c$的值為$-z_e$,現在投影矩陣看起來是 $$ \begin{bmatrix} x_c\\ y_c\\ z_c\\ w_c \end{bmatrix} = \begin{bmatrix} .&.&.&.\\ .&.&.&.\\ .&.&.&.\\ 0&0&-1&0\\ \end{bmatrix} \begin{bmatrix} x_e\\ y_e\\ z_e\\ w_e \end{bmatrix} $$ 接著,我們需要將$x_p,y_p$對映到$x_n,y_n$,$[l,r]->
[-1,1],[b,t]->[-1,1]$。 相當於是給定l,我要得到-1,給定r,我要得到1,這不就是給定二維平面上的兩個點,求其直線方程的問題。 $$ 令x_n = kx_p+\beta,求其斜率為\frac{1-(-1)}{r-l}=\frac{2}{r-l}\\ 帶入點(r,1),1 = \frac{2r}{r-l}+\beta\\ 化簡求得\beta=-\frac{r+l}{r-l}\\ 最終得x_n = \frac{2x_p}{r-l}-\frac{r+l}{r-l} $$ $$ 令y_n = ky_p+\beta,求其斜率為\frac{1-(-1)}{t-b}=\frac{2}{t-b}\\ 帶入點(t,1),1 = \frac{2t}{t-b}+\beta\\ 化簡求得\beta=-\frac{t+b}{t-b}\\ 最終得y_n = \frac{2y_p}{t-b}-\frac{t+b}{t-b} $$ 現在有了從$x_e,y_e$到$x_p,y_p$和從$x_p,y_p$到$x_n,y_n$,現在聯立一下就可以得到從$x_e,y_e$到$x_n,y_n$的關係表示式。 $$ x_n = \frac{2x_p}{r-l}-\frac{r+l}{r-l}\\ x_p = \frac{-nx_e}{z_e}=\frac{nx_e}{-z_e}\\ 最終可以化簡為(\underbrace{\frac{2n}{r-l}x_e+\frac{r+l}{r-l}z_e}_{x_c})/-z_e $$ 同理 $$ y_n = \frac{2y_p}{t-b}-\frac{t+b}{t-b}\\ y_p = \frac{-ny_e}{z_e}=\frac{ny_e}{-z_e}\\ 最終可以化簡為(\underbrace{\frac{2n}{t-b}y_e+\frac{t+b}{t-b}z_e}_{y_c})/-z_e $$ 現在我們的透視矩陣現在是這個樣子 $$ \begin{bmatrix} x_c\\ y_c\\ z_c\\ w_c \end{bmatrix} = \begin{bmatrix} \frac{2n}{r-l}&0&\frac{r+l}{r-l}&0\\ 0&\frac{2n}{t-b}&\frac{t+b}{t-b}&0\\ .&.&.&.\\ 0&0&-1&0\\ \end{bmatrix} \begin{bmatrix} x_e\\ y_e\\ z_e\\ w_e \end{bmatrix} $$ 現在還剩下矩陣的第三行。$z_n$和其他兩個軸的座標稍有不同,因為$z_e$總是投影到-n的近裁剪面,但是我們需要不同的z值來進行裁剪和深度測試,另外我們應該可以進行逆操作(逆變換)。因為我們知道z的值不依賴於x,y,我們借用w的值來尋找$z_n,z_e$之間的關係,因此我們指定第三行矩陣為 $$ \begin{bmatrix} x_c\\ y_c\\ z_c\\ w_c \end{bmatrix} = \begin{bmatrix} \frac{2n}{r-l}&0&\frac{r+l}{r-l}&0\\ 0&\frac{2n}{t-b}&\frac{t+b}{t-b}&0\\ 0&0&A&B\\ 0&0&-1&0\\ \end{bmatrix} \begin{bmatrix} x_e\\ y_e\\ z_e\\ w_e \end{bmatrix} $$ $$ z_n = z_c/w_c = \frac{Az_e+Bw_e}{-z_e} $$ 在相機座標系中,$w_e$的值是1,因此有$z_n = \frac{Az_e+B}{-z_e}$,為了獲得A和B的值,我們使用$(z_e,z_n)$的關係,$(-n,-1),(-f,1)$,然後將他們代入表示式。 $$ \frac{-An+B}{n}=-1\\ \frac{-Af+B}{f}=1 $$ 聯立,這是一個簡單二元一次方程組,容易求得 $$ A = -\frac{f+n}{f-n}\\ B = -\frac{2fn}{f-n} $$ 所以最終得到 $$ z_n = \frac{-\frac{f+n}{f-n}z_e--\frac{2fn}{f-n}}{-z_e} $$ 最終整個投影矩陣的表示式為 $$ \begin{bmatrix} x_c\\ y_c\\ z_c\\ w_c \end{bmatrix} = \begin{bmatrix} \frac{2n}{r-l}&0&\frac{r+l}{r-l}&0\\ 0&\frac{2n}{t-b}&\frac{t+b}{t-b}&0\\ 0&0&-\frac{f+n}{f-n}&-\frac{2fn}{f-n}\\ 0&0&-1&0\\ \end{bmatrix} \begin{bmatrix} x_e\\ y_e\\ z_e\\ w_e \end{bmatrix} $$ 這個投影矩陣是一般的視錐體,如果是對稱的話,有$r=-l,t=-b$,那麼有 $$ r+l=0,r-l=2r(width)\\ t+b=0,t-b=2t(height) $$ 最後矩陣可以簡單的化為 $$ \begin{bmatrix} \frac{n}{r}&0&0&0\\ 0&\frac{n}{t}&0&0\\ 0&0&-\frac{f+n}{f-n}&-\frac{2fn}{f-n}\\ 0&0&-1&0\\ \end{bmatrix} $$ 注意觀察$z_e,z_n$的關係式,這是一個非線性的反比例函式,這意味著,在近裁剪平面的是很好,精度很高,而在遠裁剪面的時候,精度很低。當$[-n,-f]$很大時,可能導致深度精度問題(z-fighting),一個較小的$z_e$的變化,在遠裁剪面可能不會影響$z_n$的值,n和f之間的距離應該短一些,從而最小化這個問題。 ps.因為浮點數會存在精度問題,畢竟計算機的儲存是離散的。 # 3.正交投影 正交投影的要比透視投影簡單許多,$x_e,y_e,z_e$相機座標系將會線性對映到NDC座標系。我們僅需要將長方體變為正方體,然後移動至原點。 $$ x_n = \frac{1-(-1)}{r-l}x_e+\beta\\ 代入(r,1),最終可得\\ x_n = \frac{2}{r-l}x_e-\frac{r+l}{r-l} $$ 同理 $$ y_n = \frac{1-(-1)}{t-b}y_e+\beta\\ 代入(t,1),最終可得\\ y_n = \frac{2}{t-b}y_e-\frac{t+b}{t-b} $$ 同理 $$ z_n = \frac{1-(-1)}{-f-(-n)}z_e+\beta\\ 代入(-f,1),最終可得\\ z_n = \frac{-2}{f-n}z_e-\frac{f+n}{f-n} $$ 因為w的值在正交投影中不必要,所以我們設定為1,因此正交投影矩陣為 $$ \begin{bmatrix} \frac{2}{r-l}&0&0&-\frac{r+l}{r-l}\\ 0&\frac{2}{t-b}&0&-\frac{t+b}{t-b}\\ 0&0&-\frac{2}{f-n}&-\frac{f+n}{f-n}\\ 0&0&0&1\\ \end{bmatrix} $$ 同透視投影一樣,如果是對稱的話,那麼就可以矩陣就可以變簡單 $$ \begin{bmatrix} \frac{1}{r}&0&0&0\\ 0&\frac{1}{t}&0&0\\ 0&0&-\frac{2}{f-n}&-\frac{f+n}{f-n}\\ 0&0&0&1\\ \end{bma