Version: 5.6
图形命令缓冲区
稀疏纹理

GPU 实例化

简介

使用 GPU 实例化可使用少量绘制调用一次绘制(或渲染)同一网格的多个副本。它对于绘制诸如建筑物、树木和草地之类的在场景中重复出现的对象非常有用。

GPU 实例化在每次绘制调用时仅渲染相同的网格,但每个实例可以具有不同的参数(例如,颜色或比例)以增加变化并减少外观上的重复。

GPU 实例化可以降低每个场景使用的绘制调用数量。可以显著提高项目的渲染性能。

为材质添加实例化

要对材质启用 GPU 实例化 (GPU Instancing),请在 Project 窗口中选择材质,然后在 Inspector 中勾选 Enable Instancing 复选框。

材质检视面板 (Inspector) 窗口中显示的 Enable Instancing 复选框
材质检视面板 (Inspector) 窗口中显示的 Enable Instancing 复选框

仅当材质着色器支持 GPU 实例化时,Unity 才会显示此复选框。这包括标准 (Standard)、标准镜面反射 (StandardSpecular) 和所有表面着色器。请参阅有关标准着色器的文档以了解更多信息。

下面的截屏显示了具有多个游戏对象的相同场景;在顶部图像中,启用了 GPU 实例化,在底部图像中没有启用。请注意 FPSBatches Saved by batching 中的差异。

With GPU Instancing: A simple Scene that includes multiple identical GameObjects that have GPU Instancing enabled
With GPU Instancing: A simple Scene that includes multiple identical GameObjects that have GPU Instancing enabled
No GPU Instancing: A simple Scene that includes multiple identical GameObjects that do not have GPU Instancing enabled.
No GPU Instancing: A simple Scene that includes multiple identical GameObjects that do not have GPU Instancing enabled.

使用 GPU 实例化时,存在以下限制:

  • Unity 自动选取要实例化的网格渲染器组件和 Graphics.DrawMesh 调用。请注意,不支持 SkinnedMeshRenderer

  • Unity 仅在单个 GPU 实例化绘制调用中批量处理那些共享相同网格和相同材质的游戏对象。使用少量网格和材质可以提高实例化效率。要创建变体,请修改着色器脚本为每个实例添加数据(请参阅下一部分了解有关此内容的更多信息)。

You can also use the calls Graphics.DrawMeshInstanced and Graphics.DrawMeshInstancedIndirect. to perform GPU Instancing from your scripts.

GPU 实例化可在以下平台和 API 上使用:

  • Windows 上的 DirectX 11DirectX 12

  • Windows、macOS、Linux、iOS 和 Android 上的 OpenGL Core 4.1+/ES3.0+

  • macOS 和 iOS 上的 Metal

  • Windows 和 Android 上的 Vulkan

  • PlayStation 4Xbox One

  • __WebGL__(需要 WebGL 2.0 API)

添加每个实例的数据

默认情况下,仅当游戏对象在每个实例化绘制调用中具有不同的变换时,Unity 才会对这些游戏对象的实例进行批处理。要为实例化的游戏对象添加更多变体,请修改着色器以添加每实例属性,例如材质颜色。

下面的示例演示了如何为每个实例创建具有不同颜色值的实例化着色器。


Shader "Custom/InstancedColorSurfaceShader" {
    Properties {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0
    }

    SubShader {
        Tags { "RenderType"="Opaque" }
        LOD 200
        CGPROGRAM
        // Physically based Standard lighting model, and enable shadows on all light types
        #pragma surface surf Standard fullforwardshadows
        // Use Shader model 3.0 target
        #pragma target 3.0
        sampler2D _MainTex;
        struct Input {
            float2 uv_MainTex;
        };
        half _Glossiness;
        half _Metallic;
        UNITY_INSTANCING_CBUFFER_START(Props)
           UNITY_DEFINE_INSTANCED_PROP(fixed4, _Color)
        UNITY_INSTANCING_CBUFFER_END
        void surf (Input IN, inout SurfaceOutputStandard o) {
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * UNITY_ACCESS_INSTANCED_PROP(_Color);
            o.Albedo = c.rgb;
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

When you declare _Color as an instanced property, Unity takes all _Color GameObjects that share a Mesh and Material and includes them in a single draw call.


MaterialPropertyBlock props = new MaterialPropertyBlock();
MeshRenderer renderer;

foreach (GameObject obj in objects)
{
   float r = Random.Range(0.0f, 1.0f);
   float g = Random.Range(0.0f, 1.0f);
   float b = Random.Range(0.0f, 1.0f);
   props.SetColor("_Color", new Color(r, g, b));
   
   renderer = obj.GetComponent<MeshRenderer>();
   renderer.SetPropertyBlock(props);
}

要使这些更改生效,必须启用 GPU 实例化。为此,请在 Project 窗口中选择您的着色器,然后在 Inspector 中勾选 Enable Instancing 复选框。

着色器检视面板 (Shader Inspector) 窗口中显示的 Enable Instancing 复选框
着色器检视面板 (Shader Inspector) 窗口中显示的 Enable Instancing 复选框

向顶点和片元着色器中添加实例化

下面的示例采用简单的无光照着色器,并使其能够使用不同的颜色进行实例化:


Shader "SimplestInstancedShader"
{
    Properties
    {
        _Color ("Color", Color) = (1, 1, 1, 1)
    }

    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        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_CBUFFER_START(MyProperties)
                UNITY_DEFINE_INSTANCED_PROP(float4, _Color)
            UNITY_INSTANCING_CBUFFER_END
           
            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(_Color);
            }
            ENDCG
        }
    }
}

着色器修改

添加 功能
#pragma multi_compile_instancing 用于命令 Unity 生成实例化变体。对于表面着色器来说是不需要的。
UNITY_VERTEX_INPUT_INSTANCE_ID 用于在顶点着色器输入/输出结构中定义实例 ID。请参阅 SV_InstanceID 以了解更多信息。
UNITY_INSTANCING_CBUFFER_START(name) / UNITY_INSTANCING_CBUFFER_END 必须在特殊命名的常量缓冲区中定义每个实例的属性。使用这对宏来包装对每个实例唯一的属性。
UNITY_DEFINE_INSTANCED_PROP(float4, _Color) 用于根据类型和名称定义每个实例着色器的属性。在此示例中,_Color 属性是唯一的。
UNITY_SETUP_INSTANCE_ID(v); 用于使着色器函数可以访问实例 ID。它必须在顶点着色器的最开头使用,并且对于片元着色器是可选的。
UNITY_TRANSFER_INSTANCE_ID(v, o); 用于将实例 ID 从顶点着色器的输入结构体复制到输出结构体中。仅当需要访问片元着色器中的每个实例的数据时才有必要这样做。
UNITY_ACCESS_INSTANCED_PROP(color) Use this to access a per-instance Shader property. It uses an instance ID to index into the instance data array.

注意

  • 使用多个每个实例的属性时,不需要在 MaterialPropertyBlocks 中填入所有这些属性。

  • 如果一个实例缺少该属性,Unity 将从引用的材质中获取默认值。如果材质没有该指定属性的默认值,Unity 会将值设置为 0。请勿将非实例化属性放在 MaterialPropertyBlock 中,因为这会禁用实例化。请为非实例化属性创建不同材质。

高级 GPU 实例化技巧

批处理优先级

进行批处理时,Unity 将优先处理静态批处理,然后再处理实例化。如果您将其中一个游戏对象标记为静态批处理,并且 Unity 成功对其进行批处理,则 Unity 会禁用该游戏对象的实例化,即使其渲染器使用实例化着色器也是如此。发生这种情况时,Inspector 窗口将显示一条警告消息,建议您禁用静态批处理。要禁用静态批处理,请打开 Player Settings (Edit > Project Settings > Player),打开 Other Settings__,然后在 Rendering__ 部分下面取消勾选 Static Batching 复选框。

Unity 将优先处理实例化,然后再处理动态批处理。如果 Unity 可以实例化网格,则会对该网格禁用动态批处理。

Graphics.DrawMeshInstanced

某些因素可能会阻止游戏对象同时自动实例化。这些因素包括材质变化和深度排序。使用 Graphics.DrawMeshInstanced 可强制 Unity 使用 GPU 实例化来绘制这些对象。类似于 Graphics.DrawMesh,此函数为一帧绘制网格,不会创建不必要的游戏对象。

Do not submit batches of instances that exceed the maxcount specified in your Shader script (500 by default). When using graphics tools from OpenGL or Metal, Unity divides the maxcount by 4, and uses the result as the maximum number of batches you can submit. The recommended practice for drawing arbitrary number of instances is to maintain a pool of pre-allocated 500-sized arrays (and MaterialPropertyBlocks if needed) and reuse these arrays as much as possible. See documentation on Automatic Memory Management for more information about object pooling.

Graphics.DrawMeshInstancedIndirect

在脚本中使用 DrawMeshInstancedIndirect 可从计算缓冲区中读取实例化绘制调用的参数,包括实例数量。如果要从 GPU 填充所有实例数据,并且 CPU 不知道要绘制的实例数(例如,执行 GPU 剔除时),这非常有用。请参阅 Graphics.DrawMeshInstancedIndirect 的 API 文档以了解详细说明和代码示例。

pragma instancing_options

#pragma instancing_options 指令可以使用以下开关:

开关 功能
maxcount: batchSize Use this to specify the maximum number of instances to draw in one instanced draw call. By default this value is 500. When using OpenGL or Metal, Unity divides the maxcount by 4, and uses the result as the maximum number of batches you can submit. Make this value as small as possible to match the amount of instances you want to draw. For example, to draw a maximum of 1000 instances, add maxcount: 1000 to your shader script. Larger values increase Shader compilation time, and can reduce GPU performance.
force_same_maxcount_for_gl Use this to force Unity to stop dividing the maxcount by 4 on graphics tools from OpenGL or Metal.
assumeuniformscaling 用于命令 Unity 假设所有实例都具有统一的缩放(所有 X、Y 和 Z 轴的比例相同)。
lodfade Use this to make LOD fade values instanceable. This is useful for SpeedTree and other LOD techniques that use the LOD fading feature.
procedural:FunctionName Use this to instruct Unity to generate an additional variant for use with Graphics.DrawMeshInstancedIndirect.
At the beginning of the vertex Shader stage, Unity calls the function specified after the colon. To set up the instance data manually, add per-instance data to this function in the same way you would normally add per-instance data to a Shader. Unity also calls this function at the beginning of a fragment Shader if any of the fetched instance properties are included in the fragment Shader.

UnityObjectToClipPos

编写着色器脚本时,请务必使用 UnityObjectToClipPos(v.vertex) 而非 mul(UNITY_MATRIX_MVP,v.vertex)

虽然您可以继续在实例化的着色器中正常使用 UNITY_MATRIX_MVP,但 UnityObjectToClipPos 是将顶点位置从对象空间转换为裁剪空间的最有效方法。Unity 还实现了一个着色器升级程序,此程序可扫描项目中的所有着色器,并自动用 UnityObjectToClipPos(v) 替换任何出现的 mul(UNITY_MATRIX_MVP, v)

如果仍有某些地方在使用 UNITY_MATRIX_MVP(以及 UNITY_MATRIX_MV),控制台窗口(菜单:__Window__ > __Console__)会显示性能警告。

其他注意事项

  • 表面着色器具有默认情况下生成的实例化变体,除非您在 #pragma 表面指令中指定 noinstancing。标准着色器和标准镜面反射着色器已经过修改以支持实例化,但除了变换之外并没有定义每个实例的属性。Unity 将在表面着色器中忽略 #pragma multi_compile_instancing 的使用。

  • 如果未在场景中的任何游戏对象上启用 GPU 实例化,Unity 会剥离实例化变体。要覆盖此剥离行为,请打开 Graphics Settings(菜单:__Edit__ > Project Settings > Graphics__),找到 Shader stripping__ 部分,并更改 Instancing Variants

  • For Graphics.DrawMeshInstanced, you need to enable GPU Instancing on the Material that is being passed into this method. However, Graphics.DrawMeshInstancedIndirect does not require you to enable GPU Instancing. The indirect instancing keyword PROCEDURAL_INSTANCING_ON is not affected by stripping.

  • 实例化的绘制调用在帧调试器 (Frame Debugger) 中显示为 Draw Mesh (instanced)

  • 并非总是需要定义每个实例的属性。但是,必须设置实例 ID,因为世界矩阵需要它才能正常运行。表面着色器会自动设置实例 ID。您必须手动设置自定义顶点和片元着色器的实例 ID。为此,请在着色器开头使用 UNITY_SETUP_INSTANCE_ID

  • 使用前向渲染时,Unity 无法有效地实例化受多个光源影响的对象。只有基础 pass 能有效使用实例化,但添加的 pass 不行。有关光照 pass 的更多信息,请参阅有关前向渲染Pass 标签的文档

  • Objects that use lightmaps, or are affected by different light or Reflection Probes, can’t be instanced.

  • 如果多 pass 着色器有两个以上的 pass,则只有第一个 pass 可以实例化。这是因为 Unity 强制将后续 pass 针对每个对象一起渲染,从而强制更改材质。

  • 以上示例中使用的所有着色器宏都在 UnityInstancing.cginc 中定义。请在以下目录中查找此文件:[Unity 安装文件夹]\Editor\Data\CGIncludes


  • 2017–05–18 Page amended with editorial review - Leave page feedback

  • 在 5.6 版中增加了 Enable Instancing 复选框指南、DrawMeshInstancedIndirect 和 #pragma multi-compile

图形命令缓冲区
稀疏纹理