GPU Instancing

Unity之GPU Instancing

在这里插入图片描述

什么是GPU Instancing

当场景中有大量使用 相同材质和网格 的物体时,通过GPU Instancing可以大幅降低Draw Call数量。

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能做什么

  • 通过减少DrawCall数量来降低CPU开销。

Instancing不能做什么

  • 减少GPU的负载。实际上,Instancing还会在GPU上带来一些额外开销。

具体来说,如果你的场景具有以下条件,那么使用Instancing可能会给你带来性能提升:

  • 有大量使用相同材质的相同网格物体;
  • 性能受限于过多的DrawCall(图形驱动在CPU上负载过大)

在实际的游戏项目中,最合适使用Instancing来优化的是植物植被。
因此通常这系统需要绘制大量相同的树木和草。
使用Instancing技术之后DrawCall的消耗会大幅度降低。

在Unity 5.4中使用Instancing

在Unity 5.4中使用Instancing需要注意:

  • 类似 Static / Dynamic Batching ,Instancing 也是一种降低(合并)的方式;
  • 适用于 MeshRenderer组件和 Graphics.DrawMesh()
  • 需要是相同的材质和Mesh
  • 當所有前提條件都滿足時,Instancing是自動進行的,並且比Static/Dynamic Batching有更高的優先順序

Unity中的Instancing的實現

Instancing的實現步骤如下:

  • 将 Per-Instance Data (世界矩阵、颜色等自顶属性)打包成为 Uniform Array,存储在 Instance Constant Buffers 中。
  • 对于可以使用 Instancing 的 Batch,调用各个平台图形API的Instanced DrawCall,这样会为每个 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,另外一個沒有定義此關鍵字。

UNITY_INSTANCE_ID

  • 用于在 Vertex Shader的输入、输出结构体中定义一个 SV_InstanceID的元素。

UNITY_INSTANCING_CBUFFER_START(name) / UNITY_INSTANCING_CBUFFER_END

  • 每个Instance独有的属性,必须定义在一个遵循特殊命名规则的ConstantBuffer中。
  • 使用这对集合来定义这些ConstantBuffer。
  • “name”参数可以是任何字符串。

UNITY_DEFINE_INSTANCED_PROP(float4, _Color)

  • 定义一个具有特定类型和名字的每个Instance独有的Shader属性。
  • 这个实际会定义一个Uniform矩阵。

UNITY_SETUP_INSTANCE_ID(v)

  • 这个必须在 Vertex Shader的最开始调用。
  • 如果你需要在Fragment Shader中存取Instanced属性。那么也必须在Fragment Shader的开始调用。

UNITY_TRANSFER_INSTANCE_ID(v, o)

  • 存取每个Instance独有的属性。
  • 会使用InstanceID作为索引去访问Uniform矩阵。

UnityObjectToClipPos

  • 在写InstanceShader时,通常时候你不需要在意顶点空间的转换。因为所有内建的矩阵名字在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” 就可以了。

使用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結果為準!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值