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

1.簡介

基本是翻譯和補充 //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{bmatrix}
\]

Tags: