简单认识 GPU Instancing

GPU Instancing 概述:

        常见的静态合批和动态合批都是在 CPU 端做优化,而 GPU Instancing 则是在 GPU 端做优化。其核心思想是,用一个 Draw Call 让 GPU 渲染一个物体有时候很浪费,不如在合适的时机,用一个 Draw Call 让 GPU 渲染一堆物体(100个,200个,甚至更多)。

        GPU Instancing 的工作原理是将具有相同 Mesh 和相同 Material 的多个 Object 的绘制放在同一个 Draw Call 中。GPU Instancing 在提高渲染建筑、树、草地等使用相同的材质和网格的对象的效率方面效果显著。虽然 GPU Instancing 只能把具有相同的 Mesh 和 Material 的对象放到同一个 Draw Call 中渲染,但是每个实例可以有不同的材质参数(例如 color,scale)。使用 GPU Instancing 可以显著提高渲染性能。

        具体来说使用 GPU Instancing,Unity 会将可见范围内的所有符合要求的 Object 的对象的属性(位置,uv等)放入到 GPU 中的缓冲区中,从中抽取一个对象作为实例送入渲染流程,这个对象包含了所有符合要求对象的公共信息(那些它们相同的部分),收到 Draw Call 之后,从显存中取出实例的部分共享信息与从GPU常量缓冲器中取出对应对象的相关信息一并传递到下一渲染阶段。


如何使用GPU Instancing

-Vertex and Fragment shader:

        这是一个简简单单的 Vertex and Fragment shader:

Shader "Custom/GPUInstancingSupport"
{
    Properties
    {
        _Color ("Color", Color) = (1, 1, 1, 1)
    }

    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
            };

            float4 _Color;

            v2f vert(appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                return UNITY_ACCESS_INSTANCED_PROP(Props, _Color);
            }
            ENDCG
        }
    }
}

        为了支持 GPU Instancing,首先要在 Shader 中添加一条预编译指令:

#pragma multi_compile_instancing

        然后要在 appdata 的定义中添加这行代码:

struct appdata
{
    float4 vertex : POSITION;
    UNITY_VERTEX_INPUT_INSTANCE_ID
};

        在 v2f 的定义中添加这行代码:

struct v2f
{
    float4 vertex : SV_POSITION;
    UNITY_VERTEX_INPUT_INSTANCE_ID // necessary only if you want to access instanced properties in fragment Shader.
};

        变量的定义也需要修改,显式地告诉 Unity 这个 Instance property 被声明在一个指定 Buffer 中:

UNITY_INSTANCING_BUFFER_START(Props)
    UNITY_DEFINE_INSTANCED_PROP(float4, _Color)
UNITY_INSTANCING_BUFFER_END(Props)

        在 vert 和 frag 函数中做如下修改:

v2f vert(appdata v)
{
    v2f o;
    UNITY_SETUP_INSTANCE_ID(v);
    UNITY_TRANSFER_INSTANCE_ID(v, o); // necessary only if you want to access instanced properties in the fragment Shader.
    o.vertex = UnityObjectToClipPos(v.vertex);
    return o;
}

fixed4 frag(v2f i) : SV_Target
{
    UNITY_SETUP_INSTANCE_ID(i); // necessary only if any instanced properties are going to be accessed in the fragment Shader.
    return UNITY_ACCESS_INSTANCED_PROP(Props, _Color);
}

        部分语句的添加与否看注释。

        修改后代码如下:

Shader "Custom/GPUInstancingSupport"
{
    Properties
    {
        _Color ("Color", Color) = (1, 1, 1, 1)
    }

    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_instancing
            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
                UNITY_VERTEX_INPUT_INSTANCE_ID // necessary only if you want to access instanced properties in fragment Shader.
            };

            UNITY_INSTANCING_BUFFER_START(Props)
            UNITY_DEFINE_INSTANCED_PROP(float4, _Color)
            UNITY_INSTANCING_BUFFER_END(Props)

            v2f vert(appdata v)
            {
                v2f o;
                UNITY_SETUP_INSTANCE_ID(v);
                UNITY_TRANSFER_INSTANCE_ID(v, o); // necessary only if you want to access instanced properties in the fragment Shader.
                o.vertex = UnityObjectToClipPos(v.vertex);
                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                UNITY_SETUP_INSTANCE_ID(i); // necessary only if any instanced properties are going to be accessed in the fragment Shader.
                return UNITY_ACCESS_INSTANCED_PROP(Props, _Color);
            }
            ENDCG
        }
    }
}

GPU Instancing使用效果:

        我们将上述 Shader 赋值给新建的 Material,可以在 Inspector 中观察到有一个新的 toggle:Enable GPU Instancing。

        当你的 Shader 支持 GPU Instancing 时,会添加这个 toggle,我们打开它就能启用 GPU Instancing。

        场景中有 5 种材质,均打开 GPU Instancing,15个 Sphere 时: 

        虽然 Sphere 数量有 15 个,但我们可以发现 Draw Call 数量仅仅为 6,其中有一个是用于画 skybox 的,因此渲染 15 个 Sphere 一共只需要 5 个 Draw Call,这就是 GPU Instancing 的力量,大幅减少了 Draw Call。

        而如果我们关闭 GPU Instancing:

        此外,GPU Instancing 能在同一 Draw Call 中提交的物体数量是有限的,这是由 GPU Constant Buffer 大小限制导致的。

        当场景中有 500 个 Sphere 时,只用一个 Draw Call 就可以渲染完成: 

        当场景中有 600 个 Sphere 时,需要两个 Draw Call: 


使用 MaterialPropertyBlock:

        在上文中,我们提到 GPU Instancing 支持在一个 Draw Call 里渲染具有相同 Mesh 相同 Material 的 Object,但 Material 的材质属性可以不一样,比如 color,scale 等。但不同于常规的使用 Render 的 material 属性直接设置材质属性,这种方式会在运行时生成新的 Material,我们需要使用 MaterialPropertyBlock 来设置材质属性。

(参考 MaterialPropertyBlock_阿赵的博客-CSDN博客


参考: 

  1. 静态批处理、动态批处理、GPU Instancing [原创]静态批处理、动态批处理、GPU Instancinghttp://newhappy.com.cn/index.php/2020/05/14/batch/
  2. U3D优化批处理-GPU Instancing了解一下 U3D优化批处理-GPU Instancing了解一下 - 知乎https://zhuanlan.zhihu.com/p/34499251
  3. Unity Documentation Manual: GPU Instancing Unity - Manual: GPU instancinghttps://docs.unity3d.com/2019.4/Documentation/Manual/GPUInstancing.html
  4. MaterialPropertyBlock MaterialPropertyBlock_阿赵的博客-CSDN博客https://blog.csdn.net/liweizhao/article/details/81937590


版权声明:本文为zionFisher原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。