(一) 3D圖形渲染管線

  • 2019 年 12 月 2 日
  • 筆記

(一) 3D圖形渲染管線(學習Shader的基礎是電腦圖形學)

正文

什麼是渲染(Rendering)

渲染簡單的理解可能可以是這樣:就是將三維物體或三維場景的描述轉化為一幅二維影像,生成的二維影像能很好的反應三維物體或三維場景(如圖1):

圖1:Rendering

一.頂點變換(Vertex Transformation):

頂點變換是圖形硬體渲染管線種的第一個處理階段。頂點變換在每個頂點上執行一系列的數學操作。這些操作包括把頂點位置變換到螢幕位置以便光柵器使用,為貼圖產生紋理坐標,以及照亮頂點以決定它的顏色。

頂點變換中的一些坐標:

坐標系統:

圖4:用於頂點處理的坐標系統和變換

物體空間:

應用程式在一個被稱為物體空間(也叫模型空間)的坐標系統里指定頂點位置。當一個美工人員創建了一個物體的三維模型的時候,他選擇了一個方便的方向、比例和位置來放置模型的組成頂點。一個物體的物體空間可以與其它物體的物體空間沒有任何關係。

世界空間:

一個物體的物體空間和其它對象沒有空間上的關係。世界空間的目的是為在你的場景中的所有物體提供一個絕對的參考。一個世界空間坐標系如何建立可以任意選擇。例如:你可以決定世界空間的原點是你房間的中心。然戶,房間里的物體就可以相對房間的中心和某個比例和某個方向放置了。

建模變換:

在物體空間中指定的物體被放置到世界空間的方法要依靠建模變換。例如:你也許需要旋轉、平移和縮放一個椅子的三維模型,以使椅子可以正確地放置在你的房間的世界坐標系統里。在同一個房間中的兩把椅子可以使用同樣的三維椅子模型,但使用不同的建模變換,以使每把椅子放在房間中不同的位置。

眼空間:

最後,你要從一個特殊的視點(「眼睛」)觀看你的場景。在稱為眼空間(或視覺空間)的坐標系統里,眼睛位於坐標系統的原點。朝「上」的方向通常是軸正方向。遵循標準慣例,你可以確定場景的方向使眼睛是從z軸向下看。

視變換:

從世界空間位置到眼空間位置的變換時視變換。典型的視變換結合了一個平移把眼睛在世界空間的位置移到眼空間的原點,然後適當地旋轉眼睛。通過這樣做,視變換定義了視點的位置和方向。 我們通常把分別代表建模和視變換的兩個矩陣結合在一起,組成一個單獨的被稱為modelview的矩陣。你可以通過簡單地用建模矩陣乘以視矩陣把它們結合在一起。

剪裁空間:

當位置在眼空間以後,下一步是決定什麼位置是在你最終要渲染的影像中可見的。在眼空間之後的坐標系統被稱為剪裁空間,在這個空間中的坐標系統稱為剪裁坐標。

投影變換:

從眼空間坐標到剪裁空間的變換被稱為投影變換。投影變換定義了一個事先平截體(view frustum),代表了眼空間中物體的可見區域。只有在視線平截體中的多邊形、線段和點背光柵化到一幅圖形中時,才潛在的有可能被看得見。

標準化的設備坐標:

剪裁坐標是齊次形式<x,y,z,w>的,但我們需要計算一個二維位置(一對x和y)和一個深度值(深度值是為了進行深度緩衝,一種硬體加速的渲染可見表面的方法)。

透視除法:

用w除x,y和z能完成這項工作。生成的結果坐標被稱為標準化的設備坐標。現在所有的幾何數據都標準化為[-1,1]之間。

窗口坐標:

最後一步是取每個頂點的標準化的設備坐標,然後把它們轉換為使用像素度量x和x的最後的坐標系統。這一步驟命名為視圖變換,它為圖形處理器的光柵器提供數據。然後光柵器從頂點組成點、線段或多邊形,並生成決定最後影像的片段。另一個被稱為深度範圍變換的變換,縮放頂點的z值到在深度緩衝中使用的深度快取的範圍內。

二.圖元裝配(Primitive Assembly)和光柵化(Rasterization)

經過變換的頂點流按照順序被送到下一個被稱為圖元裝配和光柵化的階段。首先,在圖元裝配階段根據伴隨頂點序列的幾何圖元分類資訊把頂點裝配成幾何圖元。這將產生一序列的三角形、線段和點。這些圖元需要經過裁剪到可視平截體(三維空間中一個可見的區域)和任何有效地應用程式指定的裁剪平面。光柵器還可以根據多邊形的朝前或朝後來丟棄一些多邊形。這個過程被稱為挑選(culling)。

經過裁剪和挑選剩下的多邊形必須被光柵化。光柵化是一個決定哪些像素被幾何圖元覆蓋的過程。多邊形、線段和點根據為每種圖元指定的規則分別被光柵化。光柵化的結果是像素位置的集合和片段的集合。當光柵化後,一個圖元擁有的頂點數目和產生的片段之間沒有任何關係。例如,一個由三個頂點組成的三角形佔據整個螢幕,因此需要生成上百萬的片段。

片段和像素之間的區別變得非常重要。術語像素(Pixel)是影像元素的簡稱。一個像素代表幀快取中某個指定位置的內容,例如顏色,深度和其它與這個位置相關聯的值。一個片段(Fragment)是更新一個特定像素潛在需要的一個狀態。

之所以術語片段是因為光柵化會把每個幾何圖元(例如三角形)所覆蓋的像素分解成像素大小的片段。一個片段有一個與之相關聯的像素位置、深度值和經過插值的參數,例如顏色,第二(反射)顏色和一個或多個紋理坐標集。這些各種各樣的經過插值的參數是來自變換過的頂點,這些頂點組成了某個用來生成片段的幾何圖元。你可以把片段看成是潛在的像素。如果一個片段通過了各種各樣的光柵化測試(在光柵操作將做討論),這個片段將被用於更新幀快取中的像素。


三.插值、貼圖和著色

當一個圖元被光柵化為一堆零個或多個片段的時候,插值、貼圖和著色階段就在片段屬性需要的時候插值,執行一系列的貼圖和數學操作,然後為每個片段確定一個最終的顏色。除了確定片段的最終顏色,這個階段還確定一個新的深度,或者甚至丟棄這個片段以避免更新幀快取對應的像素。允許這個階段可能丟棄片段,這個階段為它接收到的每個輸入片段產生一個或不產生著過色的片段。


四.光柵操作(Raster Operations)

光柵操作階段在最後更新幀快取之前,執行最後一系列的針對每個片段的操作。這些操作是OpenGL和Direct3D的一個標準組成部分。在這個階段,隱藏面通過一個被稱為深度測試的過程而消除。其它一些效果,例如混合和基於模板的陰影也發生在這個階段。

光柵操作階段根據許多測試來檢查每個片段,這些測試包括剪切、alpha、模板和深度等測試。這些測試涉及了片段最後的顏色或深度,像素的位置和一些像素值(像素的深度值和模板值)。如果任何一項測試失敗了,片段就會在這個階段被丟棄,而更新像素的顏色值(雖然一個模板寫入的操作也許會發生)。通過了深度測試就可以用片段的深度值代替像素深度值了。在這些測試之後,一個混合操作將把片段的最後顏色和對應像素的顏色結合在一起。最後,一個幀快取寫操作用混合的顏色代替像素的顏色。

圖5顯示了光柵操作階段本身實際上也是一個流水線。實際上,所有之前介紹的階段都可以被進一步分解成子過程。

圖5:標準OpenGL和Direct3D光柵操作


五.形象化圖形流水線

圖6描寫了圖形流水線的各個階段。在本圖中,兩個三角形被光柵化了。整個過程從頂點的變換和著色開始。下一步,圖元裝配解讀那從頂點創建三角形,如虛線所示。之後,光柵用片段填充三角形。最後,從頂點得到的值被用來插值,然後用於貼圖和著色。注意僅僅從幾個頂點就產生了許多片段。

圖6:形象化圖形流水線


可編程圖形流水線

當今圖形硬體設計上最明顯的趨勢是在圖形處理器內提供更多的可編程性。圖7顯示了一個可編程圖形處理器的流水線中的頂點處理器和片元(像素)處理器。

圖7比圖2展示了更多的細節,更重要的是它顯示了頂點和片段處理被分離成可編程單元。可編程頂點處理器和片段處理器是圖形硬體中執行Vertex Shader和Pixel Shader的硬體單元。

圖7:可編程圖形流水線

總結:

我們可以把GPU的渲染管線理解為一個流程,就是我們告訴GPU一堆數據,最後得出來一副二維影像,而這些數據就包括了」視點、三維物體、光源、照明模型、紋理」等元素。

在各種圖形學的書中,渲染管線主要分為三個階段:應用程式階段、幾何階段、光柵階段。

1,應用程式階段。

這個階段相對比較好理解,就比如我們在Unity里開發了一個遊戲,其實很多底層的東西Unity都幫我們實現好了,例如碰撞檢測、視錐剪裁等等,這個階段主要是和CPU、記憶體打交道,在把該計算的都計算完以後,在這個階段的末端,這些計算好的數據(頂點坐標、法向量、紋理坐標、紋理)就會通過數據匯流排傳給圖形硬體,作為我們進一步處理的源數據。

2,幾何階段。

主要負責頂點坐標變換、光照、裁剪、投影以及螢幕映射,改階段基於GPU進行運算,在該階段的末端得到了經過變換和投影之後的頂點坐標、顏色、以及紋理坐標。簡而言之,幾何階段的主要工作就是「變換三維頂點坐標」和「光照計算」。 問題隨之而來,為什麼要變換頂點坐標?我是這麼理解的,比如你有一個三維遊戲場景,場景中的每個模型都可以用一個向量來確定它的位置,但如何讓電腦根據這些坐標把模型正確的、有層次的畫在螢幕上?這就是我們需要變換三維頂點坐標的原因,最終目的就是讓GPU可以將這些三維數據繪製到二維螢幕上。 根據頂點坐標變換的先後順序,主要有如下幾個坐標空間:Object space,模型坐標空間;World space,世界坐標空間;Eye space,觀察坐標空間;Clip and Project space,螢幕坐標空間。下圖就是GPU的整個處理流程,深色區域就是頂點坐標空間的變換流程,大家了解一下即可,我們需要關注的是每個坐標空間的具體含義和坐標空間之間轉換的方法。

2.1,從object space到world space

object space有兩層核心含義,第一,object space中的坐標值就是模型文件中的頂點值,這些值是在建立模型時得到的,例如一個.max文件,裡面包含的數據就是object space的坐標。第二,object space的坐標與其他物體沒有任何參照關係,這是object space和world space區分的關鍵。world space坐標的實際意義就有有一個坐標原點,物體跟坐標原點相比較才能知道自己的確切位置。例如在unity中,我們將一個模型導入到場景中以後,它的transform就是世界坐標。

2.2,從world space到eye space

所謂eye space,就是以攝像機為原點,由視線方向、視角和遠近平面,共同組成的一個梯形體,如下圖,稱之為視錐(viewing frustum)。近平面,是梯形體較小的矩形面,也是靠近攝像機的平面,遠平面就是梯形體較大的矩形,作為投影平面。在這個梯形體的內的數據是可見的,超出的部分會被視點去除,也叫視錐剪裁。 例如在遊戲中的漫遊功能,螢幕的內容隨攝像機的移動而變化,這是因為GPU將物體的頂點坐標從world space轉換到了eye space。

2.3,從eye space到project and clip space

eye space坐標轉換到project and clip space坐標的過程其實就是一個投影、剪裁、映射的過程。因為在不規則的視錐體內剪裁是一件非常困難的事,所以前人們將剪裁安排到一個單位立方體中進行,這個立方體被稱為規範立方體(CCV),CVV的近平面(對應視錐體的近平面)的x、y坐標對應螢幕像素坐標(左下角0、0),z代表畫面像素深度。所以這個轉換過程事實上由三步組成: (1),用透視變換矩陣把頂點從視錐體變換到CVV中; (2),在CVV內進行剪裁; (3),螢幕映射:將經過前兩步得到的坐標映射到螢幕坐標繫上。

2.4,primitive assembly(圖元裝配)和triangle setup(三角形處理)

到目前為止我們得到了一堆頂點的數據,這一步就是根據這些頂點的原始連接關係還原出網格結構。網格由頂點和索引組成,這個階段就是根據索引將頂點鏈接到一起,組成線、面單元,然後進行剪裁,如果一個三角形超出螢幕以外,例如兩個頂點在螢幕內,一個頂點在螢幕外,這時我們在螢幕上看到的就是一個四邊形,然後把這個四邊形切成兩個小的三角形。 現在我們得到了一堆在螢幕坐標上的三角形面片,這些面片是用於光柵化的。 3,光柵化階段。 經過上面的步驟之後,我們得到了每個點的螢幕坐標值,和我們需要繪製的圖元,但此時還有兩個問題: (1)螢幕坐標是浮點數,但像素是用整數來表示的,如何確定螢幕坐標值所對應的像素? (2)如何根據已確定位置的點,在螢幕上畫出線段或者三角形? 對於問題1,繪製的位置只能接近兩指定端點間的實際線段位置,例如,一條線段的位置是(10.48, 20.51),轉換為像素位置就是(10,21)。 問題2,涉及到具體的畫線和填充演算法,有興趣的話可以研究。 這個過程結束後,頂點和圖元已經對應到像素,之後的流程就是如何處理像素,即給像素賦予顏色值。 給像素賦予顏色的階段稱為Pixel Operation,是在更新幀快取之前,執行最後一系列針對每個片段的操作,其目的是計算出每個像素的顏色值。在這個階段,被遮擋的面通過一個被稱為深度測試的過程消除。 pixel operation包含下面這些流程: (1)消除遮擋面; (2)Texture operation,紋理操作,根據像素的紋理坐標,查詢對應的紋理值; (3)Blending,通常稱為alpha blending,根據目前已經畫好的顏色,與正在計算的顏色的alpha值混合,形成新的顏色。 (4)Filtering,將正在計算的顏色經過某種濾鏡後輸出。 該階段之後,像素的顏色值被寫入幀快取中。