前言
對於Unity
渲染流程的理解可以幫助我們更好對Unity
場景進行效能消耗的分析,進而更好的提升場景渲染的效率,最後提升遊戲整體的效能表現
Unity
的遊戲畫面的最終的呈現是由CPU
與GPU
相互配合產生的效果,總體上,兩者直接的工作流程是一個流水線的模式,大概分為三個階段:
- 應用程式階段
- 幾何階段
- 光柵化階段
其中應用程式階段是由CPU
來負責計算處理的,而幾何階段與光柵化階段則是由GPU
來進行處理執行的
注意:
- 本文章大部分內容來自於馮樂樂編寫的:Unity Shader 入門精要,是一本不錯的學習
shader
的書
渲染流水線流程
一、應用程式階段
在應用程式階段,主要是CPU
在進行一系列的處理操作,主要的幾個關鍵步驟為:
1、資料準備工作
首先需要CPU
從硬碟中讀取場景中的資料,比如說攝像機位置、視錐體、場景中有哪些模型、使用了什麼樣的光源
CPU
在獲取到這些資料後,需要根據開發者對於場景中的相關引數的調整後獲取到需要被渲染的資料新增給視訊記憶體
哪些引數會影響被渲染物體資料量
- 視錐剔除:是
Unity
預設的減少渲染物體的方式- 遮罩剔除:被遮擋的物體不進行渲染
2、設定渲染狀態
這一階段的目的是輸出渲染需要的幾何資訊,即渲染圖元
可以這樣的簡單解釋,對於同一種模型,使用不同的材質,處於不同的光照條件下,模型產生的效果也是不同的,這些材質光照就是對於模型要使用哪種渲染狀態的一種引數
3、呼叫Draw Call
前面在進行效能優化時,反覆提到的一個點就是Draw Call
,那麼到底是什麼一個東西呢
簡單來說,Draw Call
就是一個命令,一個由CPU
發起,命令GPU
執行的命令,就是CPU
告訴GPU
可以對某個模型進行渲染處理的傳話人
關於Draw Call
- 簡單來說,
Draw Call
就是CPU
呼叫圖形程式設計介面,比如DirectX
或OpenGL
,來命令GPU
進行渲染的操作,可以將Draw Call
理解為一個命令
一般來說一個獨立的模型會產生一個Draw Call
,但是如果經過一些特殊的處理,比如所動態合批,靜態合批等等操作,就會降低Draw Call
為什麼
Draw Call
會影響CPU
效能
- 可以直接理解為兩者對於資料處理資料不對等而造成的結果
CPU
將Draw Call
發往GPU
過程中並不是很直接的就交給GPU
,而是先將這些資料存到一個命令緩衝區內,然後再後面的幾何階段由GPU進行資料的讀取GPU
的渲染能力是很強的,渲染300個和3000個三角網格通常沒有什麼區別,因此渲染速度往往快於CPU
提交命令的速度Draw Call
的數量太多,CPU
就會把大量時間花費在提交Draw Call
命令上,造成CPU
的過載
二、幾何階段
接下來就會來到GPU
的處理範圍,需要通過GPU
來進行圖形的渲染工作,大概流程如圖:
在開始瞭解GPU
流水線工作流程前,需要先理解輸入頂點資料,我們在應用程式階段將需要渲染的資料載入到了視訊記憶體中,這樣就可以方便GPU
的資料呼叫,而關於具體使用視訊記憶體中哪些資料,就是由Draw Call
來決定的
1、頂點著色器
頂點著色器的處理單位是頂點,每一個輸入的頂點都會呼叫一次頂點著色器,用於實現頂點的空間變換、頂點著色等功能
其需要完成的工作是,座標變換和逐頂點光照:
- 座標轉換:通過座標轉換可以實現波動水面或者說
- 逐頂點光照:根據字面理解,即對每一個頂點融合場景中的光照資訊
通過這樣的處理,即可將處理後的資料傳遞給後續流程進行進一步的處理
2、曲面細分著色器:
·是一個可選擇的著色器,用於細分圖元(可以簡單的理解為三角面)
3、幾何著色器:
同樣是可以選擇的。用於執行逐圖元著色的操作
4、裁剪
裁剪同樣是將不在相機視錐外的部分去除掉,但是與之前的視錐剔除不同,在GPU層面的去除是微觀的,基於一個個圖元進行處理的
關於視錐剔除與遮罩剔除
- 兩者是在應用程式階段進行處理的,即CPU來處了,而處理的基本單元是一個個獨立的物體,如果一個物體的一部分在視錐內,那麼這個整個物體就不會被剔除
而關於一個圖元(一般是三角面)的裁切的三種情況:
- 完全在視野內:不裁切
- 部分在視野內:裁切掉不在視野內的部分,並且與視野邊緣處生成新的頂點
- 完全在視野外:直接裁切掉
這一步完全由硬體的固定操作來控制,無法通過程式設計來控制
5、螢幕對映
這一步的輸入仍然是三維座標系下的座標,螢幕對映的任務是將每一個圖元的x和y座標轉換到螢幕座標系中,這一步的操作和我們顯示畫面的解析度有很大的關係
螢幕對映得到的座標決定了這個頂點對應螢幕上哪個畫素以及激勵這個畫素有多遠
三、光柵化階段
光柵化階段同樣是由GPU進行處理,具體的處理流程為:
1、三角形設定:
首先從上一步獲取處理完的資訊
- 即螢幕座標系下的頂點位置以及和他們相關的額外資訊,如深度值法線方向,視角方向
然後開始正式處理:
- 計算光柵化一個三角網格所需的資訊,即將這個三角網格的每一條邊與螢幕畫素點來對應起來,這樣就會在邊上產生一系列的點
2、三角形遍歷:
這一階段會檢查每個畫素是否被一個三角網格說覆蓋,如果覆蓋,則生成一個片元,這樣一個找到哪些畫素被三角形覆蓋的過程就是三角形遍歷
關於片元:
- 一個片元並不是真正意義上的畫素,是一個包含很多狀態的集合,比如說螢幕座標、深度資訊,以及從其他幾何階段輸出的頂點資訊,例如法線紋理座標,這些狀態用於計算每個畫素的最終顏色
通過這樣的處理後,最終輸出一個片元序列,即所有畫素的片元資訊的集合
片元著色器:
這一步的目的可以簡單的理解為,將前面所有的處理產生的資料轉換為對應的顏色,這樣就可以顯示出最終的畫面
這一階段具體的操作細節:
- 紋理取樣:通過頂點著色器輸出的每個頂點對應的紋理座標,然後經過光柵化階段對應的三角網格的三個頂點對應的紋理座標插值後,得到覆蓋的片元的紋理座標
逐片元操作:
作為渲染流程的最後一部分,其功能是對於每個片元進行一些操作:
- 決定每個片元的可見性:通過一些測試工作來實現:深度測試、模板測試
- 通過上一步測試,則對這個片元的顏色值和寂靜儲存在染色緩衝中的顏色進行合併,或者說混合
總結
整個流程看下來是比較晦澀難懂的,尤其是GPU處理階段,大多數的概念、流程就像天書一樣
但是在我們日常使用中,大部分流程是我們接觸不到的,也沒有辦法通過引數去調整這些過程,我們只需要關注一些我們可以調整的模組流程的
同時,我們要理解那些影響遊戲程式的一些關鍵點,比如說Draw Call
的產生,以及為啥會影響遊戲的效能,這樣會幫助我們更好的去做一些效能優化的操作