【《Real-Time Rendering 3rd》 提煉總結】(三) 第三章 GPU渲染管線與可程式設計著色器
這篇文章是解析計算機圖形學界“九陰真經總綱”一般存在的《Real-Time Rendering 3rd》系列文章的第三篇。將帶來RTR3第三章內容“Chapter 3 The Graphics Processing Unit 圖形處理器”的總結、概括與提煉。
這章的主要內容是介紹GPU渲染管線的組成,以及可程式設計著色的進化史,頂點、幾何、畫素三種可程式設計著色器。
本文總字數7.3k,分為全文內容思維導圖、原書核心內容分章節提煉、本章內容提煉總結三個部分來呈現。
一、全文內容思維導圖
1.章節框架思維導圖
2. GPU渲染管線流程圖
其中:
- 綠色的階段都是完全可程式設計的。
- 黃色的階段可配置,但不可程式設計。
- 藍色的階段完全固定。
二、原書核心內容分節提煉
3.1 GPU管線概述
- 第一個包含頂點處理,面向消費者的圖形晶片(NVIDIA GeForce256)釋出於1999年,且NVIDIA提出了圖形處理單元(Graphics Processing Unit ,GPU)這一術語,將GeForce256和之前的只能進行光柵化處理的圖形晶片相區分。在接下來的幾年中,GPU從可配置的固定功能管線演變到了支援高度可程式設計的管線。直到如今,各種可程式設計著色器依然是控制GPU的主要手段。為了提高效率,GPU管線的一部分仍然保持著可配置,但不是可程式設計的,但大趨勢依然是朝著可程式設計和更具靈活性的方向在發展。
- GPU實現了第二章中描述的幾何和光柵化概念管線階段。其被分為一些不同程度的可配置性和可程式設計性的硬體階段。如圖3.1 所示
圖3.1 GPU實現的渲染管線
上圖中,不同顏色的階段表示了該階段不同屬性。其中:
- 綠色的階段都是完全可程式設計的。
- 黃色的階段可配置,但不可程式設計。
- 藍色的階段完全固定。
需要注意,GPU實現的渲染管線和第二章中描述的渲染管線的功能階段在結構上略有不同。以下是對GPU渲染管線的一個流程概覽:
- 頂點著色器(The Vertex Shader)是完全可程式設計的階段,頂點著色器可以對每個頂點進行諸如變換和變形在內的很多操作,提供了修改/建立/忽略頂點相關屬性的功能,這些頂點屬性包括顏色、法線、紋理座標和位置。頂點著色器的必須完成的任務是將頂點從模型空間轉換到齊次裁剪空間。
- 幾何著色器(The Geometry Shader)位於頂點著色器之後,允許GPU高效地建立和銷燬幾何圖元。幾何著色器是可選的,完全可程式設計的階段,主要對圖元(點、線、三角形)的頂點進行操作。幾何著色器接收頂點著色器的輸出作為輸入,通過高效的幾何運算,將資料輸出,資料隨後經過幾何階段和光柵化階段的其他處理後,會發送給片段著色器。
- 裁剪(Clipping)屬於可配置的功能階段,在此階段可選執行的裁剪方式,以及新增自定義的裁剪面。
- 螢幕對映(Screen Mapping)、三角形設定(Triangle Setup)和三角形遍歷(Triangle Traversal)階段是固定功能階段。
- 畫素著色器(Pixel Shader,Direct3D中的叫法)常常又稱為片斷著色器,片元著色器(Fragment Shader,OpenGL中的叫法),是完全可程式設計的階段,主要作用是進行畫素的處理,讓複雜的著色方程在每一個畫素上執行。
- 合併階段(The Merger Stage)處於完全可程式設計和固定功能之間,儘管不能程式設計,但是高度可配置,可以進行一系列的操作。其除了進行合併操作,還分管顏色修改(Color Modifying),Z緩衝(Z-buffer),混合(Blend),模板(Stencil)和相關快取的處理。
隨著時間的推移,GPU管線已經遠離硬編碼的運算操作,而朝著提高靈活性和控制性改進。程式設計著色器的引入是這個進化的最重要的一步。
3.2 可程式設計著色模型
- 現代著色階段(比如支援shader model 4.0,DirectX 10以及之後)使用了通用著色核心(common-shader core),這就表明頂點,片段,幾何著色器共享一套程式設計模型。
- 早期的著色模型可以用匯編語言直接程式設計,但DX10之後,彙編就只在除錯輸出階段可見,改用高階著色語言。
- 目前的著色語言都是C-like的著色語言,比如HLSL,CG和GLSL,其被編譯成獨立於機器的組合語言語言,也稱為中間語言(IL)。這些組合語言在單獨的階段,通常是在驅動中,被轉化成實際的機器語言。這樣的安排可以相容不同的硬體實現。這些組合語言可以被看做是定義一個作為著色語言編譯器的虛擬機器。這個虛擬機器是一個處理多種型別暫存器和資料來源、預編了一系列指令的處理器。
- 著色語言虛擬機器可以理解為一個處理多種型別暫存器和資料來源、預編了一系列指令的處理器。考慮到很多圖形操作都使用短向量(最高四位),處理器擁有4路SIMD(single-instruction multiple-data,單指令多資料)相容性。每個暫存器包含四個獨立的值。32位單精度浮點的標量和向量是其基本資料型別;也隨後支援32位整型。浮點向量通常包含資料如位置(xyzw),法線,矩陣行 ,顏色(rgba),或者紋理座標(uvwq)。而整型通常用來表示,計數器,索引,或者位掩碼。也支援綜合資料型別比如結構體,陣列,和矩陣。而為了便於使用向量,向量操作如調和(swizzling,也就是向量分量的重新排序或複製),和遮蔽(masking,只使用指定的向量元素),也能夠支援。虛擬機器的輸入和輸出見圖3.2。
圖3.2 DX 10下的通用Shader核心虛擬機器架構以及暫存器佈局。每個資源旁邊顯示最大可用編號。其中,用兩個斜槓分開的三個數值,分別是頂點,幾何、畫素著色器對應的可用最大值。
- 一個繪製呼叫(也就是喜聞樂見的Draw Call)會呼叫圖形API來繪製一系列的圖元,會驅使圖形管線的執行。
- 每個可程式設計著色階段擁有兩種型別的輸入:
- uniform輸入,在一個draw call中保持不變的值(但在不同draw call 之間可以更改);
- varying 輸入,shader裡對每個頂點和畫素的處理都不同的值。紋理是特殊型別的uniform輸入,曾經一直是一張應用到表面的彩色圖片,但現在可以認為是儲存著大量資料的陣列。
- 在現代GPU上 ,圖形運算中常見的運算操作執行速度非常快。通常情況下,最快的操作是標量和向量的乘法和加法,以及他們的組合,如乘加運算(multiply- add)和點乘 (dot-product)運算。其他操作,比如倒數(reciprocal), 平方根(square root),正弦(sine),餘弦(cosine),指數(exponentiation)、對數(logarithm)運算,往往會稍微更加昂貴,但依然相當快捷。紋理操作非常高效,但他們的效能可能受到諸如等待檢索結果的時間等因素的限制。
- 著色語言表示出了大多數場常見的操作(比如加法和乘法通過運算子+和*來表示)。其餘的操作用固有的函式,比如atan() , dot() , log(),等。更復雜的操作也存在內建函式,比如向量歸一化(vector normalization)、反射(reflection)、叉乘(cross products)、矩陣的轉置(matrix transpose)和行列式(determinant)等。
- 流控制(flow control)是指使用分支指令來改變程式碼執行流程的操作。這些指令用於實現高階語言結構,如“if”和“case”語句,以及各種型別的迴圈。Shader支援兩種型別的流控制。靜態流控制(Static flow control)是基於統一輸入的值的。這意味著程式碼的流在呼叫時是恆定的。靜態流控制的主要好處是允許在不同的情況下使用相同的著色器(例如,不同數量的光源)。動態流控制(Dynamic flow control)基於不同的輸入值。但動態流控制 遠比靜態流量控制更強大但同時也需更高的開銷,特別是在呼叫shader之間,程式碼流不規律改變的時候。正如18.4.2節中討論的,評估一個shader的效能,是評估其在一段時間內處理頂點或畫素的個數。如果流對某些元素選擇“if”分支,而對其他元素選擇“else”分支,這兩個分支必須對所有元素進行評估(並且每個元素的未使用分支將被丟棄)。
- Shader程式可以在程式載入或執行時離線編譯。和任何編譯器一樣,有生成不同輸出檔案和使用不同優化級別的選項。一個編譯過的Shader作為字串或者文字來儲存,並通過驅動程式傳遞給GPU。
3.3 可程式設計著色的進化史
可程式設計著色的框架的思想可以追溯到1984年Cook的 shade trees。一個簡單的Shader以及其對應的shade tree如圖3.3。
原書圖3.3 一個簡單的銅著色器(Cook的 shade trees)和其對應的著色語言程式碼
- RenderMan 著色語言(RenderManShading Language)是從80年代中後期根據這個可程式設計著色的框架思想開發出來的,目前仍然廣泛運用於電影製作的渲染中。在GPU原生支援可程式設計著色之前,有一些通過多個渲染通道實現實時可程式設計著色的嘗試。
- 在1999年,《雷神III:競技場》指令碼語言是在這個領域上第一個成功廣泛商用的語言。
- 在2000年,Peery等人,描繪了一個將RnederMan Shader翻譯成在多通道上並且執行在圖形硬體上的系統。
- 在2001年,NVIDIA’sGeForce 3 釋出,它是第一個支援可程式設計頂點著色器的GPU,向DirectX 8.0開放,並以擴充套件的形式提供給OpenGL。
- 2002年, DirectX 9.0釋出,裡面包括了Shader Model 2.0(以及其擴充套件版本2.X),Shader Model 2.0 支援真正的可程式設計頂點和畫素著色。著色程式語言 HLSL也是隨著DirectX 9.0的釋出而問世。
- 2004年,Shader Model 3.0推出。SM3.0是一個增量改進,將之前的可選功能進行了實現,進一步增加資源上限,並在頂點著色器中添加了有限的紋理讀取支援。新一代主機的問世,2005 (Microsofts Xbox 360),2006 (Sony Play Station),它們配備配備了Shader Model 3.0級別的GPU。
- 2006年底 ,任天堂釋出了搭載固定功能管線GPU的Wii主機 ,固定功能管線,並沒有像預想中的會完全死亡(其實一直活得很好)。
圖3.4 一個名為“mental mill”的視覺化著色器設計系統。各種操作都封裝在功能窗中,位於左側。
每個功能窗都有可調引數,每個功能框的輸入和輸出彼此連線,形成最終結果。
- 2007年,Shader Model4.0釋出,包含於DirectX 10.0中,OpenGL以擴充套件的方式支援。SM 4.0新增了幾個著色器和流輸出等新特性。SM 4.0包括支援所有Shader型別(頂點、幾何、畫素著色器)的一套統一著色模型(uniform programming model)
3.3.1 著色模型對比 Comparison ofShader Models
下表3.1比較了各種著色模型的能力。在這個表格中,VS”代表頂點著色器和“PS”代表畫素著色器(而著色模型4.0 引入了幾何著色器,其能力與頂點著色相似)。如果沒有出現“VS”和“PS”,那麼該行適用於頂點和畫素著色器。
表3.1 不同版本的著色模型能力對比,按DirectX著色模型版本列出
3.4 頂點著色器 Vertext Shader
- 頂點著色器(The Vertex Shader)的功能於2001年首次在DirectX 8中引入。由於它是流水線上的第一個階段,可選是在GPU還是CPU上實現。而在CPU上實現的話,需將CPU中的輸出資料傳送到GPU進行光柵化。目前幾乎所有的GPU都支援頂點著色。
- 頂點著色器是完全可程式設計的階段,是專門處理傳入的頂點資訊的著色器,頂點著色器可以對每個頂點進行諸如變換和變形在內的很多操作。頂點著色器一般不處理附加資訊,也就是說,頂點著色器提供了修改,建立,或者忽略與每個多邊形頂點相關的值的方式,例如其顏色,法線,紋理座標和位置。通常,頂點著色器程式將頂點從模型空間(Model Space)變換到齊次裁剪空間(Homogeneous Clip Space),並且,一個頂點著色器至少且必須輸出此變換位置。
- 值得注意的是,在這個頂點著色階段之前發生了一些資料操作。比如在DirectX中叫做輸入裝配(Input Assembler)的階段,會將一些資料流組織在一起,以形成頂點和基元的集合,傳送到管線。
- 頂點著色器本身與前面3.2節所述的通用核心虛擬機器(Common-Shader Core Virtual Machine)非常相似。傳入的每個頂點由頂點著色器程式處理,然後輸出一些在三角形或直線上進行插值後獲得的值。頂點著色器既不能建立也不能消除頂點,並且由一個頂點生成的結果不能傳遞到另一個頂點。由於每個頂點都被獨立處理,所以GPU上的任何數量的著色器處理器都可以並行地應用到傳入的頂點流上。
- 頂點著色器的輸出可以以許多不同的方式來使用,通常是隨後用於每個例項三角形的生成和光柵化,然後各個畫素片段被髮送到畫素著色器,以便繼續處理。而在Shader Model 4.0中,資料也可以傳送到幾何著色器(Geometry Shader)或輸出流(Streamed Output)或同時發動到畫素著色器和幾何著色器兩者中。
原書圖3.5 左圖,一個普通茶壺。中圖,經過頂點著色程式執行的簡單剪下(shear)操作產生的茶壺。
右圖,通過噪聲(noise)產生的發生形變的茶壺。
3.5 幾何著色器 The Geometry Shader
- 幾何著色器(Geometry Shader)是頂點和片段著色器之間一個可選的著色器,隨著2006年底 DirectX10的釋出被加入到硬體加速圖形管線中。幾何著色器作為Shader Model 4.0的一部分,不能早期著色模型(<=SM 3.0)中使用。
- 幾何著色器的輸入是單個物件及物件相關的頂點,而物件通常是網格中的三角形,線段或簡單的點。另外,擴充套件的圖元可以由幾何著色器定義和處理。如圖3.6。
原書圖3.6 幾何著色器程式的輸入是一個單獨的型別:點,線段,三角形。兩個最右邊的圖元,包括與線和三角形物件相鄰的頂點也可被使用。
- 幾何著色器可以改變新傳遞進來的圖元的拓撲結構,且幾何著色器可以接收任何拓撲型別的圖元,但是隻能輸出點、折線(line strip)和三角形條(triangle strips)。
- 幾何著色器需要圖元作為輸入,在處理過程中他可以將這個圖元整個丟棄或者輸出一個或更多的圖元(也就是說它可以產生比它得到的更多或更少的頂點)。這個能力被叫做幾何增長(growing geometry)。如上所述,幾何著色器輸出的形式只能是點,折線和三角形條。
- 當我們未新增幾何著色器時,預設的行為是將輸入的三角形直接輸出。我們添加了幾何著色器之後,可以在幾何著色器中修改輸出的圖形,我們可以輸出我們想要輸出的任何圖形。
圖 3.7.一些幾何著色器的應用。左圖,使用幾何著色器實現元球的等值面曲面細分(metaball isosurface tessellation)。
中圖,使用了幾何著色器和流輸出進行線段細分的分形(fractal subdivision of line segments)。
右圖,使用頂點和幾何著色器的流輸出進行布料模擬。(圖片來自NVIDIA SDK 10的示例)
3.5.1 流輸出 Stream Output
- GPU的管線的標準使用方式是傳送資料到頂點著色器,然後對所得到的三角形進行光柵化處理,並在畫素著色器中處理它們。 資料總是通過管線傳遞,無法訪問中間結果。流輸出的想法在Shader Model 4.0中被引入。在頂點著色器(以及可選的幾何著色器中)處理頂點之後,除了將資料傳送到光柵化階段之外,也可以輸出到流,也就是一個有序陣列中進行處理。事實上,可以完全關掉光柵化,然後管線純粹作為非圖形流處理器來使用。以這種方式處理的資料可以通過管線回傳,從而允許迭代處理。如第10.7節所述,這種操作特別適用於模擬流動的水或其他粒子特效。
3.6 畫素著色器 Pixel Shader
- 畫素著色器(Pixel Shader,Direct3D中的叫法),常常又稱為片斷著色器,片元著色器(Fragment Shader, OpenGL中的叫法),用於進行逐畫素計算顏色的操作,讓複雜的著色方程在每一個畫素上執行。如圖3.1 GPU渲染管線所示,畫素著色器是光柵化階段的主要步驟之一。在頂點和幾何著色器執行完其操作之後,圖元會被裁剪、螢幕對映,結束幾何階段,到達光柵化階段,在光柵化階段中先經歷三角形設定和三角形遍歷,之後來到畫素著色階段。
- 畫素著色器常用來處理場景光照和與之相關的效果,如凸凹紋理對映和調色。名稱片斷著色器似乎更為準確,因為對於著色器的呼叫和螢幕上畫素的顯示並非一一對應。舉個例子,對於一個畫素,片斷著色器可能會被呼叫若干次來決定它最終的顏色,那些被遮擋的物體也會被計算,直到最後的深度緩衝才將各物體前後排序。
- 需要注意,畫素著色程式通常在最終合併階段設定片段顏色以進行合併,深度值也可以由畫素著色器修改。模板緩衝( stencil buffer )值是不可修改的,而是將其傳遞到合併階段(merge stage)。在SM 2.0以及以上版本,畫素著色器也可以丟棄(discard )傳入的片段資料,即不產生輸出。這樣的操作會消耗效能,因為通常在這種情況下不能使用由GPU執行的優化 。詳見第18.3.7節。 諸如霧計算和alpha測試的操作已經從合併操作轉移到SM 4.0 中的畫素著色器裡計算。
- 可以發現,頂點著色程式的輸出,在經歷裁剪、螢幕對映、三角形設定、三角形遍歷後,實際上變成了畫素著色程式的輸入。在Shader Model 4.0中,共有16個向量(每個向量含4個值)可以從頂點著色器傳到畫素著色器。當使用幾何著色器時,可以輸出32個向量到畫素著色器中。畫素著色器的追加輸入是在Shader Model 3.0中引入的。例如,三角形的哪一面是可見的是通過輸入標誌來加入的。這個值對於在單個通道中的正面和背面渲染不同材質十分重要。而且畫素著色器也可以獲得片段的螢幕位置。
3.7 合併階段 The Merging Stage
作為光柵化階段名義上的最後一個階段,合併階段(The Merging Stage)是將畫素著色器中生成的各個片段的深度和顏色與幀緩衝結合在一起的地方。這個階段也就是進行模板緩衝(Stencil-Buffer)和Z緩衝(Z-buffer)操作的地方。最常用於透明處理(Transparency)和合成操作(Compositing)的顏色混合(Color Blending)操作也是在這個階段進行的。
雖然合併階段不可程式設計,但卻是高度可配置的。在合併階段可以設定顏色混合來執行大量不同的操作。最常見的是涉及顏色和Alpha值的乘法,加法,和減法的組合。其他操作也是可能的,比如最大值,最小值以及按位邏輯運算。
3.8 效果 Effect
- GPU渲染管線中的可程式設計階段有頂點、幾何和畫素著色器三個部分,他們需要相互結合在一起使用。正因如此,不同的團隊研發出了不同的特效語言,例如HLSL FX,CgFX,以及COLLADA FX,來將他們更好的結合在一起。
- 一個效果檔案通常會包含所有執行一種特定圖形演算法的所有相關資訊,而且通常定義一些可被應用程式賦值的全域性引數。例如,一個單獨的effect file可能定義渲染塑料材質需要的vs(頂點著色器)和ps(畫素著色器),它可能暴露一些引數例如塑料顏色和粗糙度,這樣渲染每個模型的時候可以改變效果而僅僅使用同一個特效檔案。一個效果檔案中能儲存很多techniques。這些techniques通常是一個相同effect的變體,每種對應於一個不同的Shader Model(SM2.0,SM3.0等等)。如圖3.9,使用Shader實現的效果。
原書圖3.9 可程式設計著色器可以實現各種材料和後處理效果
三、本章內容提煉總結
以下是對《Real Time Rendering 3rd》第二章“圖形渲染管線”內容概括總結,有必要再次放出這張圖:
圖 GPU實現的渲染管線
上圖中,不同顏色的階段表示了該階段不同屬性。其中:
- 綠色的階段都是完全可程式設計的。
- 黃色的階段可配置,但不可程式設計。
- 藍色的階段完全固定。
對每個階段的分別概述:
- 頂點著色器(The Vertex Shader)是完全可程式設計的階段,頂點著色器可以對每個頂點進行諸如變換和變形在內的很多操作,提供了修改/建立/忽略頂點相關屬性的功能,這些頂點屬性包括顏色、法線、紋理座標和位置。頂點著色器的必須完成的任務是將頂點從模型空間轉換到齊次裁剪空間。
- 幾何著色器(The Geometry Shader)位於頂點著色器之後,允許GPU高效地建立和銷燬幾何圖元。幾何著色器是可選的,完全可程式設計的階段,主要對圖元(點、線、三角形)的頂點進行操作。幾何著色器接收頂點著色器的輸出作為輸入,通過高效的幾何運算,將資料輸出,資料隨後經過幾何階段和光柵化階段的其他處理後,會發送給片段著色器。
- 裁剪(Clipping)屬於可配置的功能階段,在此階段可選執行的裁剪方式,以及新增自定義的裁剪面。
- 螢幕對映(Screen Mapping)、三角形設定(Triangle Setup)和三角形遍歷(Triangle Traversal)階段是固定功能階段。
- 畫素著色器(Pixel Shader,Direct3D中的叫法)常常又稱為片斷著色器,片元著色器(Fragment Shader,OpenGL中的叫法),是完全可程式設計的階段,主要作用是進行畫素的處理,讓複雜的著色方程在每一個畫素上執行。
- 合併階段(The Merger Stage)處於完全可程式設計和固定功能之間,儘管不能程式設計,但是高度可配置,可以進行一系列的操作。其除了進行合併操作,還分管顏色修改(Color Modifying),Z緩衝(Z-buffer),混合(Blend),模板(Stencil)和相關快取的處理。
圖片來自《巫師3:狂獵》。