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{bmatrix}
\]