unity-shader-tutorial-gpuInstancing

原理

启用GPU Instancing的对象信息会被放入显卡的常量缓冲区(Constant Buffer)。然后从中抽取一个对象作为实例出送入渲染流程,当在执行Draw call操作后,从显存中取出实例的部分共享信息与从GPU常量缓冲器中取出对应对象的相关信息一并传递到下一渲染阶段,因此不同的着色器阶段可以从缓存区中之间获取到需要的常量,不用设置两次常量。

使用

buildin效果图

urp效果图

GPUInstancing.shader

GPUInstancing.shader
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
Shader "HHF/Tutorial/GPUInstancing"
{
Properties
{
_Color("Color",Color) = (1,1,1,1)
}

SubShader
{
Tags { "RenderPipeline"="UniversalPipeline" }

Pass
{
HLSLPROGRAM
#pragma prefer_hlslcc gles
#pragma exclude_renderers d3d11_9x
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_instancing

#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Color.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/ShaderGraphFunctions.hlsl"

struct appdata
{
float4 vertex : POSITION;

#ifdef UNITY_INSTANCING_ENABLED
// 在顶点着色器的输入定义instanceID
UNITY_VERTEX_INPUT_INSTANCE_ID
#endif
};

struct v2f
{
float4 positionCS : SV_POSITION;

#ifdef UNITY_INSTANCING_ENABLED
// 在顶点着色器的输出定义instanceID
UNITY_VERTEX_INPUT_INSTANCE_ID
#endif
};

#ifdef UNITY_INSTANCING_ENABLED
// 声明常量寄存器,并且在其中存储需要的变量属性
UNITY_INSTANCING_BUFFER_START(prop)
UNITY_DEFINE_INSTANCED_PROP(vector, _Color)
UNITY_INSTANCING_BUFFER_END(prop)
#endif

CBUFFER_START(UnityPerMaterial)
half4 _Color;
CBUFFER_END

v2f vert (appdata v)
{
v2f o = (v2f) 0;

// 在顶点着色器中设置相应的instanceID,默认的坐标变换就已经支持了
UNITY_SETUP_INSTANCE_ID(v);
// 把instanceID从顶点着色器传到片断着色器
UNITY_TRANSFER_INSTANCE_ID(v, o);

o.positionCS = TransformObjectToHClip(v.vertex.xyz);
return o;
}

half4 frag (v2f i) : SV_Target
{
// 在片断着色器中设置相应的instanceID
UNITY_SETUP_INSTANCE_ID(i);

half4 c = 0;

#ifdef UNITY_INSTANCING_ENABLED
// UNITY_ACCESS_INSTANCED_PROP,调用常量寄存器中的属性值
half4 instColor = UNITY_ACCESS_INSTANCED_PROP(prop, _Color);
c = instColor;
#endif

return 1 - c;
}

ENDHLSL
}
}

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

Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_instancing

#include "UnityCG.cginc"

struct appdata
{
float4 vertex : POSITION;

#ifdef UNITY_INSTANCING_ENABLED
// 在顶点着色器的输入定义instanceID
UNITY_VERTEX_INPUT_INSTANCE_ID
#endif
};

struct v2f
{
float4 positionCS : SV_POSITION;

#ifdef UNITY_INSTANCING_ENABLED
// 在顶点着色器的输出定义instanceID
UNITY_VERTEX_INPUT_INSTANCE_ID
#endif
};

#ifdef UNITY_INSTANCING_ENABLED
// 声明常量寄存器,并且在其中存储需要的变量属性
UNITY_INSTANCING_BUFFER_START(prop)
UNITY_DEFINE_INSTANCED_PROP(vector, _Color)
UNITY_INSTANCING_BUFFER_END(prop)
#endif

v2f vert (appdata v)
{
v2f o = (v2f)0;

// 在顶点着色器中设置相应的instanceID,默认的坐标变换就已经支持了
UNITY_SETUP_INSTANCE_ID(v);
// 把instanceID从顶点着色器传到片断着色器
UNITY_TRANSFER_INSTANCE_ID(v, o);

o.positionCS = UnityObjectToClipPos(v.vertex);
return o;
}

half4 frag (v2f i) : SV_Target
{
// 在片断着色器中设置相应的instanceID
UNITY_SETUP_INSTANCE_ID(i);

half4 c = 0;

#ifdef UNITY_INSTANCING_ENABLED
// UNITY_ACCESS_INSTANCED_PROP,调用常量寄存器中的属性值
half4 instColor = UNITY_ACCESS_INSTANCED_PROP(prop, _Color);
c = instColor;
#endif

return c;
}
ENDCG
}
}
}

GPUInstancing.cs

GPUInstancing.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
using System;
using UnityEngine;
using Random = UnityEngine.Random;

public class GPUInstancing : MonoBehaviour
{
public GameObject Prefab; // 预制体
public int Count = 2000; // 实力数量
public float Range = 30; // 创建范围

private Material mat;
private Mesh mesh;
private Matrix4x4[] arrMatrix4X4s;
private MaterialPropertyBlock materialPropertyBlock;

private void OnGUI()
{
if (GUI.Button(new Rect(100, 100, 200, 200), "直接创建预制体实例"))
{
for (int i = 0; i < Count; i++)
{
Vector2 pos = Random.insideUnitCircle * Range;
Quaternion rot = Quaternion.Euler(0, Random.Range(0, 360), 0);
Vector3 scale = Vector3.one * Random.Range(0.5f, 2);
GameObject tree = Instantiate(Prefab, new Vector3(pos.x, 0, pos.y), rot);
tree.transform.localScale = scale;

// 通过材质属性块去修改每一个材质的属性
// 这设置代码非常重要,没有gpu instance 不生效。
Color newCol = new Color(Random.value, Random.value, Random.value);
MaterialPropertyBlock prop = new MaterialPropertyBlock();
prop.SetColor("_Color", newCol);
tree.GetComponentInChildren<MeshRenderer>().SetPropertyBlock(prop);
}
} else if (GUI.Button(new Rect(100, 400, 200, 200), "Graphics创建实例"))
{
mesh = Prefab.GetComponentInChildren<MeshFilter>().sharedMesh;
mat = Prefab.GetComponentInChildren<MeshRenderer>().sharedMaterial;
arrMatrix4X4s = new Matrix4x4[Count];

materialPropertyBlock = new MaterialPropertyBlock();
Vector4[] arrColor = new Vector4[Count];
for (int i = 0; i < Count; i++)
{
Vector2 pos = Random.insideUnitCircle * Range;
Quaternion rot = Quaternion.Euler(0, Random.Range(0, 360), 0);
Vector3 scale = Vector3.one * Random.Range(0.5f, 2);

arrMatrix4X4s[i] = Matrix4x4.identity;
arrMatrix4X4s[i].SetTRS(new Vector3(pos.x, 0, pos.y), rot, scale);
arrColor[i] = new Vector4(Random.value, Random.value, Random.value, 1);
}

materialPropertyBlock.SetVectorArray("_Color", arrColor);
}

}

private void Update()
{
if (arrMatrix4X4s == null)
{
return;
}

// graphic创建
// 一条graphics指令最多可以画1023个对象
// 一个批次最多可以合批511个对象
const int GRAPHICS_MESH_NUM = 511 * 2;
int remain = arrMatrix4X4s.Length;
int times = Mathf.CeilToInt(arrMatrix4X4s.Length * 1.0f / GRAPHICS_MESH_NUM);

for (int i = 0; i < times; i++)
{
if (remain >= GRAPHICS_MESH_NUM)
{
Matrix4x4[] m = new Matrix4x4[GRAPHICS_MESH_NUM];
Array.ConstrainedCopy(arrMatrix4X4s, i * GRAPHICS_MESH_NUM, m, 0, GRAPHICS_MESH_NUM);
Graphics.DrawMeshInstanced(mesh, 0, mat, m, GRAPHICS_MESH_NUM, materialPropertyBlock);
remain -= GRAPHICS_MESH_NUM;
}
else
{
Matrix4x4[] m = new Matrix4x4[remain];
Array.ConstrainedCopy(arrMatrix4X4s, i * GRAPHICS_MESH_NUM, m, 0, remain);
Graphics.DrawMeshInstanced(mesh, 0, mat, m, remain, materialPropertyBlock);
}
}
}
}