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

141 篇文章 33 订阅

前一篇:

这一篇直接使用 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);
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值