作者:蔡元星 原文連結
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能做什麼:
Instancing能做什麼:
- 通過減少Draw Call數量來降低CPU開銷。
- 減少GPU的負載。實際上,Instancing還會在GPU上帶來一些額外的開銷。
- 有大量使用相同材質和相同網格的物體
- 效能受制於過多的Draw Call (圖形驅動在CPU上負載過大)
在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:
最後需要再次強調的是,Instancing在Shader上有額外的開銷,並不代表一定能提高FPS。永遠要以實際Profiling結果為準!
- 使用Lightmap的物體
- 受不同Light Probe / Reflection Probe影響的物體
- 使用包含多個Pass的Shader的物體,只有第一個Pass可以Instancing
- 前向渲染時,受多個光源影響的物體只有Base Pass可以instancing,Add Passes不行
最後需要再次強調的是,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的文件
沒有留言:
張貼留言