1. 程式人生 > >Shader、Draw Call和渲染管線(Rendering Pipeline)

Shader、Draw Call和渲染管線(Rendering Pipeline)

了解 and strong format 檢查 vid 包括 忽略 得到

翻閱了很多資料,也做了不少筆記,決定還是對渲染進行一個總結,以鞏固所學的東西。

《Real-Time Rendering, Third Edition》 (PDF的配圖鏈接)將一個渲染流程分為三個階段:

技術分享圖片

應用階段(PApplication Stage)、幾何階段(Geometry Stage)、光柵化階段(Rasterizer Stage)

我借用《Unity Shader入門精要》的網頁貼圖來說明

技術分享圖片

技術分享圖片

顏色表示了不同階段的可配置性或可編程性:綠色表示該流水線階段是完全可編程控制的,黃色表示該流水線階段可以配置但不是可編程的,藍色表示該流水線階段是由GPU固定實現的,開發者沒有任何控制權。實線

表示該shader必須由開發者編程實現,虛線表示該Shader是可選的。

頂點著色器(Vertex Shader)、片元著色器(Fragment Shader)是我們編寫 Shader最常用的二個。另外二個曲面細分著色器(Tessellation Shader)、幾何著色器(Geometry Shader)都是可選著色器。

既然提到了著色器(Shader),那什麽是Shader呢

A shader is a piece of code, that is executed on the GPU. The engine feeds it with 3d model vertices, textures and other information, and gets back from it pixel colours.
-- from What is a Shader?

翻譯過來:Shader 就是運行在GPU上的一段代碼,引擎提供給它3D的模型頂點、紋理和其它信息,並獲取返回的像素顏色。

Draw Call又是什麽呢

A draw call is a command to render one mesh. It is given by the CPU. It is received by the GPU. The command only points to a mesh which shall be rendered and doesn’t contain any material information since these are already defined via the render state. The mesh resides at this point in the memory of your graphic card (VRAM).

-- from renderhell-book1

技術分享圖片

有興趣可以觀看下面這個視頻,很形象和生動。

視頻地址:

https://data.simonschreibt.de/gat049/cpu_calls_gpu.webm

https://data.simonschreibt.de/gat049/commandbuffer_communication_chunk.webm

實際上,Draw Call就是一個命令,它的發起方是CPU,接收方是GPU。這個命令僅僅會指向一個需要被渲染的圖元(primitives)列表,而不會再包含任何材質信息(這些信息已經在渲染狀態中被定義了),此時網格是駐留在顯存(Video Random Access Memory)中的。

當給定了一個Draw Call時,GPU就會根據渲染狀態(例如材質、紋理、著色器等)和所有輸入的頂點數據來進行計算,最終輸出成屏幕上所顯示的那些像素。

只看文字不免過於抽象,我之前在網上看到一張圖,描述的挺不錯。

技術分享圖片

而實際的工作比這個要復雜很多,包括:坐標轉換、透視、裁剪等一系列操作。

技術分享圖片

所上圖所示,需要歷經:坐標轉換(模型空間 –> 齊次裁剪空間),逐頂點光照,透視除法(歸一化的設備坐標 —— Normalized Device Coordinates, NDC),裁剪,屏幕映射。

這裏再多解釋一下,何為齊次裁剪空間。齊次裁剪空間是一個中心點是坐標原點的立方體,xyz取值範圍是[-1, 1]。使用一個4x4的齊次變換矩陣將點從攝像機坐標空間變換到齊次裁剪空間,將頂點的深度值z保存在頂點經過變換得到的齊次坐標的w分量中。最後,把頂點在齊次空間中的坐標通過將x,y,z分量除以w分量的方式,將齊次坐標轉為NDC。

為什麽需要使用NDC呢?為了適配屏幕的多分辨率問題。

歸一化坐標中,兩個軸其中一個軸的範圍是由0至1(但不能兩個都是0~1),而且能輕易縮放至不同分辨率下的像素單位。假設把y軸的範圍設置為0.0 ~ 1.0,當使用4:3長寬比時,x軸的範圍就是0.0至1.333(=4/3),而16:9時x軸的範圍則是0.0 ~ 1.777(=16/9),這樣就不會出現拉伸了。

技術分享圖片

技術分享圖片

技術分享圖片

接下來就進入光柵化階段了。

技術分享圖片

技術分享圖片

技術分享圖片

接收頂點信息,進行適當的轉換後,對頂點進行插值處理,然後對三角形進行遍歷,檢查每個網格是否被三角形覆蓋,如果被覆蓋就會生成一個片元。之後交給片元著色器(在DirectX中,也稱為像素著色器 Pixel Shader)。

經過片元著色器(Fragment Shader)處理後,得到一個或者多個顏色值(如下圖所示)。

技術分享圖片

之後進入逐片元操作(Per-Fragment Operations),會經過模板測試(Stencil Test)、深度測試(Depth Test)、混合(Blend)等一系列操作,最後得到的結果(一系列顏色值)存放到幀緩沖區,供GPU進行屏幕的更新。

技術分享圖片

大體的渲染過程就如上所述。中間忽略了不少信息,如坐標轉換(主要使用矩陣、四元數,矩陣就是映射),還有投影(正交/平行投影、透視投影),以及光照模型(各種貼圖和法線、切線等)。整個過程中裁剪不僅僅只發生在應用階段,背面剔除、遮擋剔除(光柵化階段)都使用了裁剪。也沒有提及為什麽是減少Draw Call,以及如何優化。

先大體理解整個脈絡,中間每一個點都可以單獨寫很多內容,但是總的流程是這樣的。對整個渲染流程,時刻要在腦海中有一個比較清晰的認識和了解,不然很容易被各種轉換和模型給搞暈,不知道當前處於什麽階段,輸入是什麽,輸出又是什麽,接下來要怎麽走。

Shader、Draw Call和渲染管線(Rendering Pipeline)