OpenGL投影矩陣(Projection Matrix)構造方法


(翻譯,圖片也來自原文)

一、概述

絕大部分計算機的顯示器是二維的(a 2D surface)。在OpenGL中一個3D場景需要被投影到屏幕上成為一個2D圖像(image)。這稱為投影變換(參見),需要用到投影矩陣(projection matrix)。

首先,投影矩陣會把所有頂點坐標從eye coordinates(觀察空間,eye space或view space)變換到裁剪坐標(clip coordinated,屬於裁剪空間,clip space)。然后,這些裁剪坐標被變換到標准化設備坐標(normalized device coordinates, NDC,即坐標范圍在-1到1之間),這一步是通過用用裁剪坐標的\(w_c\)分量除裁剪坐標實現的

因此,我們要記住投影矩陣干了兩件事: 裁剪clipping(即frustum culling,視景體剔除)和生成NDC。下文會講述如何根據6個參數(left, right, bottom, top, near和far邊界值)來構建投影矩陣。

注意視景體剔出(也即clipping)是在裁剪坐標下完成的,是早於用\(w_c\)(即上面提到的\(w\)分量,c表示clipping)除裁剪坐標的(它會生成NDC)。裁剪坐標\(x_c, y_c, z_c\)會與\(w_c\)進行比較。如果裁剪坐標比\(-w_c\)小或者比\(w_c\)大,則丟棄這個頂點(vertex)。即經裁剪后剩余的頂點的裁剪坐標滿足:\(-w_c < x_c, y_c, z_c < w_c\)。OpenGL會成發生裁剪的地方生成新的邊,如下圖1,一個三角形經裁后,成了一個梯形,兩條紅色的邊就是裁剪后新生成的。
一個被視體裁剪的三角形
(圖1. 一個被視體裁剪的三角形)

一般常用的有透視投影和正交投影,相應地也就有兩種投影矩陣。

二、透視投影(Perspective Projection)

透視投影中的視景體和標准化設備坐標
(圖2. 透視投影中的視景體和標准化設備坐標NDC)

在透視投影中,一個3D point是在一個截頭錐體中(truncated pyramid frustum,上面圖2左圖,即一個棱台),會被映射到一個立方體(NDC坐標空間)中,x坐標范圍從[1, r]變成了[-1, 1],y坐標范圍從[b, t]變成了[-1, 1],z坐標從[-n, -f]變成了[-1, 1]。

注意在view space中(即eye coordinate),OpenGL使用的是右手坐標系(上面圖2左圖),但是在NDC中使用的是左手坐標系(上面圖2右圖)。這樣的話,在view space中camera位於坐標原點看向-z軸,而在NDC中camera是看向+z軸的。上面圖2中的n表示近裁剪面(near plane),是正值。因為glFrustum()接受的near、far的值是正的,所以在在構造投影矩陣時,要為它們取負(negate them)。

在OpenGL中,view space(又稱為eye space)中的一個3D point被投影到近裁剪面(此處用近裁剪面作投影平面,projection plane)上。下圖3和圖4顯示了eye space中的一個點\((x_e, y_e, z_e)\)是怎樣被投影成近裁剪面上的一個點\((x_p, y_p, z_p)\)

視景體的俯視圖
(圖3. 視景體的俯視圖)
視景體的左視圖
(圖4.視景體的側視圖)

從視景體的俯視圖(圖3)看,x軸坐標\(x_e\)被映射成為\(x_p\),而\(x_p\)可以根據三角形相似形計算出來:

\[\frac{x_p}{x_e}=\frac{-n}{z_e} \Longrightarrow x_p = \frac{-n\cdot x_e}{z_e}=\frac{n\cdot x_e}{-z_e} \]

從視景體的側視圖(圖4)看,可以用相似的方法計算出\(y_p\):

\[\frac{y_p}{y_e}=\frac{-n}{z_e} \Longrightarrow y_p = \frac{-n\cdot y_e}{z_e}=\frac{n\cdot y_e}{-z_e} \]

注意\(x_p\)\(y_p\)都依賴\(z_e\)並與\(-z_e\)成反比。這是構建投影矩陣的第一個線索。在eye coordinates被投影矩陣乘后,得到的裁剪坐標仍然是齊次坐標(homogeneous coordinates)。最終它需要除以裁剪坐標的w分量,才能變成標准化設備坐標(NDC)。

\[\left( \begin{matrix} x_{clip}\\ y_{clip}\\ z_{clip}\\ w_{clip} \end{matrix} \right) = M_{projection}\cdot \left( \begin{matrix} x_{eye}\\ y_{eye}\\ z_{eye}\\ w_{eye} \end{matrix} \right), \left( \begin{matrix} x_{ndc}\\ y_{ndc}\\ z_{ndc} \end{matrix} \right)=\left( \begin{matrix} \frac{x_{clip}}{w_{clip}}\\ \frac{y_{clip}}{w_{clip}}\\ \frac{z_{clip}}{w_{clip}} \end{matrix} \right) \]

因此,我們可以把裁剪坐標的w分量設置為\(-z_e\),則投影矩陣第4行變為(0, 0, -1, 0)。

\[\left( \begin{matrix} x_c\\ y_c\\ z_c\\ w_c \end{matrix} \right)=\left( \begin{matrix} \cdot & \cdot &\cdot &\cdot\\ \cdot & \cdot &\cdot &\cdot\\ \cdot & \cdot &\cdot &\cdot\\ 0 & 0 & -1 & 0 \end{matrix} \right) \left( \begin{matrix} x_e\\ y_e\\ z_e\\ w_e \end{matrix} \right), \therefore w_c=-z_e \]

接下來,我們把剛計算得到的\(x_p, y_p\)線性地(with linear relationship)映射到NDC中的\(x_n, y_n\)(這里的n表示NDC):\([l, r] \Rightarrow [-1, 1]\)\([b, t] \Rightarrow [-1, 1]\)

把xp映射到xn
(圖5. 把\(x_p\)映射到\(x_n\))

因為\(x_p\)\(x_n\)之間是線性映射關系,如圖5,所以可設兩者之間的映射函數為:

\[x_n = \frac{1-(-1)}{r-l}\cdot + \beta \]

\((x_p, x_n) = (r, l)\)代入上面方程得:

\[1 = \frac{2r}{r-l}+\beta \]

所以

\[\begin{equation} \begin{aligned} \beta&=1 - \frac{2r}{r-l}=\frac{r-l}{r-l} - \frac{2r}{r-l}\\ &=\frac{r-l-2r}{r-l}=\frac{-r-l}{r-l}=-\frac{r+l}{r-l} \end{aligned} \end{equation} \\ \therefore x_n=\frac{2x_p}{r-l}-\frac{r+l}{r-l} \]

同理,可以求出\(y_p\)\(y_n\)之間的關系表達式,如圖6及以下公式:

把yp映射到yn
(圖6.把\(y_p\)映射到\(y_n\))

\[y_n = \frac{1-(-1)}{t-b}\cdot y_p + \beta \]

用$ (y_p, y_n)=(t,1)$代入上式得

\[1 = \frac{2t}{t-b}+\beta\\ \begin{equation} \begin{aligned} \beta &= 1 - \frac{2t}{t-b} = \frac{t-b}{t-b} - \frac{2t}{t-b}\\ &=\frac{t-b-2t}{t-b}=\frac{-t-b}{t-b}=-\frac{t+b}{t-b} \end{aligned} \end{equation} \\ \therefore y_n=\frac{2y_p}{t-b}-\frac{t+b}{t-b} \]

接下來,把上上面求得的\(x_p=\frac{nx_e}{-z_e}\)\(y_p=\frac{ny_e}{-z_e}\)代入剛剛求到的線性關系式得:

\[\begin{equation} \begin{aligned} x_n &= \frac{2x_p}{r-l}-\frac{r+l}{r-l}\\ &= \frac{2\cdot \frac{n\cdot x_e}{-z_e}}{r-l}-\frac{r+l}{r-l}\\ &= \frac{2n\cdot x_e}{(r-l)(-z_e)} - \frac{r+l}{r-l}\\ &= \frac{\frac{2n}{r-l}\cdot x_e}{-z_e} - \frac{r+l}{r-l}\\ &= \frac{\frac{2n}{r-l}\cdot x_e}{-z_e} + \frac{\frac{r+l}{r-l}\cdot z_e}{-z_e}\\ &= \left. \left(\underbrace{\frac{2n}{r-l}\cdot x_e + \frac{r+l}{r-l}\cdot z_e}_{x_c}\right) \middle/ (-z_e) \right. \end{aligned} \end{equation} \]

\[\begin{equation} \begin{aligned} y_n &= \frac{2y_p}{t-b} - \frac{t+b}{t-b}\\ &= \frac{2\cdot \frac{n\cdot y_e}{-z_e}}{t-b} - \frac{t+b}{t-b}\\ &= \frac{2n\cdot y_e}{(t-b)(-z_e)} - \frac{t+b}{t-b}\\ &= \frac{\frac{2n}{t-b}\cdot y_e}{-z_e} - \frac{t+b}{t-b}\\ &= \frac{\frac{2n}{t-b}\cdot y_e}{-z_e} + \frac{\frac{t+b}{t-b}\cdot z_e}{-z_e}\\ &= \left. \left(\underbrace{\frac{2n}{t-b}\cdot y_e + \frac{t+b}{t-b}\cdot z_e}_{y_c}\right) \middle/ (-z_e) \right. \end{aligned} \end{equation} \]

注意上面剛剛求得的\(x_n, y_n\)是NDC坐標,而NDC應該是由裁剪坐標除以\(w_c\)得到,也即透視除法(perspective division), \((x_c/w_c, y_c/w_c)\)。又因為,之前我們把\(w_c\)的值設置為\(-z_e\),所以上面\(x_n, y_n\)表達式中括號里的部分表示裁剪空間的坐標\(x_c, y_c\)

加上上面的兩個方程,我們可以找到投影矩陣的第1行和第2行:

\[\begin{equation} \left( \begin{matrix} x_c\\ y_c\\ z_c\\ w_c \end{matrix} \right) =\left( \begin{matrix} \frac{2n}{r-l} & 0 & \frac{r+l}{r-l} & 0\\ 0 & \frac{2n}{t-b} & \frac{t+b}{t-b} & 0\\ \cdot & \cdot & \cdot & \cdot\\ 0 & 0 & -1 & 0 \end{matrix} \right) \left( \begin{matrix} x_e\\ y_e\\ z_e\\ w_e \end{matrix} \right) \end{equation} \]

現在矩陣只剩下第三行是待求解的。在eye space中\(z_e\)總是被投影到近裁剪面(near plane)上,即值總是為-n。但是我們為了完成裁剪(clipping)和深度測試(depth test),每一個頂點應該具有不同的z值。此外,投影變換應該是可逆的。既然我們知道z不依賴於x和y的值,那么我們就借用w分量來找到\(z_n\)\(z_e\)之間的關系。因此,我們可以指定第三行長這樣:

\[\begin{equation} \left( \begin{matrix} x_c\\ y_c\\ z_c\\ w_c \end{matrix} \right) =\left( \begin{matrix} \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{matrix} \right) \left( \begin{matrix} x_e\\ y_e\\ z_e\\ w_e \end{matrix} \right) \end{equation}, z_n=z_c/w_c=\frac{Az_e + Bw_e}{-z_e} \]

因為在eye space中,\(w_e\)總是等於1,因此:

\[z_n = \frac{Az_e + B}{-z_e} \]

(注意,\(w_c = -z_e, w_e=1\)別搞混淆了)

為了找到系數A和B,我們把\((z_e, z_n)\)之間的關系: (-n, -1)和(-f, 1),代入上面這個等式中,得到:

\[\begin{equation} \left\{ \begin{array}{lr} \frac{-An+B}{n} = -1 & \\ \frac{-Af+B}{f} = 1 & \end{array} \right. \end{equation} \\ \Downarrow \]

\[\begin{equation} \left\{ \begin{array}{lr} -An + B = -n & (1)\\ -Af + B = f & (2) \end{array} \right. \end{equation} \]

由方程(1)可得:

\[\begin{equation} \begin{array}{lr} B=An-n & (1') \end{array} \end{equation} \]

把方程(1')代入到方程(2),可解出A:

\[\begin{equation} \begin{array}{lr} -Af + (An-n) = f & (2')\\ -(f-n)A=f+n &\\ A=-\frac{f+n}{f-n}& \end{array} \end{equation} \]

把A的值代入方程(1')可求得B:

\[\begin{equation} \begin{aligned} B &=-n - \left(\frac{f+n}{f-n}\right)n=-\left(1+\frac{f+n}{f-n}\right)n\\ &= -\frac{2fn}{f-n} \end{aligned} \end{equation} \]

有了A和B,則\(z_e\)\(z_n\)之間的關系表達式為:

\[\begin{equation} \begin{aligned} z_n = \frac{-\frac{f+n}{f-n}z_e - \frac{2fn}{f-n}}{-z_e} &\quad (3) \end{aligned} \end{equation} \]

最后,完整的投影矩陣為:

\[\left( \begin{matrix} \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{matrix} \right) \]

上面這是一個通用視景體的投影矩陣。當視景體是對稱時,即r=-l, t=-b,則:

\[\begin{equation} \left\{ \begin{array}{lr} r+l=0\\ r-l=2r \end{array} \right. \end{equation} \]

\[\begin{equation} \left\{ \begin{array}{lr} t+b=0\\ t-b=2t \end{array} \right. \end{equation} \]

故投影矩陣可以簡化為:

\[\left( \begin{matrix} \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{matrix} \right) \]

透視投影矩陣我們已經求出來了,在繼續往下探討之前,請再看一下上面的方程(3),即:

\[\begin{equation} \begin{aligned} z_n = \frac{-\frac{f+n}{f-n}z_e - \frac{2fn}{f-n}}{-z_e} &\quad (3) \end{aligned} \end{equation} \]

可以看到它是一個有理函數(rational function),且是一個非線性函數。這意味着在近裁剪面(near plane)附近,它具有很高的精度(very high precision),而在遠裁剪面(far plane)附近具有非常小的精度(very little precision)。如果[-n, -f]的范圍比較大,它會造成深度值精度問題(z-fighting),即可能在離far plane比較近的地方,當\(z_e\)的值差異較小時,它們對應的\(z_n\)值相同,或者說當一個\(z_e\)值發生小的變化時,對應的\(z_n\)不受影響(即值不變)。這會產生錯誤的視覺效果。如下面圖7所示,在遠裁剪面附近,\(z_n\)的值幾乎不隨\(z_e\)發生變化。

深度緩存的精度比較)
(圖7. 深度緩存的精度比較)

一些避免z-fighting的方法:

  • 首先也是最重要的技巧是不要把物體放的太近。即使是視覺效果上貼在一塊的物體,也可以把它們稍微分開一點,只要肉眼看不到即可。
  • 把近裁剪面設置的盡可能遠。因為上面說過,離近裁剪面近的地方,精度會高。但這樣可能造成離camera很近的物體被裁剪掉。這需要大量實驗才能找到適合的距離。
  • 盡量縮短n和f之間的距離。這和上一條其實一樣。
  • 使用更高精度的depth buffer。現在一般depth bufer中depth value使用16, 24或32 bit的flotas。大部分系統使用的是24 bits的floats。因此可以改成使用32 bits的depth buffer。但這樣會增加一點性能負擔。

三、正交投影

構建正交投影矩陣相對來說會簡單一些。

正交投影視景體及對應的NDC
(圖8. 正交投影視景體及對應的NDC)

在eye space中,所有\(x_e, y_e, z_e\)分量是線性映射到NDC中的。我們只需要把一個長方體(rectangular volume)所表達的體積縮放成一個立方體(cube),並把它移動到原點(如圖8)。下面我們將使用線性映射關系(linear relationship)來找到正交投影矩陣的各個元素。

把xe映射到xn
(圖9. 把\(x_e\)映射到\(x_n\))

\[\begin{equation} \begin{aligned} x_n &= \frac{1-(-1)}{r-l}\cdot x_e + \beta\\ 1&=\frac{2r}{r-l} + \beta, (substitute (r, 1) for (x_e, x_n))\\ \beta &= 1 - \frac{2r}{r-l}=-\frac{r+l}{r-l}\\ \therefore x_n &= \frac{2}{r-l}\cdot x_e - \frac{r+l}{r-l} \end{aligned} \end{equation} \]

把ye映射到yn
(圖10. 把\(y_e\)映射到\(y_n\))

\[\begin{equation} \begin{aligned} y_n &= \frac{1-(-1)}{t-b}\cdot y_e + \beta\\ 1 &= \frac{2t}{t-b}+\beta, (substitute (t, 1) for (y_e, y_n))\\ \beta &= 1 - \frac{2t}{t-b} = -\frac{t+b}{t-b}\\ \therefore y_n &= \frac{2}{t-b}\cdot y_e - \frac{t+b}{t-b} \end{aligned} \end{equation} \]

把ze映射到zn
(圖11. 把\(z_e\)映射到\(z_n\))

\[\begin{equation} \begin{aligned} z_n &= \frac{1-(-1)}{-f-(-n)}\cdot z_e + \beta\\ 1 &=\frac{2f}{f-n} + \beta, (substitute (-f, 1) for (z_e, z_n))\\ \beta &= 1 - \frac{2f}{f-n}=-\frac{f+n}{f-n}\\ \therefore z_n &= \frac{-2}{f-n}\cdot z_e - \frac{f+n}{f-n} \end{aligned} \end{equation} \]

因為對於正交投影w分量不是必須的,所以正交投影矩陣的第4行為(0, 0, 0, 1)。因此完整的正交投影矩陣為:

\[\left( \begin{matrix} \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{matrix} \right) \]

如果視景體對稱的話,即r=-l, t=-b, 則:

\[\begin{equation} \left\{ \begin{array}{lr} r+l=0 \\ r-l=2r \end{array} \right. \end{equation} \]

\[\begin{equation} \left\{ \begin{array}{lr} t+b=0 \\ t-b=2r \end{array} \right. \end{equation} \]

故正交投影矩陣被簡化為:

\[\left( \begin{matrix} \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{matrix} \right) \]

首發於我的知乎專欄

References:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM