深入URP之Shader篇14: GPU Instancing

2,425 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第14天,点击查看活动详情

GPU Instancing

必须是同一个模型,材质也必须相同,但材质的参数可以不同(使用MaterialPropertyBlock指定),然后基于一个Instanced Draw Call,一次性绘制多个模型。 参考:docs.unity3d.com/Manual/GPUI…

SRP中几种Bathcing方式的优先级

  • 如果物体是静态的(Batching Static),则会使用Static Batching。如果物体的材质兼容SRP Batcher,则会同时使用SRP Batcher。

  • 如果物体的材质和Renderer兼容GPU Instancing,则会启用GPU Instancing

  • 如果开启了Dynamic Batching,则会使用动态Batch。 由上可知,如果想启用GPU Instancing,必须不能开启Static Batching,且不能满足SRP Batcher条件。 如果材质是兼容GPU Instancing的,且物体是开启了Static Batching的,则Unity会在物体的Inspector中给出提示: 在这里插入图片描述

GPU Instacing开启的条件

  • 首先Shader必须兼容与Instancing。
  • 材质开启 Enable GPU Instancing
  • SRP Batcher的优先级高于GPU Instancing,对于Game Objects,如果SRP Batcher能被使用(Shader兼容SRP Batcher,节点本身也兼容等),则就会使用SRP Batcher,即便材质开启了Enable GPU Instancing也没用。
  • 如果SPR Batcher的条件被破坏,例如使用了MaterialPropertyBlock,且开启了Enable GPU Instancing,则GPU Instancing则会启用。

GPU Instancing的性能

  • GPU Instancing对于顶点数比较少的模型不一定能提高性能,因为顶点数少时GPU不能充分的分配资源去绘制多个实例,这个顶点数的阈值根据不同显卡是不一样的,但一般来说少于256个顶点是不合适的。
  • 如果有很多顶点少的物体需要绘制,可以将他们合并到一个mesh中进行绘制。

自定义Shader兼容GPU Instancing

UNITY_INSTANCING_BUFFER_START(UnityPerMaterial)
    UNITY_DEFINE_INSTANCED_PROP(float4, _BaseColor)
UNITY_INSTANCING_BUFFER_END(UnityPerMaterial)


struct Attributes
{
    float3 positionOS : POSITION;
    UNITY_VERTEX_INPUT_INSTANCE_ID
};

struct Varyings
{
    float4 positionCS : SV_POSITION;
    UNITY_VERTEX_INPUT_INSTANCE_ID
};

Varyings UnlitPassVertex(Attributes input)
{
    Varyings output;
    UNITY_SETUP_INSTANCE_ID(input);
    UNITY_TRANSFER_INSTANCE_ID(input, output);
    float3 positionWS = TransformObjectToWorld(input.positionOS);
    output.positionCS = TransformWorldToHClip(positionWS);
    return output;
}

float4 UnlitPassFragment(Varyings input) : SV_TARGET
{
    UNITY_SETUP_INSTANCE_ID(input);
    return UNITY_ACCESS_INSTANCED_PROP(UnityPerMaterial, _BaseColor);
}
  • 首先,定义PerMaterial Uniform block时,要使用INSTANCING相关的宏UNITY_INSTANCING_BUFFER_STARTUNITY_INSTANCING_BUFFER_ENDUNITY_DEFINE_INSTANCED_PROP。这些宏的作用是将Uniform block定义成数组。需要注意的是,只有当同一个材质下面的不同Instance存在逐Instance不同的属性时,才需要将UnityPerMaterial的CBuffer改成使用这些宏,否则是不需要的,因为这些宏只是为了将属性定义成数组,然后可以使用Instance索引去得到数组里面不同Instance各自的属性。另外,由于CBuffer的名字不能冲突,所以也不能仅仅将不同的属性单独拿出来使用这些宏包裹,如果需要拿出来就得全部拿出来,也就是将SRPBatcher使用的CBuffer的宏替换成这些宏。
  • VS和FS的输入都要使用结构体作为参数,且结构体中需要使用宏UNITY_VERTEX_INPUT_INSTANCE_ID定义逐物体的instance id。
  • 在VS和FS中,都要使用宏UNITY_SETUP_INSTANCE_ID来设置instance id变量,这个宏的作用是使用一个base instance id和input中的instance id组合出一个instance id变量来作为数组索引获取相应的属性值。
  • 如果需要在FS中获取属性,则VS中需要使用宏UNITY_TRANSFER_INSTANCE_ID从input向output传递instance id。然后在FS中使用宏UNITY_ACCESS_INSTANCED_PROP获取属性。当然如果是在VS中获取所有属性进行计算,则不需要传递,直接在VS中使用宏UNITY_ACCESS_INSTANCED_PROP获取属性。
  • 另外,必须要添加#pragma multi_compile_instancing

让Shader不兼容SRP Batcher

使用上面这些宏,其实也就同时兼容了SRP Batcher。如果在Renderer上使用MaterialPropertyBlock,则会破坏SPR Batcher的兼容性,从而可以使用GPU Instancing。但如果不使用MaterialPropertyBlock呢?也可以通过修改Shader来让Shader不兼容于SRP Batcher,方法是在材质的Properties中定义一个属性,但是这个属性不要放到UnityPerMaterial的block中。具体文档是这么说的:

Removing shader compatibility You can make both hand-written and Shader Graph shaders incompatible with the SRP Batcher. However, for Shader Graph shaders, if you change and recompile the Shader Graph often, it’s simpler to make the renderer incompatible instead. To make a Unity shader incompatible with the SRP Batcher, you need to make changes to the shader source file: For hand-written shaders, open the shader source file. For Shader Graph shaders, copy the Shader Graph’s compiled shader source code into a new shader source file. Use the new shader source file in your application instead of the Shader Graph. Add a new material property declaration into the shader’s Properties block. Don’t declare the new material property in the UnityPerMaterial constant buffer. The material property doesn’t need to do anything; just having a material property that doesn’t exist in the UnityPerMaterial constant buffer makes the shader incompatible with the SRP Batcher. Warning: If you use a Shader Graph, be aware that every time you edit and recompile the Shader Graph, you must repeat this process.

但我实际测试发现,这个新定义的属性必须在Shader中用到,否则就不会去除SRP Batcher的兼容,我猜测是Shader代码编译时优化掉了不使用的属性。

参考