2016年5月3日 星期二

Unite 2016 - Unity 5.4 GPU Instancing 功能簡介

作者:蔡元星 原文連結

Unity在5.4 Beta版本中引進了一種新的Draw Call Batching方式-GPU Instancing。當場景中有大量使用相同材質和網格的物體時,通過GPU Instancing可以大幅降低Draw Call數量。本文將為大家簡單介紹GPU Instancing的原理並介紹修改現有Shader來啟用Instancing。

什麼是GPU Instancing?

GPU Instancing是指由GPU和圖形API支援,用一個Draw Call同時繪製多個Geometry相同的物體的技術。


上圖中的場景有數千塊隕石,但只有三種隕石模型,這種情況下使用Instancing之後只需要幾十個Draw Call。

在D3D11中,Instanced Draw Call API如下所示:



注意前兩個參數:IndexCountPerInstance和InstanceCount,這是不同於一般Draw Call API的地方。你需要告訴D3D每個Instance用到多少個頂點索引以及這個Draw Call要畫多少個Instances。

那麼如何做到像上圖中那樣每塊石頭都有不同的位置、旋轉和大小呢?在使用Instancing時,我們一般會把世界矩陣這種每個Instance獨有的資料放到一個額外的Buffer中以供Shader呼叫,可以是第二個Vertex Buffer,也可以是Constant Buffer。

Instancing的應用場景


Instancing技術並不代表一定能提高性能,所以必需明白Instancing技術可以和不能做什麼。

Instancing能做什麼:
  • 通過減少Draw Call數量來降低CPU開銷。 
Instancing不能做什麼:
  • 減少GPU的負載。實際上,Instancing還會在GPU上帶來一些額外的開銷。
具體來說,如果你的場景具備以下條件,使用Instancing可能會給你帶來效能提升:
  • 有大量使用相同材質和相同網格的物體 
  • 效能受制於過多的Draw Call (圖形驅動在CPU上負載過大) 
在實際的遊戲專案中,最合適使用Instancing來優化的是植物植被。因為通常這系統需要繪製大量相同的樹木和草,使用Instancing之後Draw Call的消耗會大幅降低。

在Unity 5.4中使用Instancing


在Unity 5.4中使用Instancing需要注意:
  • 類似於Static / Dynamic Batching,Instancing是一種新的合併Draw Call的方式 
  • 適用於MeshRenderer組件和Graphics.DrawMesh() 
  • 需要使用相同的Material和Mesh 
  • 需要把Shader改成Instanced的版本 
  • 當所有前提條件都滿足時,Instancing是自動進行的,並且比Static/Dynamic Batching有更高的優先順序 

Instancing的實現

Instancing的實現步驟如下:
  • 將Per-Instance Data(世界矩陣、顏色等自訂屬性)打包成Uniform Array,存儲在Instance Constant Buffers中
  • 對於可以使用Instancing的Batch,呼叫各平臺圖形API的Instanced Draw Call,這樣會為每一個Instance產生一個不同的SV_InstanceID 
  • 在Shader中使用SV_InstanceID作為Uniform Array的索引獲取當下的Instance的Per-Instance Data 

如何修改Shader以支持Instancing

1.自訂Vertex / Fragment Shader

下面的程式碼展示如何把一個簡單的Unlit Shader修改為支援Instancing的版本。紅色字體的部分是在已有Shader基礎上需要增加或修改的地方。




下面我們來逐一解釋每一處的修改是什麼意思。 

#pragma multi_compile_instancing

“multi_compile_instancing”會使你的Shader產生兩個Variant,其中一個定義了Shader關鍵字INSTANCING_ON,另外一個沒有定義此關鍵字。 

除了這個#pragma指令,下面所列其他的修改都是使用了在UnityInstancing.cginc裡定義的巨集(此cginc檔位於Unity_Install_Dir\Editor\Data\CGIncludes)。取決於關鍵字INSTANCING_ON是否被定義,這些巨集將展開為不同的代碼。 

UNITY_INSTANCE_ID

用於在Vertex Shader輸入 / 輸出結構中定義一個語義為SV_InstanceID的元素。

UNITY_INSTANCING_CBUFFER_START(name) / UNITY_INSTANCING_CBUFFER_END
每個Instance獨有的屬性必須定義在一個遵循特殊命名規則的Constant Buffer中。使用這對巨集來定義這些Constant Buffer。“name”參數可以是任意字串。

UNITY_DEFINE_INSTANCED_PROP(float4, _Color)

定義一個具有特定類型和名字的每個Instance獨有的Shader屬性。這個巨集實際會定義一個Uniform陣列。 

UNITY_SETUP_INSTANCE_ID(v)

這個巨集必須在Vertex Shader的最開始呼叫,如果你需要在Fragment Shader裡存取Instanced屬性,則需要在Fragment Shader的開始也用一下。這個巨集的目的在於讓Instance ID在Shader函數裡也能夠被訪問到。

UNITY_TRANSFER_INSTANCE_ID(v, o)

在Vertex Shader中把Instance ID從輸入結構拷貝至輸出結構中。只有當你需要在Fragment Shader中訪問每個Instance獨有的屬性時才需要寫這個巨集。 

UNITY_ACCESS_INSTANCED_PROP(_Color)

存取每個Instance獨有的屬性。這個巨集會使用Instance ID作為索引到Uniform陣列中去取當下Instance對應的資料。 

最後我們需要提一下UnityObjectToClipPos

在寫Instanced Shader時,通常情況下你並不用在意頂點空間轉換,因為所有內建的矩陣名字在Instanced Shader中都是被重定義過的。比如unity_ObjectToWorld實際上會變成unity_ObjectToWorldArray[unity_InstanceID];UNITY_MATRIX_MVP會變成mul(UNITY_MATRIX_VP, unity_ObjectToWorldArray[unity_InstanceID])。注意到如果直接使用UNITY_MATRIX_MVP,我們會引入一個額外的矩陣乘法運算,所以推薦使用UnityObjectToClipPos / UnityObjectToViewPos函數,它們會把這一次額外的矩陣乘法優化為向量-矩陣乘法。 

2.Surface Shader


如果想把一個Surface Shader改寫成支持Instancing的版本,你只需要加上“#pragma multi_compile_instancing” 就可以了。設定Instance ID的程式會自動產生。定義或存取每個Instance獨有屬性的方法同Custom Vertex / Fragment Shader。

另外,你可以在Project視窗按右鍵,選擇Create->Shader->Standard Surface Shader (Instanced)來建立一個範例Shader。

使用Instancing的限制

下列情況不能使用Instancing:
  • 使用Lightmap的物體 
  • 受不同Light Probe / Reflection Probe影響的物體 
  • 使用包含多個Pass的Shader的物體,只有第一個Pass可以Instancing 
  • 前向渲染時,受多個光源影響的物體只有Base Pass可以instancing,Add Passes不行
另外,由於Constant Buffer的尺寸限制,一個Instanced Draw Call能畫的物體數量是有上限的(參見UnityInstancing.cginc中的UNITY_MAX_INSTANCE_COUNT)

最後需要再次強調的是,Instancing在Shader上有額外的開銷,並不代表一定能提高FPS。永遠要以實際Profiling結果為準

5.4 Beta目前支持Instancing的平臺

  • Windows: DX11 / DX12 with SM 4.0以及更高 
  • OS X & Linux: OpenGL 4.1以及更高
  • PlayStation 4 
  • 手機移動平臺和其他主機平臺會在後續版本支持 

立即下載Unity 5.4 Beta 試用Instancing

這裡下載:所有人都可以使用(包括Personal Edition用戶)
可以到這裡反映意見,以及更多關於Instancing的文件

沒有留言:

張貼留言

著作人