使用 GPU 实例化可使用少量绘制调用一次绘制(或渲染)同一网格的多个副本。它对于绘制诸如建筑物、树木和草地之类的在场景中重复出现的对象非常有用。
GPU 实例化在每次绘制调用时仅渲染相同的网格,但每个实例可以具有不同的参数(例如,颜色或比例)以增加变化并减少外观上的重复。
GPU 实例化可以降低每个场景使用的绘制调用数量。可以显著提高项目的渲染性能。
要对材质启用 GPU 实例化 (GPU Instancing),请在 Project 窗口中选择材质,然后在 Inspector 中勾选 Enable Instancing 复选框。
仅当材质着色器支持 GPU 实例化时,Unity 才会显示此复选框。这包括标准 (Standard)、标准镜面反射 (StandardSpecular) 和所有表面着色器。请参阅有关标准着色器的文档以了解更多信息。
下面的截屏显示了具有多个游戏对象的相同场景;在顶部图像中,启用了 GPU 实例化,在底部图像中没有启用。请注意 FPS、Batches 和 Saved by batching 中的差异。
使用 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 11 和 DirectX 12
Windows、macOS、Linux、iOS 和 Android 上的 OpenGL Core 4.1+/ES3.0+
macOS 和 iOS 上的 Metal
Windows 和 Android 上的 Vulkan
PlayStation 4 和 Xbox 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 "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_EN D |
必须在特殊命名的常量缓冲区中定义每个实例的属性。使用这对宏来包装对每个实例唯一的属性。 |
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
中,因为这会禁用实例化。请为非实例化属性创建不同材质。
进行批处理时,Unity 将优先处理静态批处理,然后再处理实例化。如果您将其中一个游戏对象标记为静态批处理,并且 Unity 成功对其进行批处理,则 Unity 会禁用该游戏对象的实例化,即使其渲染器使用实例化着色器也是如此。发生这种情况时,Inspector 窗口将显示一条警告消息,建议您禁用静态批处理。要禁用静态批处理,请打开 Player Settings (Edit > Project Settings > Player),打开 Other Settings__,然后在 Rendering__ 部分下面取消勾选 Static Batching 复选框。
Unity 将优先处理实例化,然后再处理动态批处理。如果 Unity 可以实例化网格,则会对该网格禁用动态批处理。
某些因素可能会阻止游戏对象同时自动实例化。这些因素包括材质变化和深度排序。使用 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.
在脚本中使用 DrawMeshInstancedIndirect
可从计算缓冲区中读取实例化绘制调用的参数,包括实例数量。如果要从 GPU 填充所有实例数据,并且 CPU 不知道要绘制的实例数(例如,执行 GPU 剔除时),这非常有用。请参阅 Graphics.DrawMeshInstancedIndirect 的 API 文档以了解详细说明和代码示例。
#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 |
用于命令 Unity 生成额外变体以用于 Graphics.DrawMeshInstancedIndirect。 在顶点着色器阶段开始时,Unity 调用在冒号后面指定的函数。要手动设置实例数据,请按照通常将每个实例数据添加到着色器的方式将每个实例数据添加到此函数。如果片元着色器中包含任何获取的实例属性,Unity 还会在片元着色器的开头调用此函数。 |
编写着色器脚本时,请务必使用 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 页面已修订并进行了编辑审查
在 5.6 版中增加了 Enable Instancing 复选框指南、DrawMeshInstancedIndirect 和 #pragma multi-compile