《Fundamentals of Computer Graphics》第九章 圖形管線
開篇
??前幾個章節(jié)為第二種也就是基于物體順序的渲染搭好了數(shù)學(xué)的腳手架,稍微回顧一下之前的內(nèi)容,和基于像素順序的渲染不同的是,基于物體順序的渲染以幾何物體為中心,為每個幾何物體找到它能影響的像素。為每個幾何物體找到它所占據(jù)的圖像上的像素的過程就叫做光柵化(Rasterization),因此基于物體順序的渲染也被稱為光柵化渲染。需要一序列操作,從物體出發(fā)到更新圖像上的像素結(jié)束則被稱為圖形管線(Graphics Pipeline)。
??這章的標題可能讓你覺得只有一種方法來實現(xiàn)基于物體順序的渲染,其實不是這樣的。例如有兩種截然不同的圖形管線的例子,為不同的目的打造。第一種是硬件管線用于支持交互式渲染,通過OpenGL和Direct3D等API實現(xiàn)。第二種是軟件管線用于影視制作,支持RenderMan等API。硬件管線必須運行的足夠快來為游戲和可視化以及用戶界面進行實時響應(yīng)。而軟件管線必須盡可能渲染出最高質(zhì)量的動畫和視覺效果并且能應(yīng)對龐大的場景,不過這會花費更多的時間就是了。盡管是出于不同決定設(shè)計的,不過大部分圖形管線都有著非常多共同點,這一章將聚焦于這些共同點并且更貼近于硬件管線。
??基于物體順序的渲染所需要的任務(wù)可以組織成光柵化自己的任務(wù)、在光柵化之前要對幾何進行的操作、在光柵化之后要對像素進行的操作。最常見的幾何操作就是施加矩陣變換,就如同前兩章所討論的把定義在物體空間的幾何體映射到屏幕空間。對于像素來說最常見的操作是隱藏表面去除(Hidden Surface Removal),它能讓更近的表面呈現(xiàn)在觀察者面前。在每個階段也能有其它操作添加,由此可以使用相同的流程來達到許多不同的渲染效果。
??對于這一章節(jié)來說,就是討論圖形管線的四個階段,如下圖所示

從交互式應(yīng)用程序或者場景描述文件中被送進管線的幾何物體通常都是用一些頂點描述的。這些頂點會在頂點處理階段(Vertex-Processing Stage)被處理,接著使用這些頂點的圖元會被送入光柵化階段(Rasterization Stage)。光柵器會把每個圖元分解成一些片段(Fragment),每個片段對應(yīng)著被圖元覆蓋的一個像素。片段會在片段處理階段(Fragment Processing Stage)被處理,然后對應(yīng)著相同像素的不同片段會進入片段混合階段(Fragment Blending Stage)從而被組合。
光柵化(Rasterization)
??光柵化在基于物體順序的渲染中處于中心地位,而光柵器(Rasterizer)是所有圖形管線的核心。對于每個被送入的圖元,光柵器有兩個工作,首先是枚舉圖元覆蓋的像素,接著在整個圖元上插值叫做屬性(看到后面就懂了)的東西。光柵器的輸出是一些片段,每個片段對應(yīng)著圖元覆蓋的一個像素。每個片段“存活”于一個特定的像素中并且攜帶著屬于它自己的一系列屬性值。
直線繪制(Line Drawing)
??絕大多數(shù)圖形包都有一個指令能繪制有屏幕坐標的兩個端點構(gòu)成的線段。比如給定兩個端點\((1,1)\)和\((3,2)\),則會取像素\((1,1)\)和\((3,2)\)以及這兩個像素之間的一個像素作為一條線段。那么對于更加一般的端點\((x_0,y_0)\)和\((x_1,y_1)\)來說就要取一定合理數(shù)量的像素來接近理想的線段,畫這種線段得基于直線方程。直線方程有隱式形式和參數(shù)形式可選,下面介紹使用隱式形式的方法。
使用隱式直線方程繪制直線(Line Drawing Using Implicit Line Equations)
??最常用的使用隱式方程繪制直線的方法就是中點算法,而中點算法最終和布雷森漢姆算法(Bresenham Algorithm)繪制了相同的直線,不過中點算法顯得更直接一些。使用這個方法首先要找到直線的隱式方程
假設(shè)\(x_0 \leq x_1\),如果不成立交換兩個點即可。直線的斜率\(m\)為
下面的討論假設(shè)\(m \in (0,1]\),當(dāng)然了還有其它三種情況即\(m \in (-\infty,-1]\)、\(m \in (-1,0]\)、\(m \in (1,\infty)\)。對于\(m \in(0,1)\)來說水平移動比豎直升高要多,在直線上移動的點的\(x\)坐標比\(y\)坐標變化快,這個時候你有可能覺得\(y\)軸的正半軸如果朝下會讓處理變得麻煩起來,實際上代數(shù)運算并不關(guān)心\(y\)軸的朝向問題。中點算法的關(guān)鍵設(shè)想就是盡可能繪制細的沒有間隙的直線(斜著連接的兩個像素不算有間隙)。
??當(dāng)從左端點往右繪制線段時只有兩種可能,要么繪制當(dāng)前像素位置右邊的像素要么繪制當(dāng)前像素位置右上的像素,這樣每一列都會有一個像素被繪制從而保證在盡可能細的情況下無間隙,下圖展示了相同線段的不同繪制方法

對于\(m \in (0,1]\)的情況,中點算法會從最左側(cè)的像素中心位置開始,往右循環(huán)為每列挑選一個像素。基礎(chǔ)的算法應(yīng)該長這樣

要注意的是\(x\)和\(y\)都是整數(shù),這個算法最核心的地方就在分支語句這里,應(yīng)該有個十分效率的方法來判斷是否要“上升”。有個好的方法是查看兩個待選像素中心之間的中點和直線的關(guān)系。以像素中心位置\((x,y)\)來說,兩個待選像素中心為\((x+1,y)\)和\((x+1,y+1)\),中點為\((x+1,y+0.5)\),如果直線在中點上方則繪制上面的待選像素,否則繪制下面的待選像素。我們可以通過\(f(x,y)\)的取值來判斷這一關(guān)系,如果\(f(x,y) = 0\)那么中點在直線上,如果\(f(x,y)<0\)那么直線在中點上方,否則直線在中點下方。因此上面的偽代碼的分支部分就替換成了這樣

在此基礎(chǔ)上我們還可以進一步優(yōu)化算法,觀察\(f(x+1,y)\)和\(f(x+1,y+1)\)我們可以得到
上面兩個式子和\(f(x,y)\)之間實際上就相差了個常數(shù),因此我們只需要在開始時計算\(f(x_0+1,y_0+0.5)\),接著循環(huán)累加常數(shù)來繪制直線。

上面的做法可能會累積更多的數(shù)值錯誤,當(dāng)繪制一條非常長的線段時會有很多次的常量累加,不過繪制線段需要的像素一般很少超過幾千個,因此這種錯誤不是特別嚴重。
三角形光柵化(Triangle Rasterization)
??我們通常可能會想繪制由有屏幕坐標的三個二維點\(\mathbf{p}_0 = (x_0,y_0)\)、\(\mathbf{p}_1=(x_1,y_1)\)、\(\mathbf{p}_2=(x_2,y_2)\)構(gòu)成的二維三角形,這和直線繪制有相似的地方不過也有它自己的特點。和直線繪制一樣我們可能想通過頂點攜帶的數(shù)據(jù)獲得插值后的顏色或者其它屬性,如果我們有重心坐標\((\alpha,\beta,\gamma)\),三個頂點分別攜帶顏色\(\mathbf{c}_0\)、\(\mathbf{c}_1\)、\(\mathbf{c}_2\),那么可以直接得到插值后的顏色\(\mathbf{c}\)為
顏色的這一類型的插值在圖形學(xué)中被稱為高洛德插值法(Gouraud Interpolation)。和直線繪制不同的是我們可能想光柵化有著共享頂點和邊的三角形,這意味著光柵化相接的三角形從而沒有孔洞產(chǎn)生。我們可以通過中點算法來繪制每個三角形的輪廓接著填充內(nèi)部像素,不過這意味著有著共享邊的兩個三角形會覆蓋一些相同的像素,如果這兩個三角形有不同的顏色,那么渲染出的圖像會取決于這兩個三角形的繪制順序。最常用的方法是只有像素中心在三角形內(nèi)時才會繪制對應(yīng)的像素,不過僅僅這么做是不夠的,在大多數(shù)情況下像素中心的重心坐標都會在區(qū)間\((0,1)\)內(nèi),當(dāng)像素中心正好在邊上時又會引發(fā)問題,有一些方法能解決這個問題不過將在后續(xù)討論。一個關(guān)鍵的觀察是重心坐標會決定像素是否會被繪制并且確定從頂點插值后的顏色,所以問題來到了要有效率的方法找到像素中心的重心坐標,首先給出暴力光柵算法

我們可以用第二章提供的方法來計算重心坐標
此外我們還能找到一個不用遍歷圖像上所有像素的優(yōu)化方法,那就是為三角形找到一個包圍矩形,這樣就只需遍歷包圍矩形中的像素。因此可以得到如下的偽代碼

其中\(f_{ij}\)為
一個繪制有著插值顏色的三角形的例子如下

處理正好在邊上的像素(Dealing with Pixels on Triangle Edges)
??一旦像素中心正好在邊上,就沒有明顯的方法來解決這一情況。最壞的決定是不繪制這種像素,這會導(dǎo)致相接的三角形繪制出來可能有孔洞。一個更好的處理是不管這個情況讓兩個三角形都繪制一遍,但是在共享邊上的像素會取決于三角形的繪制順序。其實最好選擇其中一個三角形的頂點的屬性進行插值然后繪制像素,而這個選擇的過程一定要簡單。
??一個方法是先挑選任意一離屏的點,因為離屏的點總是在共享邊的某一側(cè),選擇三角形可以看與共享邊相對的點是否和離屏點在共享邊同側(cè),下圖是一張示例圖

點\(\mathbf{a}\)與離屏點在共享邊同側(cè),因此可以取點\(\mathbf{a}\)所屬的三角形的頂點的屬性進行插值,然后繪制共享邊上的像素。接下來可以推廣到更加一般的情況,三角形的三條邊可能都有像素正好在上面。我們就看與每一條邊相對的點是否和離屏點在每條一邊同側(cè),如果同側(cè)就繪制正好在這條邊上的像素否則不繪制,這樣就簡單地解決了這一問題。修改后的代碼如下圖所示

要注意的是對于每個三角形來說,兩個共享的頂點在繪制調(diào)用中必須有相同的順序,否則直線方程會翻轉(zhuǎn)符號,因此需要有健壯的實現(xiàn)并且需要檢查編譯器的細節(jié)和算數(shù)單元。除此之外處理像素中心正好在邊上的代碼應(yīng)該被謹慎地編寫。
透視修正的插值(Perspective Correct Interpolation)
??把位于三維空間中的三角形投影到二維圖像上進行繪制時,繼續(xù)使用上個部分的插值代碼會出現(xiàn)問題。經(jīng)過前面幾個章節(jié)的學(xué)習(xí)你可能猜出來了是透視中近大遠小的原因,上個部分的插值代碼并不能正確地反映這一特點。下圖是正確插值和不正確插值的示例

??通過前面幾章的學(xué)習(xí)你可能已經(jīng)想到了一個方法,可以首先通過逆變換把屏幕空間的坐標變換到規(guī)范視圖體內(nèi)的歸一化坐標,有了歸一化坐標接著再通過逆變換把歸一化坐標變換到世界坐標,有了逆變換求出的世界坐標再利用三個頂點攜帶的世界坐標就能求出真正的重心坐標。不過這個方法稍微有點復(fù)雜,實際上有更加便捷的方法。首先以三維直線繪制的透視修正為例,后續(xù)可直接推廣到三維三角形繪制的透視修正。
??前一個章節(jié)有個部分證明了世界空間的直線被變換到歸一化坐標空間后依然是直線,而這個部分有三個非常重要的公式
這幾個式子看起來有點怪,稍加修改后這三個式子可重寫成
因為從屏幕空間內(nèi)的坐標逆變換到規(guī)范視圖體內(nèi)的歸一化坐標只是一次線性變換,所以歸一化坐標可以在屏幕空間內(nèi)進行線性插值。因此,上三式告訴了我們在屏幕空間內(nèi)進行線性插值的歸一化坐標\(\mathbf{R}^\prime/w^\prime\),與它被逆變換到世界空間的坐標\(\mathbf{Q}^\prime\)的關(guān)系。這個時候你可能會想,如果我們能求出\(t(\alpha)\),那么就能輕易求出頂點的任意屬性插值后的結(jié)果。但是這樣不太好,對于三維三角形頂點的插值來說。首先,\(t(\alpha)\)的公式會很復(fù)雜。其次,相比于三維直線,我們要為三維三角形的兩個頂點求出正確的插值參數(shù)。這兩個缺點導(dǎo)致了較大的計算量,因此得找個更加簡單且便捷的計算方法。
??截取式\((5)\)的最后兩個部分有
稍微觀察下式\((7)\),發(fā)現(xiàn)像左側(cè)式子這種形式都可以被改寫成右側(cè)式子的形式,比如我們可以把\(\mathbf{r}\)和\(\mathbf{R}\)替換成頂點攜帶的世界空間坐標\(\mathbf{q}\)、\(\mathbf{Q}\)從而得到
因此利用式\((7)\)的性質(zhì)還有之前提到的歸一化坐標可以在屏幕空間內(nèi)進行線性插值的結(jié)論,可以得出一個非常重要的結(jié)論:除了歸一化坐標可以在屏幕空間內(nèi)進行線性插值外,頂點的任意屬性只要除以齊次坐標\(w\),就能在屏幕空間內(nèi)進行線性插值,比如式\((8)\)提到的\(\mathbf{Q}^\prime/w^\prime\)。除了把\(\mathbf{r}\)和\(\mathbf{R}\)替換成世界坐標得到式\((8)\)外,還可以把式\((7)\)的\(\mathbf{r}\)和\(\mathbf{R}\)替換成實數(shù)\(1\),從而得到\(1/w^\prime\)為
有了\(1/w^\prime\)后,我們就能直接求出頂點的任意屬性正確插值后的結(jié)果,比如我們能求出\(\mathbf{Q}^\prime\)為
假設(shè)一個三維三角形的三個頂點有世界坐標\(\mathbf{Q}_0\)、\(\mathbf{Q}_1\)、\(\mathbf{Q}_2\),變換到4D空間后的齊次坐標分別為\(w_0\)、\(w_1\)、\(w_2\),在屏幕空間內(nèi)進行線性插值求得的重心坐標為\((\alpha,\beta,\gamma)\)。我們可以得到世界坐標\(\mathbf{Q}\)的正確插值方法為
那么對于頂點的任意屬性\(\mathbf{A}\)可得到
如果我們想要插值紋理坐標\((u,v)\)并利用紋理坐標繪制三維三角形時可直接這么做

裁剪(Clipping)
??僅僅把圖元變換到屏幕空間接著再光柵化它們并不是總是有效的,這是因為圖元有可能在可視范圍外。比如在“眼睛”后的圖元如果被光柵化會導(dǎo)致錯誤,上一章提到我們使用的\(z\)坐標變換公式為
當(dāng)\(z\)大于\(0\)也就是在“眼睛”后面時\(z^\prime\)會大于\(1\),在“眼睛”后面的點會被變換到前面的一個不合理的位置,這就是問題所在。因此,在進行光柵化之前必須執(zhí)行一個叫裁剪(Clipping)的操作,這個操作用于移除圖元超出視野范圍的那一部分。
??事實上裁剪在圖形學(xué)中是很常見的操作,會在一個幾何體“切開”另外一個幾何體時用到。比如讓\(x=0\)平面裁剪一個三角形,如果這個三角形的頂點的\(x\)坐標符號相反,那么這個平面會把這個三角形剪成兩個部分。在裁剪的大部分應(yīng)用中被認為是錯誤的那一部分都會被拋棄,對于一個平面來說的裁剪操作如下圖所示

??在為光柵化做準備的裁剪操作中,被認為錯誤的是在可視體之外的那部分。那么裁剪一般要用到可視體的六個面,不過有許多系統(tǒng)只會用近平面進行裁剪。這個部分會討論基礎(chǔ)的裁剪模塊的實現(xiàn),兩個常用的方法是
-
在世界坐標空間中用六個平面包圍視錐臺進行裁剪
-
在齊次除法之前的4D空間中進行裁剪
任意一個方法都能被有效率地實現(xiàn),只要為每個三角形的裁剪跟隨下列步驟

在變換前裁剪(Clipping Before the Transform)
??這個方法看起來很直接,不過唯一的問題是如何得到6個平面。其實答案很簡單,可以通過逆變換把規(guī)范視圖體的8個角點變換到世界坐標空間。當(dāng)然了還有種方式也可以,那就是通過觀察參數(shù)例如近平面、觀察位置以及FOV等數(shù)據(jù)求出6個平面。
在四維空間中進行裁剪(Clipping in Homogeneous Coordinate)
??出人意料的是,這個方法是通常被實現(xiàn)的。在四維空間中可視體是四維的,并且會被三維體(超平面)包圍,分別為
這樣就能發(fā)現(xiàn)實際上比前一個方法一效率高。
讓平面裁剪(Clipping against a Plane)
??不論用哪種方法求出包圍平面,最后都得用求出的平面裁剪幾何體。第二章提到過點\(\mathbf{q}\)且法線為\(\mathbf{n}\)的平面的隱式方程為
這個方程通常也寫作
對于有著\(\mathbf{a}\)和\(\mathbf{b}\)兩個端點的線段來說,我們先判斷\(f(\mathbf{a})\)和\(f(\mathbf{b})\)是否異號。如果異號就說明平面與線段相交,那么就得找到交點\(\mathbf{p}\)。因為點\(\mathbf{p}\)在線段上,首先可以給出一條參數(shù)直線
求交點就令\(f(\mathbf{p})=0\),因此得到
解得\(t\)為
對于裁剪三角形來說,得在第十二章了解完圖形學(xué)中的數(shù)據(jù)結(jié)構(gòu)的時候才會進行了解。
光柵化之前和之后的操作(Operations Before and After Rasterization)
??在光柵化之前,頂點必須處于屏幕空間中,而且在圖元上被插值的屬性得是已知的。準備這些數(shù)據(jù)是圖形管線中頂點處理階段的任務(wù)。在這個階段中輸入的頂點會進行模型、觀察、投影變換,從它們位于的原始坐標空間中被映射到屏幕空間。在同一時間如果有需要的話,其它信息例如顏色、表面法線等可能也會被變換,下面將會討論這些額外的屬性。
??在光柵化后,后續(xù)的處理是為每個片段計算顏色還有深度。這些處理可以簡單到僅僅傳遞由光柵器計算的顏色和深度值,或者可以包含復(fù)雜的著色操作。最后的混合階段會結(jié)合對應(yīng)著相同像素的片段,計算出最終的顏色。最通常的混合操作是選擇深度值最小(最接近眼睛)的片段。下面將用一些例子講解這些不同的階段。
簡單的二維繪制(Simple 2D Drawing)
??最簡單的管線在頂點或片段階段以及混合階段時會什么也不做,每個片段的顏色會直接覆蓋原來的顏色。應(yīng)用程序會用屏幕坐標來提供圖元,而光柵器會包攬所有工作。這種基礎(chǔ)的安排是許多簡單的老的用于繪制用戶界面和圖表以及其它二維內(nèi)容的API的精髓。
一個極簡的三維管線(A Minimal 3D Pipeline)
??為了繪制三維中的物體,需要對二維繪制管線做的修改就是矩陣變換。在頂點處理階段把輸入的頂點的位置變換到屏幕空間,從而得到屏幕空間中的三角形,這樣可以使用用于二維圖形繪制的方法。
??一個極簡的三維管線會遇到的問題是獲得正確的遮蔽關(guān)系,近處的物體應(yīng)該顯示在遠處的物體前方。一個簡單的方法是從后往前繪制圖元,這種通常被稱為用于移除隱藏表面的畫家算法(Painter's Algorithm),和繪畫師一樣從背景一路往前繪制。不過這個方法有一些缺點,比如三角形相交的時候還有三角形之間形成遮蔽循環(huán)關(guān)系的時候就不能得出繪制順序,下圖是一個符合上述情況示例圖

除上面的缺點之外就算理論上可以找到繪制順序,使用深度值來排序圖元實際上是比較慢的,特別是對于大場景來說,這樣會拖慢原本非常快的渲染速度。
為隱藏表面使用一個z緩沖(Using a z-Buffer for Hidden Surfaces)
??在實踐中畫家算法很少被使用,反而使用的是另一個簡單的有效率的隱藏表面移除算法,這個算法被稱為z緩沖(z-Buffer)算法。方法很簡單,只需要為每個像素追蹤迄今最近的片段的距離值,那些代表著更遠表面的片段自然地就會被拋棄。要這么做得為每個像素分配一個額外的值用來存儲迄今為止最近的距離,而這個值則被稱為深度或\(z\)值。深度緩沖或z緩沖是深度值網(wǎng)格的名稱。
??z緩沖算法會在片段混合階段被實現(xiàn),通過比較片段的深度值和z緩沖當(dāng)前存儲的值。如果片段的深度更近,那么這個片段的顏色和深度值會分別覆蓋顏色緩沖和深度緩沖中對應(yīng)的值,反之這個片段會被拋棄。為了確保第一個片段總是能通過深度測試,深度緩沖一般都會用最大深度值來初始化,不管繪制順序是怎么樣的,最終都會是相同的片段通過深度測試,從而保證渲染出的圖像一致。
??z緩沖算法需要每個片段攜帶一個深度值,可以通過簡單地插值頂點的歸一化坐標的\(z\)坐標來實現(xiàn),就像其它屬性被插值一樣。
??z緩沖是個簡單以及實際的方法用于解決基于物體順序渲染中的隱藏表面問題,而且是目前處于主導(dǎo)地位的方法。它比那些把表面分成更小的片段再進行深度排序的幾何方法要簡單得多,因為它解決了任何不需要被解決的問題。它不僅被普遍的硬件圖形管線支持,而且在軟件圖形管線中也是被使用最多的。下圖展示了使用z緩沖的效果。

精度問題
??在實踐中,在z緩沖中存儲的\(z\)值一般都是非負的整數(shù),且范圍一般都在\([0.0,1.0]\)。選擇整數(shù)而不是浮點數(shù)的原因是因為z緩沖所需要的快速內(nèi)存有點昂貴,而且需要被保持在最低限度。
??不過使用整數(shù)會導(dǎo)致一些精度問題,如果我們使用有\(B\)個值的整數(shù)范圍\(\{0,1,...,B-1\}\),就能把這里面的整數(shù)一一映射到\(\{0,1/B-1,...,1\}\)。下面的討論假設(shè)\(z\)、\(n\)、\(f\)都為正,不過都為負也能有相同的結(jié)果,只是都為正討論起來更簡單。對于每個輸入的\(z\)值來說,都會先把\(z\)值舍入到最近的整數(shù)深度\(\mathrm{round}(z \cdot (B-1))\),如果小于當(dāng)前存儲的整數(shù)深度就直接替換原來存儲的值,反之則什么都不做。因此得分配足夠的比特位讓有距離的任意兩個三角形的深度舍入到不同的整數(shù)深度值,從而渲染出正確的圖像。
??打個比方來說,假設(shè)使用正交投影渲染一個場景,里面的三角形都隔著至少\(1\)米。由于使用的是正交投影,我們可以得到相機空間中的距離間隔\(\Delta z_\mathrm{cam}=(f-n)/B\),為了不產(chǎn)生錯誤那么就得讓\(\Delta z_\mathrm{cam}\)越小越好,因為間隔為\(1\)米因此可知\(\Delta z_\mathrm{cam}<1\)。有兩個方法可以讓\(\Delta z_\mathrm{cam}\)更小,首先可以讓近平面和遠平面的距離變短,然后還可以分配更多的比特位來存儲\(z\)值。
??當(dāng)渲染透視圖象時z緩沖的精度應(yīng)該被更加小心地處理,因為這里的\(\Delta z_\mathrm{cam}\)和透視除法相關(guān)。回想上一個章節(jié)再加上之前做的假設(shè)可以得到相機空間中的z坐標到歸一化空間中的z坐標的變換公式為
由上式可以得到
首先可以得到\(\Delta z_\mathrm{norm}\)為
當(dāng)\(n=0\)時\(\Delta z_\mathrm{norm}\)為極小的一個值,這就導(dǎo)致要用近乎無限的比特位來存儲深度值,這也是最壞的一個情況。此外由上式還能得到\(\Delta z_\mathrm{cam}\)為
這下還看不出什么,不過由上式可知當(dāng)\(z_\mathrm{cam}=f\)時\(\Delta z_\mathrm{cam}\)有最大值
和正交投影類似,對于透視投影來說應(yīng)該讓\(\Delta z^\mathrm{max}_\mathrm{cam}\)越小越好,這樣渲染就不容易出錯。稍微觀察上式可得出應(yīng)該最小化\(f\)最大化\(n\)。總之,為了保證精度應(yīng)該總是小心地選擇\(n\)和\(f\)的值。
逐頂點著色(Per-vertex Shading)
??目前,把三角形送入管線的應(yīng)用程序會提前設(shè)置好顏色,光柵器只需插值顏色接著直接寫入圖像。對于某些應(yīng)用場合來說是足夠的,不過在很多情況下我們也許想為三維物體的表面著色,比如使用第四章提到的光照公式進行著色。
??第一種方法是在頂點階段的時候進行著色計算,應(yīng)用會為頂點提供法線和位置以及其它的光照計算需要讓頂點攜帶的值,光源的顏色和位置則會被分開提供。對于每個頂點我們就能利用它的屬性以及光源屬性還有攝像機的位置進行光照計算,計算出來顏色后就可以讓光柵器進行插值,最后對圖像寫入插值后的顏色。這種逐頂點著色有時也被稱為高洛德著色(Gouraud Shading)。
??關(guān)于著色計算一個可以做的決定是著色在哪個坐標系統(tǒng)中進行,回顧上一個章節(jié)的內(nèi)容就知道一般有世界空間和相機空間可選。在相機空間中進行著色的優(yōu)勢是不需要追蹤相機的位置和朝向,因為在相機空間中相機永遠處于原點位置并且朝向固定。
??逐頂點著色的劣勢是它不能在著色中產(chǎn)生比用于繪制表面的圖元還要小的細節(jié),因為它只為頂點計算顏色而不是在頂點之間。比如在房間中的地板是由兩個大三角構(gòu)成的,它會被房間中的燈點亮。如果使用逐頂點著色,這個時候就會觀察到明顯的視覺瑕疵。此外彎曲的表面如果需要繪制鏡面高光,那么繪制時就必須用到足夠小的圖元來產(chǎn)生正確的高光。
??下圖展示了使用逐頂點著色繪制的兩個球體

逐片段著色(Per-fragment Shading)
??為了避免逐頂點著色帶來的插值缺陷,可以在插值后的片段階段進行著色。在逐片段著色中使用的是和逐頂點著色一樣的著色公式,不過著色公式需要的值都是從頂點插值后得到的。
??在逐片段著色中著色需要的幾何信息都是作為屬性被傳遞給光柵器,因此頂點階段必須和片段階段協(xié)調(diào)配合來恰當(dāng)?shù)販蕚鋽?shù)據(jù)。如果選擇在相機空間中進行著色計算,這個時候就可以讓光柵器插值處于相機空間的法線和頂點位置。
??下圖展示了使用逐片段著色繪制的兩個球體

紋理映射(Texture Mapping)
??紋理(Texture)是圖像,它被用于給著色的表面增加額外的細節(jié)。背后的想法很簡單,在每次著色時讀取紋理的值用于著色計算。比如漫反射顏色,我們就能從紋理中讀取而不是直接用頂點攜帶的屬性。這個讀取的操作則被稱為紋理查找(Texture Lookup),通過著色代碼聲明的紋理坐標(Texture Coordinate)也就是紋理域中的一個點,紋理映射系統(tǒng)會找到位于那個點的值然后返回它,紋理存儲的值就是這樣被用于著色計算的。
??最通常的用于定義紋理坐標的方法就是讓紋理坐標作為頂點的屬性,這樣每個圖元就知道它在紋理的哪個位置。
著色頻率(Shading Frequency)
??在哪里進行著色計算取決于顏色有多快改變,即被計算的細節(jié)的尺度。著色大尺度的細節(jié)例如在曲面的漫反射著色可以被不那么頻繁地計算然后進行插值,也就是采用低著色頻率進行計算。那些產(chǎn)生小尺度細節(jié)的著色比如尖銳的高光或者詳細的紋理,得采用高著色頻率進行計算。
??因此大尺度的細節(jié)可以被安全地在頂點階段進行計算,甚至當(dāng)定義圖元的頂點相隔了許多像素都行。那些需要高著色頻率的效果也能在頂點階段被計算,只要頂點在圖像中離得夠近。
??被用于計算機游戲的硬件管線通常會使用占據(jù)了一些像素的圖元來確保高效率,絕大多數(shù)著色計算通常都是對每個片段進行的。在另一方面,PhotoRealistic RenderMan系統(tǒng)所有的著色都是在每個頂點上計算的,在第一次細分或切割后所有表面都會變成一些只有像素大小的被稱作微多邊形(Micropolygon)的小四邊形。在這種情況下圖元會變得非常小,因此逐頂點著色在這個系統(tǒng)中能達到一個適合進行細節(jié)著色的高著色頻率。
簡單的抗走樣(Simple Antialiasing)
??如果我們僅通過判斷像素是否在圖元內(nèi)來繪制圖元,將會產(chǎn)生鋸齒狀線和三角形邊緣。事實上在這個章節(jié)中描述的能生成一系列像素的簡單的三角形光柵化算法有時候被稱為標準或走樣(Aliased)的光柵化。
??有一些不同的方法可以在光柵化應(yīng)用中進行抗走樣,比如有個方法是對于每個像素以像素中心為原點計算一定范圍內(nèi)的平均顏色,接著把結(jié)果寫入到一張新的圖像中,這樣我們就能得到一個抗走樣的圖像,這個方法被稱為方框濾波。不過使用這個方法意味著所有可繪制的實體必須要有明確定義的區(qū)域,例如下方的圖可以被認為是接近一像素寬的矩形。

??最簡單的實現(xiàn)方框濾波的方法是通過超采樣(Supersampling),即先渲染一張非常高分辨率的圖像接著再降采樣。比如我們的最終目標是一張有著1.2像素寬直線的256x256圖像,我們可以先渲染一張有著4.8像素寬直線的1024x1024圖像,接著把圖像劃分到4x4的像素組并求每組像素的平均顏色,最后把結(jié)果寫入到256x256的圖像。這其實是對真正的線框濾波圖像的一種接近,不過當(dāng)物體沒有極端地小到比像素還小的時候,這個方法效果還是不錯的。
??因為是升分辨率渲染,超采樣操作實際上是非常昂貴的。由于導(dǎo)致走樣的非常尖銳的邊緣通常在圖元邊緣出現(xiàn),因此有一個被廣泛使用的優(yōu)化方法,通過比著色更高頻率地采樣可見性來實現(xiàn)。如果在每個像素中的一些點上存儲了覆蓋值和深度值,那么盡管在只有一個顏色被計算的情況下也能能實現(xiàn)非常好的抗走樣。在像RenderMan一樣的使用逐頂點著色的系統(tǒng)中,是通過在高分辨率下進行光柵化實現(xiàn)的。因為開銷實際上不是特別昂貴,著色僅僅是為片段插值生成的顏色或能見度樣本。在硬件管線這種逐片段著色系統(tǒng)中,多重采樣抗鋸齒(Multisample Antialiasing)是通過為每個片段存儲一個單獨的顏色再加上一個覆蓋值蒙板以及一系列深度值做到的。
為了效率剔除圖元(Culling Primitives for Efficiency)
??基于物體順序的渲染的強度使它需要在一次Pass中渲染場景中的所有幾何體,這也是對于復(fù)雜場景來說的弱點。比如在一個完整的城市模型中,在一個給定的時間只有少量建筑物是可見的。我們可以調(diào)用drawcall一次性繪制所有的物體,但是這會導(dǎo)致大部分在處理幾何體上所作的工作都會被浪費掉,因為有很多幾何體會在可視建筑的后面或者在觀察者后面,因此這些幾何體實際上對最后的圖像沒有任何貢獻。
??識別以及丟棄不可見的幾何體來節(jié)約花費在處理上的時間的操作就叫剔除(Culling)。有三個通常被實現(xiàn)的剔除策略為
- 視圖體剔除:移除在視圖體之外的幾何體。
- 遮蔽剔除:移除那些可能在視圖體內(nèi)但是被更近的幾何體遮擋或遮蔽的幾何體。
- 背面剔除:移除那些相對于相機朝外的圖元。
接下來簡要地討論下視圖體剔除和背面剔除,在高性能系統(tǒng)中的剔除是個復(fù)雜的話題。
視圖體剔除(View Volume Culling)
??當(dāng)整個圖元都在視圖體外時就能被剔除,因為這種圖元被光柵化后產(chǎn)生不了任何片段。如果我們能通過一次快速的測試來確定是否要剔除大量的圖元,那么就能顯著地加快繪制速度。在另一方面,為每個圖元單獨地判斷是否在視圖體內(nèi)的開銷可能比讓光柵器來剔除的開銷要大。
??視圖體剔除也被稱為視錐臺剔除(View Frustum Culling),當(dāng)許多三角形組成了一個物體時,這個時候就能得到一個與物體相關(guān)聯(lián)的包圍體。當(dāng)包圍體完全在視圖體外時,那么組成這個物體的三角形都會在視圖體外,這個時候就能一次性剔除大量圖元。如果我們有1000個三角形被一個中心在\(\mathbf{c}\)點半徑為\(r\)的球包圍時,我們可以檢查球是否在裁剪平面外,通過下面的公式就可以做到
這個公式實際上就是檢查圓心\(\mathbf{c}\)到平面的距離是否要大于半徑\(r\)。要注意的是當(dāng)球與平面重疊時,所有三角形可能還是會在平面外,因此這實際上是一次保守的測試,有多么保守取決于球包圍物體包圍的有多好。
背面剔除(Backface Culling)
??當(dāng)多邊形模型是封閉的時候,也就是包圍一個封閉空間的時候,組成模型的多邊形的朝向一般都被認為是朝外的。繪制這種模型時,那些相對于相機朝外的多邊形都會被相對于相機朝內(nèi)的多邊形覆蓋,因此可以在一開始就剔除這種朝外的圖元。
本文來自博客園,作者:TiredInkRaven,轉(zhuǎn)載請注明原文鏈接:http://www.rzrgm.cn/TiredInkRaven/p/18990611

浙公網(wǎng)安備 33010602011771號