前一篇:
- Unity GPU Instancing 自己写个简单的测试用例1 实现了 Unity 中最简单的 Instancing draw 的方式
- Unity GPU Instancing 自己写个简单的测试用例2 在1的基础上做了一丢丢优化
这一篇直接使用 Graphics.DrawInstanced API 来绘制 Instancing 的内容,减少 Hierarchy中 GameObject 的层级树的维护开销
CSharp
using Unity.Collections;
using Unity.Jobs;
using UnityEngine;
using UnityEngine.Rendering;
public class InstancedScript_Solution2 : MonoBehaviour
{
private static int _Color = Shader.PropertyToID("_Color");
private static MaterialPropertyBlock mpb;
public int instance_count = 5;
public Mesh mesh;
public Material mat;
public bool procedurce_fill_data;
private bool last_fill_data = true;
public Color[] col;
public float[] angle_offset;
public Vector3[] pos_offset;
private Matrix4x4[] model_mats;
private Vector4[] col_vec4;
private void Start()
{
mpb = new MaterialPropertyBlock();
UpdateDatas();
}
private void Update()
{
UpdateDatas();
RenderCoins();
}
private void ProcedureFillData()
{
for (int i = 0; i < instance_count; i++)
{
col[i] = new Color(Random.value, Random.value, Random.value);
angle_offset[i] = Random.value * 360.0f;
pos_offset[i] = new Vector3(Random.Range(-2.0f, 2.0f), 0.0f, Random.Range(-2.0f, 2.0f));
}
}
private void UpdateDatas()
{
var count = instance_count;
if (col == null || col.Length != count)
{
col = new Color[count];
}
if (col_vec4 == null || col_vec4.Length != count)
{
col_vec4 = new Vector4[count];
}
if (angle_offset == null || angle_offset.Length != count)
{
angle_offset = new float[count];
}
if (pos_offset == null || pos_offset.Length != count)
{
pos_offset = new Vector3[count];
}
if (model_mats == null || model_mats.Length != count)
{
model_mats = new Matrix4x4[count];
}
if (last_fill_data != procedurce_fill_data)
{
last_fill_data = procedurce_fill_data;
ProcedureFillData();
}
// colors
for (int i = 0; i < instance_count; i++)
{
col_vec4[i] = col[i];
}
mpb.Clear();
mpb.SetVectorArray(_Color, col_vec4);
// update angles
for (int i = 0; i < count; i++)
{
angle_offset[i] += 1.0f;
}
// model materix
for (int i = 0; i < count; i++)
{
var tr =
Matrix4x4.Translate(pos_offset[i]) *
Matrix4x4.Rotate(Quaternion.Euler(new Vector3(0, angle_offset[i], 0)));
model_mats[i] = transform.localToWorldMatrix * tr;
}
}
private void RenderCoins()
{
if (!mat.enableInstancing)
{
mat.enableInstancing = true;
}
Graphics.DrawMeshInstanced(
mesh,
0,
mat,
model_mats,
instance_count,
mpb,
ShadowCastingMode.Off,
false);
}
}
Shader
// author : jave.lin
// Unity 中 实现 Instancing 的 Shader
Shader "Game/CoinInstancingShader2"
{
Properties
{
[NoScaleOffset] _MainTex ("Texture", 2D) = "white" {}
}
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;
float2 uv : TEXCOORD0;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct v2f
{
float4 vertex : SV_POSITION;
float2 uv : TEXCOORD0;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
sampler2D _MainTex;
// SRP 中,可以使用一个 UnityPerMaterial 再裹一层
// 总之这些潜规则要熟悉 Unity 才能知道
// - https://forum.unity.com/threads/materialpropertyblock-with-shader-graph.697868/
// CBUFFER_START(UnityPerMaterial)
UNITY_INSTANCING_BUFFER_START(MyInstancing)
UNITY_DEFINE_INSTANCED_PROP(fixed4, _Color)
UNITY_INSTANCING_BUFFER_END(MyInstancing)
// CBUFFER_END
// unity 的 instancing 参考下面的连接内容
// jave.lin : refer to :
// - https://docs.unity3d.com/Manual/GPUInstancing.html
// UNITY_INSTANCING_BUFFER_START(MyInstancing)
// UNITY_DEFINE_INSTANCED_PROP(float4x4, _MMat)
// UNITY_DEFINE_INSTANCED_PROP(half4, _Color)
// UNITY_INSTANCING_BUFFER_END(MyInstancing)
v2f vert (appdata v)
{
v2f o;
UNITY_SETUP_INSTANCE_ID(v)
UNITY_TRANSFER_INSTANCE_ID(v, o);
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
UNITY_SETUP_INSTANCE_ID(i);
// return tex2D(_MainTex, i.uv);
fixed4 col = tex2D(_MainTex, i.uv);
col.rgb = UNITY_ACCESS_INSTANCED_PROP(MyInstancing, _Color).rgb * col;
return col;
}
ENDCG
}
}
}
在 shader 中可以看到与 1、2篇的 VS 不太一样,此第3篇使用了默认的 unity_ObjectToWorld 的矩阵,而没用像1、2篇中自己声明的 instancing property的方式
但是我们在update时传入矩阵数据
运行效果
可以看到相比1、2篇来说,FPS 降低了
因为我们很多数据需要在 CPU 层计算
而1、2篇的方式是将矩阵构建放在了 GPU 中的 VS 处理的,一般顶点数不多的话,可以这么处理
具体还是需要按情况来选择
当然这种方式直接调用 Graphics.DrawInstanced,所以从 Hierarchy GameObject 层级树维护消耗部分没了
但是 Update 量太大,所以 FPS 才有所下降的,如果我们将 Update 取消掉:
可以看到 1000+ FPS,帧率大幅度提升
添加 enabled_update 开关的 CSharp
using Unity.Collections;
using Unity.Jobs;
using UnityEngine;
using UnityEngine.Rendering;
public class InstancedScript_Solution2 : MonoBehaviour
{
private static int _Color = Shader.PropertyToID("_Color");
private static MaterialPropertyBlock mpb;
public int instance_count = 5;
public Mesh mesh;
public Material mat;
public bool procedurce_fill_data;
private bool last_fill_data = true;
public Color[] col;
public float[] angle_offset;
public Vector3[] pos_offset;
public bool enabled_update = true;
private Matrix4x4[] model_mats;
private Vector4[] col_vec4;
private void Start()
{
mpb = new MaterialPropertyBlock();
UpdateDatas();
}
private void Update()
{
if (enabled_update)
{
UpdateDatas();
}
RenderCoins();
}
private void ProcedureFillData()
{
for (int i = 0; i < instance_count; i++)
{
col[i] = new Color(Random.value, Random.value, Random.value);
angle_offset[i] = Random.value * 360.0f;
pos_offset[i] = new Vector3(Random.Range(-2.0f, 2.0f), 0.0f, Random.Range(-2.0f, 2.0f));
}
}
private void UpdateDatas()
{
var count = instance_count;
if (col == null || col.Length != count)
{
col = new Color[count];
}
if (col_vec4 == null || col_vec4.Length != count)
{
col_vec4 = new Vector4[count];
}
if (angle_offset == null || angle_offset.Length != count)
{
angle_offset = new float[count];
}
if (pos_offset == null || pos_offset.Length != count)
{
pos_offset = new Vector3[count];
}
if (model_mats == null || model_mats.Length != count)
{
model_mats = new Matrix4x4[count];
}
if (last_fill_data != procedurce_fill_data)
{
last_fill_data = procedurce_fill_data;
ProcedureFillData();
}
// colors
for (int i = 0; i < instance_count; i++)
{
col_vec4[i] = col[i];
}
mpb.Clear();
mpb.SetVectorArray(_Color, col_vec4);
// update angles
for (int i = 0; i < count; i++)
{
angle_offset[i] += 1.0f;
}
// model materix
for (int i = 0; i < count; i++)
{
var tr =
Matrix4x4.Translate(pos_offset[i]) *
Matrix4x4.Rotate(Quaternion.Euler(new Vector3(0, angle_offset[i], 0)));
model_mats[i] = transform.localToWorldMatrix * tr;
}
}
private void RenderCoins()
{
if (!mat.enableInstancing)
{
mat.enableInstancing = true;
}
Graphics.DrawMeshInstanced(
mesh,
0,
mat,
model_mats,
instance_count,
mpb,
ShadowCastingMode.Off,
false);
}
}