我們都希望能夠在場景中投入一百萬個物體,不幸的是,渲染和管理大量的游戲對象是以犧牲CPU和GPU性能為代價的,因為有太多Draw Call的問題,最后我們必須找到其他的解決方案。
在本文中,我們將討論兩種優化技術,它們可以幫助您減少Unity游戲中的Draw Call數量以提高整體性能:批處理和GPU Instancing。
批處理
開發者在日常工作中遇到的最常見的問題之一是性能不足,這是由於CPU和GPU的運行能力不足。一些游戲可以運行在PC上,但是在移動設備上不行。游戲運行時運行是否流暢受Draw Call數量的影響很大。有幾個解決方案能幫助您解決這個問題。最常見的是批處理,包括Static Batching和Dynamic Batching。
Static Batching可以讓引擎降低任何尺寸網格的Draw Call,如下圖所示:

要讓場景中的物體使用Static Batching,需要將其標記為Static,並在Mesh Renderer中共享相同的材質,因為Static Batching不會在CPU上做頂點轉換,所以它通常比Dynamic Batching更有效。不過它會使用更多的內存,例如你的場景中有相同物體的多個副本,Unity會將它們組合成一個大網格並可能會增加內存使用。Unity將盡可能多的網格結合到一個靜態網格中,並將其作為一個Draw Call提交。這種方法的缺點是:標記為Static的物體在其生命周期中不能移動。
Dynamic Batching啟用時,Unity將嘗試自動批量移動物體到一個Draw Call中。要使物體可以被動態批處理,它們應該共享相同的材質,但是還有一些其他限制:
- 頂點數量:Dynamic Batching場景中物體的每個頂點都有一定的開銷,因此批處理只適用於少於900個頂點屬性的網格物體。舉個例子,如果你的着色器使用頂點位置,法線和一個UV,那么你可以動態批處理多達300個頂點;而如果你的着色器使用頂點位置,法線,UV0,UV1和切線,那么只有180個頂點。值得注意的是,屬性計數限制可能會在將來更改。
- 鏡像信息:如果物體包含的Transform具備鏡像信息,例如A物體的大小是(1f, 1f, 1f),而B物體的大小則是(-1f, -1f, -1f),則無法做批處理。
材質:如果物體使用不同的材質實例,即使它們本質上相同,也不會被批量處理。而Shadow Caster Rendering是個例外。 - 渲染器:擁有光照貼圖的物體有其他渲染器參數,例如光照貼圖索引或光照貼圖的偏移與縮放。一般來說,動態光照貼圖的游戲對象應該指向要批量處理的完全相同光照貼圖的位置。
- 不能使用Multi-pass着色器的情況:幾乎所有的Unity着色器都支持多個燈光的正向渲染模式(Forward Rendering),這要求額外的渲染次數,所以繪制 “額外的每像素燈”時不會被批處理;Legacy Deferred(Light Pre-Pass)渲染路徑不能被動態批處理,因為它必須繪制物體兩次。
Dynamic Batching通過將所有物體的頂點轉換為CPU上的世界空間來工作,所以它只能在渲染Draw Call的工作量小於CPU頂點轉換工作量的時候,才會起到提高性能的作用。當用游戲機或如Metal這樣的現代API,Draw Call的開銷通常低得多,Dynamic Batching就無法提高性能了。了解到以上限制后,如果明智地使用批處理,可以顯著提高您游戲的性能。
GPU Instancing
提高圖形性能的另一個好辦法是使用GPU Instancing。GPU Instancing的最大優勢是可以減少內存使用和CPU開銷。當使用GPU Instancing時,不需要打開批處理,GPU Instancing的目的是一個網格可以與一系列附加參數一起被推送到GPU。要利用GPU Instancing,您必須使用相同的材質,且可以傳遞額外的參數到着色器,如顏色,浮點數等。
Unity從5.4版本開始支持GPU Instancing。 唯一的限制是在游戲物體上要使用相同的材質和網格。 目前支持以下平台:
- Windows DX11/DX12 和 SM 4.0 或更高/OpenGL 4.1 或更高
- OS X and Linux:OpenGL 4.1 and above
- 移動:OpenGL ES 3.0 或更高/Metal
- PlayStation 4
- Xbox One
如果您想要進行進一步的優化,例如減少管理場景物體的開銷,您也可以使用Graphics.DrawMeshInstanced方法。 您只需要傳遞您的網格,材質和附加屬性來繪制您的物體。現在的限制是一次最多1023個實例。在Unity 5.6中,我們添加了Graphics.DrawMeshInstancedIndirect的新方法,可以用來指定需要渲染的實例數量。
GPU Instancing案例
要創建支持GPU Instancing的基本標准表面着色器,可以在您的項目里面點擊:
Create->Shader->StandardSurfaceShader(Instanced)。

然后,在材質屬性中選擇新創建的着色器。

雖然實例化的物體共享相同的網格和材質,但您可以使用MaterialPropertyBlock API為每一個物體設置單獨的着色器屬性。
如果一個游戲對象被標記為“Static”並且打開了Static Batching,那么這個游戲對象就不能進行GPU Instancing,檢視器中會出現一個警告框,提示“靜態批處理”標志可以在播放器設置(Player Settings)中取消。如果游戲對象支持Dynamic Batching,但是它使用的某個材質可以進行實例化,那么這個游戲對象將不會被批處理,並且將被自動實例化。
當使用Forward Rendering渲染模式,受多個燈光影響的物體無法有效地實例化。只有Base Pass可以有效地利用實例化,而不是添加的Pass。此外,使用光照貼圖或受不同光或Reflection probe影響的物體無法實例化。如下圖所示,您可以在Frame Debug中發現和GPU Instancing相關的Draw Call被標記為“Draw Mesh(Instanced)”。

GPU Instancing是一個非常強大的功能。在Unity 5.6中,您可以使用Graphics.DrawMeshInstancedIndirect繪制大量網格。在Mac Pro中,我們能夠畫出約68萬個具有不同顏色的移動立方體並保持穩定的60幀每秒的幀率。
下圖是一個示例場景,超過6千個包子在天空中圍繞一個大碗飛翔,它們都投射和接收陰影。由於使用了GPU Instancing,幾乎沒有性能開銷。這里的包子模型使用了StandardSurface Shader(Instanced)。

總結
在本文中,我們描述了用於優化渲染性能的兩種最流行的技術:批處理和GPU Instancing。我們向您展示了如何在實踐中使用它們並討論可能的應用。正因為有諸如批處理和GPU Instancing等優化技術的存在,我們能夠繪制大量的對象並保持穩定的性能。
想要及時獲得更多有關Unity的技術分享,請掃描下方二維碼,關注“Unity官方平台”微信公眾號!

轉載請注明來源:Unity官方中文社區 (forum.china.unity3d.com)。請勿私自更改任何版權說明信息。