首先,GPU 的渲染流程就是一個程序,該程序是由多個著色器組成。著色器本身也是一個程序,所以需要先進行編寫、編譯,然后再鏈接到渲染程序中,多個著色器鏈接之后生成最終的渲染程序。
GPU 本身是高并發(fā)設(shè)計,在渲染管線進行時,各個小的著色器可以并發(fā)執(zhí)行。比如在頂點輸入階段,輸入了 10 個頂點,可能就有 10 個著色器程序同時執(zhí)行并輸出結(jié)果。
至于在同一個渲染管線內(nèi),不同階段之間是否能夠并發(fā),這個取決于 GPU 是 Tiled Based Render 還是 ???比如,頂點著色器運行階段,第十個頂點還沒有輸出時,第一個頂點的輸出有沒有可能已經(jīng)進入到了片段著色器階段?如果是 TBR,因為只會渲染一次,所以需要等到每個 Tile 對應(yīng)的頂點都輸出完成之后才能傳遞到下一個階段?這么理解對不對???
一般的渲染管線包括如下著色器:

這里需要幾點需要說明:
- 可編程著色器
上圖中,在 OpenGL 加入可編程著色器功能之后,頂點著色器、幾何著色器、片段著色器是可編程的。但是系統(tǒng)有提供默認幾何著色器的,所以要生成一個渲染程序,開發(fā)者至少需要提供兩個著色器:頂點著色器 + 片段著色器;
- 輸入和輸出
上述著色器中,每個著色器的輸出都會作為輸入傳遞給下一個著色器。頂點著色器因為是第一個著色器,所以直接接受外部浮點類型數(shù)組作為輸入。
Rendering Pipline 步驟如下:
1.頂點輸入
float 類型的數(shù)組作為頂點輸入到頂點著色器,其格式一般是這樣的:

因為是在 2D 框架中,所以這個的 Z 值一般直接寫 1 即可。
2. 頂點著色器處理
這個著色器是可編程著色器,可以對輸入的頂點進行一些自定義的處理,后面的文章會詳細講;
另外,如何對頂點數(shù)據(jù)做解析,后面的文章也會講到??傊?,頂點著色器就是對輸入的頂點做一些自定義的處理并輸出給圖元裝配著色器;
3. 圖元裝配
圖形裝配著色器接收到頂點著色器的輸出之后,按照圖元參數(shù)對輸入的頂點做一些處理,包含裁剪、透視分割和視口變換等,最終裝配成指定的圖元。
比如裁剪,如果給入的頂點超過了可視范圍,那么圖形裝配之后生成的頂點就不一定是原來的頂點了,而是處理過后的頂點。
即:將輸入的頂點處理成符合當前上下文的頂點;
圖元的位置、形狀等信息在計算機中仍然是通過頂點 + 圖元類型來表示的,所以,這一步的輸出形式依然是頂點數(shù)組。而圖元的類型有點、線段、三角形,這個參數(shù)的傳遞是貫穿整個 pipline 的,在 draw calls 中傳入,比如 drawArray 方法;
4. 圖元的概念
說說自己對圖元和圖元枚舉的概念的理解。
首先是 draw call 中的圖元枚舉:

這個枚舉是告訴著色器,需要按照怎樣的方式處理數(shù)據(jù)。比如裝配階段,如果輸入是 3 個頂點且有一個頂點超出坐標系,繪制圖元的類型是線段和三角形時,最終生成的頂點就會有區(qū)別:
- 線段時,仍然輸出 3 個頂點;
- 三角形時,輸出 4 或 5 個頂點;
圖元的概念:圖元其實就是一個可視化的概念,目的是方便理解 pipline,對于計算機而言,圖元本質(zhì)上仍然是由(頂點數(shù)據(jù) + 圖元類型)組成;
比如提供三個點繪制三角形,且有一個點超出范圍時,圖元裝配階段需要進行裁剪。圖形裝配之后,假如輸出輸出 5 個點:

最終其實是生成了 3 個三角形的圖元,但是這三個圖元本質(zhì)上是由 5 個頂點 + Triangle 這個枚舉來標識;
另外,OpenGL ES 是 OpenGL 的子版本,用于嵌入式設(shè)備,在各個方面進行了精簡。OpenGL ES 中只有點、線、三角形,所有的圖形最終會轉(zhuǎn)換成三角形來進行處理,沒有 OpenGL 中的矩形、多邊形等概念;
5. 幾何著色器處理
幾何著色器階段是對上一步的圖元進行再加工,可能會生成新的頂點和圖元。
最開始的 pipline 圖中,一個三角形變成了兩個,需要注意的是,這里變成兩個三角形并不是某些渲染流程相關(guān)的潛規(guī)則,而是完全由幾何著色器的代碼決定的。
類似的還有很多,比如傳入一個頂點數(shù)據(jù),生成多個頂點從而生成多個基礎(chǔ)圖元,最終組裝成一個小房子的形狀:

上圖中,傳入一個頂點,在幾何著色器階段生成了 5 個頂點并構(gòu)成了 3 個三角形圖元,最終組成了小房子的形狀,代碼如下:

因為幾何著色器是可編程的,所以這里開發(fā)者可以編寫幾何著色器對圖元進行多樣化的處理的。
因此,各種各樣的幾何著色器可以理解成實現(xiàn)各種功能的 Api,也就是傳入頂點數(shù)據(jù)之后自動生成對應(yīng)的圖元數(shù)據(jù)。比如上述小房子的幾何著色器就是實現(xiàn)傳入一個頂點,從而生成一個小房子圖元的功能。
iOS 中的 Tiled Based Rendering 中,將較大的三角形處理成小的三角形是不是可以理解成類似于幾何著色器的功能?還是說是圖元裝配的過程??另外,因為 Metal 已經(jīng)是和 OpenGL 一個級別的框架了,所以 Metal 中的 pipline 并不一定遵循 OpenGL 中的 pipline,所以不要對號入座,最多只能借鑒和參考;
6. 光柵化
OpenGL 中的一個片段(Fragment)是 OpenGL 渲染一個像素所需的所有數(shù)據(jù)。
幾何著色器的輸出會被傳入光柵化階段(Rasterization Stage),光柵化階段會把圖元映射為最終屏幕上相應(yīng)的像素,生成供片段著色器(Fragment Shader) 使用的片段(Fragment)。
這一步說白了就是圖元到硬件(像素)的轉(zhuǎn)換。
因為屏幕是由很多個像素點組成,一個圖形要展示在屏幕上,就需要知道哪些像素點需要亮起來,且要用什么強度的信號來展現(xiàn)出怎樣的顏色。光柵化這一步就是將上下文坐標系中的圖元轉(zhuǎn)化成硬件層面上真正要展示的像素點,即:光柵化就是計算出哪些像素需要展示。而像素具體需要展示成什么顏色則在下一步的片段著色器中計算;
光柵化階段計算并生成像素點模型(容器),片段著色器階段生成完整的像素點數(shù)據(jù)(向容器中填充數(shù)據(jù))。所以,一個 Fragment 中包含該像素被渲染時所需要的所有的數(shù)據(jù)。
一種簡單的劃分就是根據(jù)中心點,如果像素的中心點在圖元內(nèi)部,那么這個像素就屬于這個圖元。如上圖所示,深藍色的線就是圖元信息所構(gòu)建出的三角形;而通過是否覆蓋中心點,可以遍歷出所有屬于該圖元的所有像素;

如上圖,淺藍色部分就是光柵化計算出來需要展示的像素點;
之前的步驟都相當于美術(shù)中的構(gòu)圖,只不過電腦世界的構(gòu)圖是以三角形作為基本圖形。而光柵化這一步就相當于美術(shù)中的素描,這一步完成后就意味著將形狀畫到紙上了。素描完成了,接下來就是上色了,也就是美術(shù)中的水彩等階段。
另外,在片段著色器運行之前會執(zhí)行裁切(Clipping)。裁切會丟棄超出你的視圖以外的所有像素,用來提升執(zhí)行效率。
7.片段著色器
片段著色器(Fragment Shader)也叫做像素著色器(Pixel Shader),這個階段的目的是給每一個像素 Pixel 賦予正確的顏色。
計算像素點的顏色需要頂點 + 場景數(shù)據(jù)。頂點可以從前面的步驟中獲取。通常,片段著色器包含 3D 場景的數(shù)據(jù)(比如光照、陰影、光的顏色等等),這些數(shù)據(jù)可以被用來計算最終像素的顏色由于需要處理紋理、光照等復雜信息,所以該階段通常是整個系統(tǒng)的性能瓶頸。
片段著色器的輸入是什么?工作方式是什么?多個像素點并發(fā)、單獨使用一個片段著色器計算?
8. 混合階段
在所有對應(yīng)顏色值確定以后,最終的對象將會被傳到最后一個階段,我們叫做Alpha測試和混合(Blending)階段。這個階段檢測片段的對應(yīng)的深度(和模板(Stencil))值(后面會講),用它們來判斷這個像素是其它物體的前面還是后面,決定是否應(yīng)該丟棄。這個階段也會檢查alpha值(alpha值定義了一個物體的透明度)并對物體進行混合(Blend)。所以,即使在片段著色器中計算出來了一個像素輸出的顏色,在渲染多個三角形的時候最后的像素顏色也可能完全不同。
另外,iOS 中的混合可能會相對簡單,因為 iOS 是基于 TBR 方式計算了,如果沒有離屏渲染,是不是混合都沒有必要?或者說,混合是在 Tiler 生成之前就做完了?具體還得學習下 Metal;