Unity GPU Instancing 自己写个简单的测试用例4 - DrawInstanced + IJobParallelFor

这里先说明一下,我是在下班时间,挤牙膏的时间出来半小时,写了这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;

/// 
/// author  :   jave.lin
/// Unity GPU Instancing 方案3
/// 
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,如下图:
Unity GPU Instancing 自己写个简单的测试用例4 - DrawInstanced + IJobParallelFor_第1张图片

才只有 70+ FPS

那么用了 IJobParallelFor 之后呢?如下图:
Unity GPU Instancing 自己写个简单的测试用例4 - DrawInstanced + IJobParallelFor_第2张图片
250 FPS 左右

提升了大概3.5倍+

还是挺有用的

而且我这还不是最优写法,还可以将部分 math 库使用新的 math ,这部分还没使用,以后有空再试试

然后再使用上:Bursts 编译器优化,性能会更高


其他

  • GPU Instancing手机兼容性报告 - GPU Instancing 不是算新技术了,在 2010 年就有了,但是还是有部分老旧手机不支持,特别是一些比较落后的国家,所以需要要兼容性处理

OK,挤牙膏时间完毕,下班回家。

你可能感兴趣的:(unity,unity-shader,GPU,Instancing)