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

簡介

基本是翻譯和補充 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\)之前進行視錐體剔除工作。

透視投影

在透視投影中,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.因為浮點數會存在精度問題,畢竟計算機的存儲是離散的。

正交投影

正交投影的要比透視投影簡單許多,\(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{bmatrix} \]

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

※推薦評價好的iphone維修中心

網頁設計最專業,超強功能平台可客製化

※別再煩惱如何寫文案,掌握八大原則!