UnityShader入門精要-渲染流水線


一、什么是渲染流水線

渲染流水線的工作在與有一個三維場景出發,生成或者說渲染一張二維圖像。

即計算機從一些列的頂點數據和紋理等信息出發,將這些信息轉換成一張人眼可以看到的圖像。

《Real-Time Rendering》一書將渲染流程分為三個階段:應用階段 Application Stage,幾何階段 Geometry Stage,光柵化階段 Rasterizer Stage。

 


應用階段

這個階段是由我們的應用主導的, 因此通常由CPU負責實現。在這一階段中,開發者有3個主要任務。

①准備好場景數據, 例如攝像機的位置、視錐體、模型、些光源等等。

②做一個粗粒度剔除工作,把那些不可見的物體剔除出去,可以提高渲染性能。

③設置每個模型的渲染狀態。 渲染狀態包括但不限於它使用的材質、紋理和Shader等。

這一階段最重要的輸出是渲染所需的幾何信息, 即渲染圖元(rendering primitives)。

通俗來講, 渲染圖元可以是點、線、三角面等。這些渲染圖元將會被傳遞給下一個階段——幾何階段。

 


幾何階段

幾何階段處理所有和我們要繪制的幾何相關的事情。

例如決定需要繪制的圖元是什么,怎樣繪制它們,在哪里繪制他們。這一階段通常在GPU上進行。

幾何階段和每個渲染圖元打交道,進行逐頂點、逐多邊形的操作。該階段可進一步被分解為更小的流水線階段。

幾何階段的另一個重要的任務是把頂點坐標變換道屏幕空間中,再交給光柵器進行處理。

這一階段會將輸出屏幕空間的二維頂點坐標、每個頂點對應的深度值、着色等相關信息傳遞給下個階段。

 


光柵化階段

這一階段會使用上一階段傳遞的數據來產生屏幕上的像素,然后渲染出最終的圖像,這一階段在GPU上運行。

光柵化階段的任務主要是決定每個渲染圖元中的那些像素應該被繪制在屏幕上。

他需要對上階段的到的逐頂點數據進行插值,然后再進行逐像素處理。

與幾何階段類似,光柵化階段又可以分為更小的流水線階段。

 


二、CPU和GPU之間的通信

渲染流水線的起點是應用階段,其可分為以下三個階段:

①把數據加載到顯存中

②設置渲染狀態

③調用Draw Call

 


把數據加載到顯存中

渲染所需的數據都要從硬盤加載到系統內存,然后網格和紋理等數據又被加載到顯存。

因為顯卡對RAM沒有直接的訪問權利,而且顯存更快。

 

 


設置渲染狀態

這些狀態定義了場景中網格是怎么被渲染的,比如使用那個shader,light,material等。

如果沒有更改渲染狀態,那么所有網格都使用同一種。

 

准備好以上的工作后,CPU調用一個渲染命令告訴GUP,這個渲染命令就是Draw Call

 


調用 Draw Call

Draw Call就是一個命令。他的發起方是CPU,接收方是GPU。

這個命令僅僅會指向一個需要被渲染的圖元列表,而且不會再包含任何材質信息,上個階段已經完成了。

給定一個 Draw Call 后GPU根據渲染狀態,如材質紋理着色器等和所有輸入的頂點數據來進行計算,最終輸出成屏幕上顯示的像素。

這個計算過程就是GPU流水線。

 


GPU流水線 

GPU從CPU得到渲染命令后就進行一系列流水線操作,將圖元渲染到屏幕上。

 


概述

GPU對開發者開放了很多控制權。

從圖中可以看出GPU渲染流水線接受頂點數據作為輸入。

這些數據是應用階段加載到顯存中,再由Draw Call指定的。這些數據隨后被傳給shader。

頂點着色器 Vertex Shader 

頂點着色器是完全可編程的,通常用於實現頂點的空間變換、頂點着色等功能。

曲面細分着色器 Tessellation Shade

曲面細分着色器是一個可選的着色器,用於細分圖元。

幾何着色器 Geometry Shader 

幾何着色器是一個可選的着色器,用於執行逐圖元着色操作,或產生更多圖元。

裁剪 Clipping 

是將那些不在攝像機視野內的頂點剪裁掉,並剔除某些三角圖元的面片。

這個階段可以配置,我們可以使用自定義的裁剪平面來配置裁剪區域,也可通過指令控制裁剪三角圖元的正面或背面。

屏幕映射 Screen Mapping 

屏幕映射是不可配置和編程的,負責把每個圖元的坐標轉換到屏幕坐標系。

 


頂點着色器

頂點着色器的處理單位

頂點着色器是流水線的第一個階段,其輸入來自CPU。

頂點着色器的處理單位是頂點,每個進入的頂點都會調用一次頂點着色器。

其本身不能創建或者銷毀任何頂點,因此無法得到頂點之間的關系。比如無法得知兩頂點是否屬於同一個三角網格。

這種獨立性的好處是GPU可以利用本身特性並行優化每一個頂點,這一階段處理速度會非常快。

頂點着色器計算頂點顏色

頂點着色器的工作還有坐標變換和逐頂點光照,以及輸出后續階段所需的數據。

如下圖演示頂點着色器對頂點位置進行坐標變換並計算頂點顏色的過程。

 

頂點着色器對頂點坐標進行坐標變換

顧名思義就是對頂點的坐標進行某種變換,頂點着色器可以在這一步改變頂點的位置。無論怎樣改變頂點的位置,都必須把頂點坐標從模型空間轉換到其次裁剪空間。

o.pos = mul(UNITY_MVP, v.position);

類似上面這句代碼的作用就是把頂點坐標轉換到齊次裁剪坐標系下,接着通常再由硬件做透視除法后,最終得到歸一化的設備坐標NDC。

 

需要注意的是上圖給出的坐標范圍是OpenGL也是Unity使用的NDC,其z分量在[-1,1]。而DirectX中,NDC的z分量范圍是[0,1]。

頂點着色器的輸出方式

頂點着色器可以有不同的輸出方式,最常見的是經光柵化后交給片元着色器進行處理,現代Shader Model中還能把數據發送給曲面細分着色器。

 


裁剪

攝像機的視野范圍不會覆蓋所有的場景物體,而不在視野范圍的物體應該被裁剪處理掉。

圖元和攝像機的視野關系

完全在視野內、部分在視野內、完全在視野外。

完全在視野內的圖元就傳遞給一下一階段,完全在視野外的圖元不會被傳遞,部分在視野內的就需要進行裁剪了。

如何裁剪

視野外部頂點應該使用一個新的頂點來代替,新頂點位於這條線段和視野邊界的交點處。

因為一直在NDC下的頂點位置,頂點位置在一個立方體中,所以裁剪就非常簡單了,將圖元裁剪到單位立方體內即可。

這一步是不可編程的,但我們可以自定義一個裁剪操作對這一步進行配置。

 


屏幕映射

這一步輸入的坐標仍然是三維坐標下的坐標,是齊次裁剪空間下的坐標。

屏幕映射的任務是把每個圖元的x和y坐標轉換到屏幕坐標系下。

屏幕坐標系是一個二維坐標系,她和我們用於顯示畫面的分辨率有很大的關系。

實際上,屏幕映射不會對輸入的z坐標做任何處理,屏幕坐標和z坐標一起構成了一個坐標系,叫做窗口坐標系。這些值會一起被傳到光柵化階段。

屏幕映射得到的屏幕坐標決定了這個頂點對應屏幕上哪個像素以及距離這個像素有多遠。

注意,OpenGL和DirectX,在屏幕坐標系上有差異,前者將屏幕左下角當作最小的窗口坐標值,后者則是定義為左上角。

 


三角形設置

這一步開始進入了光柵化階段。上階段輸出的信息是屏幕坐標系下的頂點位置以及和他相關的額外信息,如深度值z坐標、法線方向、視角方向等。

光柵化階段第一個流水線階段是三角形設置,該階段會計算光柵化一個三角形網格所需的信息。

具體來說,上一個階段輸出的都是三角形網格的頂點,也就是三角形網格每條邊的兩個端點。

如果要得到整個三角網格對像素的覆蓋情況,我們就必須計算每條邊上的像素坐標。

為了能夠計算邊界像素的坐標信息,就需要得到三角形邊界的表示方式。這樣的計算三角形網格表示數據的過程就叫做三角形設置。

 


三角形遍歷

該階段會將檢查每個像素是否被一個三角網格所覆蓋。如果覆蓋的話就會生成一個片元。

而這樣一個找到哪些像素被三角網格覆蓋的過程就是三角形遍歷,也稱為掃描變換。

三角形遍歷階段會根據上一個階段的計算結果判斷一個三角形網格覆蓋了哪些像素,並使用三角網格3頂點的頂點信息對整個覆蓋區域進行插值。

 

這一步的輸出就是得到一個片元序列。

一個片元並不是真正意義上的像素,而是包含了很多狀態的集合,這些狀態用於計算每個像素的最終顏色。

這些狀態包括了他的屏幕坐標、深度信息、以及其他幾何階段輸出的頂點信息,如法線、紋理坐標等。

 


片元着色器

片元着色器是另一個非常重要的可編程着色器階段。

DirectX中被稱為像素着色器,但是片元更合適,因為此時片元不是一個真正的像素。

前面的光柵化階段實際不會影響屏幕上每個像素的顏色值,而是產生一系列的數據信息,來表述一個三角網格怎樣覆蓋每個像素。

每個片元就負責存儲這些數據。真正對像素產生影響的階段是逐片元操作。

片元着色器的輸入是上階段對頂點信息插值的結果,具體來說就是從頂點着色器中輸出的數據插值得到的。而他輸出的是一個或者多個顏色值。

紋理采樣

這階段可以完成很多重要的渲染技術,比如紋理采樣。

為了在片元着色器中進行紋理采樣,先在頂點着色器階段輸出每個頂點對應的紋理坐標,

然后經過光柵化階段對三角形網格的三個頂點對應的紋理坐標進行插值后,就可以得到其覆蓋的片元的紋理坐標了。

其局限在於僅可以影響單個片元。即執行片元着色器時,不能將結果直接發給旁邊的鄰居,除了導數信息。

 


逐片元操作

這是OpenGL中的說法,在DirectX中,這階段被稱為輸出合並階段,Output-Merger。

該階段是對每一片片元進行操作,主要任務有:

①決定每個片元的可見性,如深度測試、模板測試。

②如果一個片元通過了所有測試,就把這個片元的顏色值和已經存儲在顏色緩沖區的顏色進行合並,混合。

該階段是高度可配置的,我們可以設置每一步的操作細節。該階段首先解決的是,每個片元的可見性問題。

這需要一系列測試,通過了才能和顏色緩沖區進行合並。沒通過任何一個測試,片元都會被丟棄。

測試過程是很復雜的,不同接口實現細節也不同。

模板測試

與之相關的是模板緩沖Stencil Buffer。實際上模板緩沖和顏色換成深度緩沖幾乎是一類東西。

開啟了模板測試,GPU就會使用讀取掩碼讀取模板緩沖區中該片元的模板值,將該值和讀取到的參考值進行比較。

這個比較函數可以是開發者指定的,例如小於時舍棄該片元或者大於時舍棄該片元。

不管一個片元有沒有通過模板測試都可以根據模板測試和下面的深度測試結果來修改模板緩沖區。

這個修改操作也是由開發者指定的。模板測試通常用於限制渲染的區域,高級用法有渲染陰影、輪廓渲染等。

深度測試

通過模板測試后,片元就會進行深度測試。其同樣是高度可配置的。

開啟后,GPU會把該片元深度值和已存在與深度緩沖區的深度值進行比較,這個比較函數也是開發者設置的。

例如我們總想只顯示里攝像機最近的物體,而其他被遮擋的的物體就不需要顯示在屏幕上。

如果一個片元沒有通過這個測試他就沒有權利更改深度緩沖區中的值。

通過之后開發者還能指定是否用該片元的深度值覆蓋原來的深度值。這是通過開啟/關閉深度寫入做到的。

合並操作

通過了所有測試后,片元就來到了合並。

每個像素的信息被存儲在一個名為顏色緩沖的地方,因此執行此次渲染時,顏色緩沖中往往已經有了上次渲染之后的結果。

合並就是要決定到底是使用這次渲染得到的顏色完全覆蓋之前的還是進行其他處理。

對於不透明物體,開發者可以關閉混合操作。這樣片元着色器計算得到的顏色值就會直接覆蓋顏色緩沖區中的像素值。

對於半透明物體,我們需要使用混合操作來讓這個物體看起來是透明的。

混合操作也是可以高度配置的。開啟了混合,GPU會取出源顏色和目標顏色將兩者混合。

源顏色是片元着色器得到的顏色,目標顏色是已經存在於顏色緩沖區中的顏色值。

之后就會使用一個混合函數進行混合操作。該函數和透明通道息息相關,例如根據透明通道的值進行相加減乘等。

提前測試

雖然邏輯上這些測試是在片元着色器之后進行的,但對於大多數GPU來說,他們會盡可能在執行片元着色器之前進行這些測試。

盡可能早知道哪些片元會被舍棄可以提高性能,比如unity的渲染流水線中其深度測試就在片元着色器之前。

這種將深度測試提前的技術被稱為Early-Z技術。

但將這些測試提前其檢驗結果可能會與片元着色器中一些操作產生沖突。

雙重緩存策略

當模型的圖元經過了上面的層層計算和測試后就會顯示到屏幕上。屏幕顯示的就是顏色緩沖區中的顏色值。

為了避免我們看到那些光柵化的圖元,GPU會使用雙重緩沖策略。

即對場景的渲染是在幕后發生的,在后置緩沖中,一旦已經被渲染到后置緩沖中,GPU就會交換后置緩沖取和前置緩沖區中的內容。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM