Simple understanding of GPU Instancing

GPU Instancing overview:

        Common static and dynamic batch combination are optimized on the CPU side, while GPU Instancing is optimized on the GPU side. The core idea is that it is sometimes a waste to use a Draw Call to make the GPU render an object. It is better to use a Draw Call to make the GPU render a pile of objects (100, 200, or even more) at the right time.

        The working principle of GPU Instancing is to draw multiple objects with the same Mesh and the same Material in the same Draw Call. GPU Instancing can significantly improve the efficiency of rendering buildings, trees, grasslands and other objects using the same materials and meshes. Although GPU Instancing can only render objects with the same Mesh and Material in the same Draw Call, each instance can have different Material parameters (such as color, scale). Using GPU Instancing can significantly improve rendering performance.

        Specifically, using GPU Instancing, Unity will put the attributes (location, uv, etc.) of all objects that meet the requirements within the visible range into the buffer in the GPU, and extract an Object from it as an instance into the rendering process. This Object contains the public information of all objects that meet the requirements (those parts that are the same). After receiving the Draw Call, Part of the shared information of the instance extracted from the video memory and the relevant information of the corresponding Object extracted from the GPU constant buffer are transferred to the next rendering stage.

How to use GPU Instancing:

-Vertex and Fragment shader:

         This is a simple 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
        }
    }
}

        To support   For GPU Instancing, first add a precompiled instruction in the Shader:

#pragma multi_compile_instancing

         Then add this line of code to the definition of appdata:

struct appdata
{
    float4 vertex : POSITION;
    UNITY_VERTEX_INPUT_INSTANCE_ID
};

         Add this line of code to the definition of v2f:

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

         The definition of the variable also needs to be modified. It explicitly tells Unity that this Instance property is declared in a specified Buffer:

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

         Make the following modifications in vert and frag functions:

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);
}

         Whether some statements are added or not depends on the comments.

         The modified code is as follows:

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 effect:

         We assign the above Shader to the new Material, and we can observe a new toggle: Enable GPU Instancing in the Inspector.

         When your Shader supports GPU Instancing, this toggle will be added. We can enable GPU Instancing by opening it.

         There are five kinds of materials in the scene. GPU Instancing is turned on. When there are 15 spheres:  

        Although there are 15 spheres, we can find that the number of draw calls is only 6, one of which is used to draw skybox es. Therefore, only 5 draw calls are required to render 15 spheres. This is the power of GPU Instancing, which greatly reduces the number of draw calls.

         And if we turn off GPU Instancing:

         In addition, the number of objects that GPU Instancing can submit in the same Draw Call is limited, which is caused by the size limit of GPU Constant Buffer.

         When there are 500 spheres in the scene, the rendering can be completed with only one Draw Call:  

         When there are 600 spheres in the scene, two draw calls are required:  

Use MaterialPropertyBlock:

        In the above, we mentioned that GPU Instancing supports rendering objects with the same Mesh and the same Material in a Draw Call, but the Material properties of the Material can be different, such as color, scale, etc. However, unlike the conventional method of directly setting Material properties using Render's Material properties, this method will generate new materials at runtime. We need to use MaterialPropertyBlock to set Material properties.

(Reference) MaterialPropertyBlock_ a Zhao's blog - CSDN blog )

reference resources:  

  1. Static batch, dynamic batch, GPU Instancing [original] static batch processing, dynamic batch processing, GPU Instancinghttp://newhappy.com.cn/index.php/2020/05/14/batch/
  2. U3D optimization batch - GPU Instancing U3D optimization batch processing - GPU Instancing learn about it - knowhttps://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_ a Zhao's blog - CSDN bloghttps://blog.csdn.net/liweizhao/article/details/81937590

Tags: Unity

Posted on Tue, 02 Nov 2021 21:48:42 -0400 by shu