You can use GPU instancing to draw many identical objects with only a few draw calls. There are some restrictions which you need to bear in mind:
There is a Standard Surface Shader available that supports instancing. Add one to your project by selecting Shader > Standard Surface Shader (Instanced).
Apply this shader to your GameObject’s Material. In your Material’s Inspector window, click the Shader drop-down, roll over the Instanced field and choose your instanced shader from the list:
Even though the instanced objects are sharing the same mesh and material, you can set shader properties on a per-object basis using the MaterialPropertyBlock API. In the example below, each object is assigned a random color value using the _Color property:
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);
}
Let’s take a simple unlit shader and make it capable of 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_INSTANCE_ID
};
struct v2f
{
float4 vertex : SV_POSITION;
UNITY_INSTANCE_ID
};
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);
o.vertex = UnityObjectToClipPos (v.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
UNITY_SETUP_INSTANCE_ID (i);
return UNITY_ACCESS_INSTANCED_PROP (_Color);
}
ENDCG
}
}
}
Addition: | Function: |
---|---|
#pragma multi_compile_instancing | multi_compile_instancing generates a shader with two variants: one with built-in keyword INSTANCING_ON defined (allowing instancing), the other with nothing defined. This allows the shader to fall back to a non-instanced version if instancing isn’t supported on the GPU. |
UNITY_INSTANCE_ID | This is used in the vertex shader input/output structure to define an instance ID. See SV_InstanceID for more information. |
UNITY_INSTANCING_CBUFFER_START(name) / UNITY_INSTANCING_CBUFFER_END | Every per-instance property must be defined in a specially named constant buffer. Use this pair of macros to wrap the properties you want to be made unique to each instance. |
UNITY_DEFINE_INSTANCED_PROP(float4, color) | This defines a per-instance shader property with a type and a name. In this example the color property is unique. |
UNITY_SETUP_INSTANCE_ID(v); | This makes the instance ID accessible to shader functions. It must be used at the very beginning of a vertex shader, and is optional for fragment shaders. |
UNITY_TRANSFER_INSTANCE_ID(v, o); | This copies the instance ID from the input structure to the output structure in the vertex shader. This is only necessary if you need to access per-instance data in fragment shader. |
UNITY_ACCESS_INSTANCED_PROP(color) | This accesses a per-instance shader property. It uses instance ID to index into instance data array. |
Note: As long as material properties are instanced, renderers can always be rendered instanced, even if you put different instanced properties into different renderers. Normal “non-instanced” properties cannot be batched, so do not put them in the MaterialPropertyBlock; instead, create different materials for them.
UnityObjectToClipPos(v.vertex) is always preferred where mul(UNITY_MATRIX_MVP,v.vertex) would otherwise be used. While you can continue to use UNITY_MATRIX_MVP as normal in instanced shaders, UnityObjectToClipPos is the most efficient way of transforming vertex positions from object space into clip space.
In instanced shaders, UNITY_MATRIX_MVP (among other built-in matrices) is transparently modified to include an extra matrix multiply. Specifically, it is expanded to mul(UNITY_MATRIX_VP, unity_ObjectToWorld). unity_ObjectToWorld is expanded to unity_ObjectToWorldArray[unity_InstanceID]). UnityObjectToClipPos is optimized to perform 2 matrix-vector multiplications simultaneously, and is therefore more efficient than performing the multiplication manually as the shader compiler will not automatically perform this optimization.