这里先说明一下,我是在下班时间,挤牙膏的时间出来半小时,写了这2、3、4篇的内容
其实还有一大堆打杂期间学习的内容没有写到博客
上班学习的速度,确实没有猫在家里学习的速度快
前一篇:
- Unity GPU Instancing 自己写个简单的测试用例1 实现了 Unity 中最简单的 Instancing draw 的方式
- Unity GPU Instancing 自己写个简单的测试用例2 在1的基础上做了一丢丢优化
- Unity GPU Instancing 自己写个简单的测试用例3 在2的基础上做了一丢丢优化
这一篇直接使用 Graphics.DrawInstanced API + IJobParallelFor 并行执行多线程来 update 数据
Shader
沒改过,和第3篇的一模一样
主要是 CSharp update 数据的方式变了,改用 IJobParallelFor
CSharp
using Unity.Collections;
using Unity.Jobs;
using UnityEngine;
using UnityEngine.Rendering;
/// <summary>
/// author : jave.lin
/// Unity GPU Instancing 方案3
/// </summary>
public class InstancedScript_Solution3 : MonoBehaviour
{
public struct ParallelUpdateDataJob : IJobParallelFor
{
public NativeArray<Color> colors;
public NativeArray<Vector4> colors_vec4;
public NativeArray<float> angle_offset;
public NativeArray<Vector3> pos_offset;
public NativeArray<Matrix4x4> model_mats;
public Matrix4x4 parent_model_mat;
public void Execute(int index)
{
colors_vec4[index] = colors[index];
var angle = angle_offset[index];
angle += 1.0f;
angle = angle % 360.0f;
angle_offset[index] = angle;
var t = Matrix4x4.Translate(pos_offset[index]);
var r = Matrix4x4.Rotate(Quaternion.Euler(new Vector3(0, angle_offset[index], 0)));
model_mats[index] = parent_model_mat * t * r;
}
}
private static int _Color = Shader.PropertyToID("_Color");
private static MaterialPropertyBlock mpb;
public int instance_count = 5;
public Mesh mesh; // 用于 GPU instance 的 Mesh
public Material mat; // 用于 GPU instance 的 Material
public Camera cam; // 指定要渲染的相机,如果 cam == null,那意味着所有相机都会渲染,如果需要在多个指定的相机渲染,这里可以声明为:Camera[] 数组即可
//public LayerMask layer; // Instancing 每个实例对象所属的 LayerMask 层级,配合对应的 Camera 使用,如:Camera 需要渲染 YourLayerName 层,那么 LayerMask 就加上 YourLayerName 层即可
public int layer; // 上面的 LayerMask 是无效的,因为获取layermask 的数值,而不是索引值,这里 Graphics.DrawInstanced 的 layer 参数值索引值,官方文档只是一句注释:to use,WTF,太不负责任的,WTF
public ShadowCastingMode castShadow;
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[] colors_vec4;
private ParallelUpdateDataJob job = new ParallelUpdateDataJob();
private int last_instance_count = 0;
private void Start()
{
mpb = new MaterialPropertyBlock();
UpdateDatas();
}
private void Update()
{
CheckSupport();
UpdateDatas();
RenderCoins();
}
private void OnDestroy()
{
if (job.colors.IsCreated) job.colors.Dispose();
if (job.colors_vec4.IsCreated) job.colors_vec4.Dispose();
if (job.angle_offset.IsCreated) job.angle_offset.Dispose();
if (job.pos_offset.IsCreated) job.pos_offset.Dispose();
if (job.model_mats.IsCreated) job.model_mats.Dispose();
}
private void CheckSupport()
{
if (!SystemInfo.supportsInstancing)
{
Debug.LogError("can not support GPU INSTANCING!");
}
}
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));
}
// 这里 copy from 有点浪费,但是没有办法,谁叫:Graphics 的只能用 .net 的数据结构,他就不能封装一个 NativeArray 的结构吗?
// 要是封装了,性能就会再次提升,我是服了!
job.colors.CopyFrom(col);
job.colors_vec4.CopyFrom(colors_vec4);
job.angle_offset.CopyFrom(angle_offset);
job.pos_offset.CopyFrom(pos_offset);
job.model_mats.CopyFrom(model_mats);
}
private void UpdateDatas()
{
if (last_instance_count != instance_count)
{
last_instance_count = instance_count;
var count = instance_count;
col = new Color[count];
if (job.colors.IsCreated) job.colors.Dispose();
job.colors = new NativeArray<Color>(count, Allocator.Persistent, NativeArrayOptions.ClearMemory);
colors_vec4 = new Vector4[count];
if (job.colors_vec4.IsCreated) job.colors_vec4.Dispose();
job.colors_vec4 = new NativeArray<Vector4>(count, Allocator.Persistent, NativeArrayOptions.ClearMemory);
angle_offset = new float[count];
if (job.angle_offset.IsCreated) job.angle_offset.Dispose();
job.angle_offset = new NativeArray<float>(count, Allocator.Persistent, NativeArrayOptions.ClearMemory);
pos_offset = new Vector3[count];
if (job.pos_offset.IsCreated) job.pos_offset.Dispose();
job.pos_offset = new NativeArray<Vector3>(count, Allocator.Persistent, NativeArrayOptions.ClearMemory);
model_mats = new Matrix4x4[count];
if (job.model_mats.IsCreated) job.model_mats.Dispose();
job.model_mats = new NativeArray<Matrix4x4>(count, Allocator.Persistent, NativeArrayOptions.ClearMemory);
mpb.Clear();
}
if (last_fill_data != procedurce_fill_data)
{
last_fill_data = procedurce_fill_data;
ProcedureFillData();
}
job.parent_model_mat = transform.localToWorldMatrix;
var hand = job.Schedule(instance_count, 20);
// 这里会阻塞主线程,所以我们的 instance_count 的量,和 job 中的任务不能太繁重
hand.Complete();
// 这里 copy to 有点浪费,但是没有办法,谁叫:Graphics 的只能用 .net 的数据结构,他就不能封装一个 NativeArray 的结构吗?
// 要是封装了,性能就会再次提升,我是服了!
job.colors.CopyTo(col);
job.colors_vec4.CopyTo(colors_vec4);
job.angle_offset.CopyTo(angle_offset);
job.pos_offset.CopyTo(pos_offset);
job.model_mats.CopyTo(model_mats);
mpb.SetVectorArray(_Color, colors_vec4);
}
private void RenderCoins()
{
if (!mat.enableInstancing)
{
mat.enableInstancing = true;
}
Graphics.DrawMeshInstanced(
mesh,
0,
mat,
model_mats,
instance_count,
mpb,
castShadow,
false,
//layer.value,
layer,
cam);
}
}
运行情况
为了看出单线程 update 数据与多线程的区别,我先拿前面第3篇的 update 数据的情况的 FPS,如下图:
才只有 70+ FPS
那么用了 IJobParallelFor 之后呢?如下图:
250 FPS 左右
提升了大概3.5倍+
还是挺有用的
而且我这还不是最优写法,还可以将部分 math 库使用新的 math ,这部分还没使用,以后有空再试试
然后再使用上:Bursts 编译器优化,性能会更高
其他
- GPU Instancing手机兼容性报告 - GPU Instancing 不是算新技术了,在 2010 年就有了,但是还是有部分老旧手机不支持,特别是一些比较落后的国家,所以需要要兼容性处理
OK,挤牙膏时间完毕,下班回家。