【Unity】Graphics.DrawMeshInstancedを使う

# Graphics.DrawMeshInstanced

> Draws the same mesh multiple times using GPU instancing.

GPU instancingを利用して同一メッシュのインスタンスを複数回描画できる。

- 各インスタンスのTransformation情報は`Matrix4x4`の配列で指定する。
- `MaterialPropertyBlock`でインスタンスごとのデータを設定できる。
- 一度に描画できるインスタンス数は**1023個**まで。
- マテリアルの`Material.enableInstancing`をtrueに設定しておく必要がある。

## 実際に試す

メッシュにQuadを指定、GPU Instancingに対応したマテリアルを用意して以下のコードを実行する。
(マテリアルに使用したシェーダーは後述。)


```TestDrawMeshInstanced01.cs
using UnityEngine;

namespace TestGPUInstancing
{
    public class TestDrawMeshInstanced01 : MonoBehaviour
    {
        [SerializeField] private Mesh _mesh;
        [SerializeField] private Material _material;
        
        private const int MeshCount = 1023;
        private Matrix4x4[] _matrices;
        
        private void Start()
        {
            _matrices = new Matrix4x4[MeshCount];
            for (int i = 0; i < MeshCount; i++)
            {
                var pos = new Vector3(
                    UnityEngine.Random.Range(-10f, 10f),
                    UnityEngine.Random.Range(-10f, 10f),
                    UnityEngine.Random.Range(-10f, 10f)
                );

                _matrices[i] = Matrix4x4.TRS(pos, Quaternion.identity, Vector3.one);
            }
        }

        private void Update()
        {
            Graphics.DrawMeshInstanced(_mesh, 0, _material, _matrices);
        }
    }
}
```

結果は以下の通り。
`Batches`が5と非常に少なくなっており、バッチが効いていることがわかる。

![スクリーンショット 2022-03-22 15.09.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/85509/913edfde-3795-fecb-3241-709ed90f3dc1.png)


# インスタンス毎に色を変える

上述の通り`MaterialPropertyBlock`を用いてインスタンス毎にプロパティを設定できる。
色を設定するために`SetColor`とうい関数が用意されているが、これだと全インスタンスが同一カラーになってしまう。インスタンス毎に色を指定したい場合は、`SetVectorArray`を用いる。

```TestDrawMeshInstanced02.cs
using UnityEngine;

namespace TestGPUInstancing
{
    public class TestDrawMeshInstanced02 : MonoBehaviour
    {
        [SerializeField] private Mesh _mesh;
        [SerializeField] private Material _material;
        
        private const int MeshCount = 1023;
        private Matrix4x4[] _matrices;
        // MaterialPropertyBlockを使用してインスタンス毎にプロパティを設定できる
        private MaterialPropertyBlock _propertyBlock;
        
        private void Start()
        {
            _matrices = new Matrix4x4[MeshCount];
            _propertyBlock = new MaterialPropertyBlock();

            var colors = new Vector4[MeshCount];
            for (int i = 0; i < MeshCount; i++)
            {
                var pos = new Vector3(
                    UnityEngine.Random.Range(-10f, 10f),
                    UnityEngine.Random.Range(-10f, 10f),
                    UnityEngine.Random.Range(-10f, 10f)
                );

                _matrices[i] = Matrix4x4.TRS(pos, Quaternion.identity, Vector3.one);
                var r = i / (float)MeshCount;
                var g = 1f - i / (float)MeshCount;
                colors[i] = new Vector4(r, g, 0f, 1f);
            }
            _propertyBlock.SetVectorArray("_Color", colors);
        }

        private void Update()
        {
            Graphics.DrawMeshInstanced(_mesh, 0, _material, _matrices, MeshCount, _propertyBlock);
        }
    }
}
```
結果。
![スクリーンショット 2022-03-22 16.03.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/85509/eaacf23c-2d86-9bdf-4f3b-2cd12a72304c.png)

# 1024個以上のインスタンスを描画する。

上述の通り、`Graphics.DrawMeshInstancedで`一度に描画できるインスタンス数は**1023**個が上限。それ以上のインスタンスを描画する場合は自前でバッチすればOK。

```TestDrawMeshInstanced03.cs
using System.Collections.Generic;
using UnityEngine;

namespace TestGPUInstancing
{
    public class TestDrawMeshInstanced03 : MonoBehaviour
    {
        [SerializeField] private Mesh _mesh;
        [SerializeField] private Material _material;

        private int MeshCount = 1023 * 4;
        private List<Matrix4x4[]> _batches;
        
        private void Start()
        {
            _batches = new List<Matrix4x4[]>();
            var matrices = new Matrix4x4[1023];

            for (int i = 0; i < MeshCount; i++)
            {
                if (i % 1023 == 0)
                {
                    matrices = new Matrix4x4[1023];
                    _batches.Add(matrices);
                }

                var pos = new Vector3(
                    UnityEngine.Random.Range(-10f, 10f),
                    UnityEngine.Random.Range(-10f, 10f),
                    UnityEngine.Random.Range(-10f, 10f)
                );

                matrices[i % 1023] = Matrix4x4.TRS(pos, Quaternion.identity, Vector3.one);
            }
        }

        private void Update()
        {
            foreach (var batch in _batches)
            {
                Graphics.DrawMeshInstanced(_mesh, 0, _material, batch, 1023);
            }
        }
    }
}
```

結果。

![スクリーンショット 2022-03-22 16.40.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/85509/13a0fdf9-658c-ce2c-49d2-7c07d51d0129.png)

# 使用したShader

ShaderはGPU Instancingに対応させる必要がある。この辺は凹みさんの記事を参考。

```.shader
Shader "Unlit/GPUInstanceTest"
{
    Properties
    {
        _Color ("Color", Color) = (1, 1, 1, 1)
    }
    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;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };
 
            struct v2f
            {
                float4 vertex : SV_POSITION;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };
 
            UNITY_INSTANCING_BUFFER_START(Props)
                UNITY_DEFINE_INSTANCED_PROP(float4, _Color)
            UNITY_INSTANCING_BUFFER_END(Props)
       
            v2f vert(appdata v)
            {
                v2f o;
 
                UNITY_SETUP_INSTANCE_ID(v);
                UNITY_TRANSFER_INSTANCE_ID(v, o);
 
                o.vertex = UnityObjectToClipPos(v.vertex);
                return o;
            }
       
            fixed4 frag(v2f i) : SV_Target
            {
                UNITY_SETUP_INSTANCE_ID(i);
                return UNITY_ACCESS_INSTANCED_PROP(Props, _Color);
            }
            ENDCG
        }
    }
}
```

# 参考
https://docs.unity3d.com/ja/current/ScriptReference/Graphics.DrawMeshInstanced.html

https://tips.hecomi.com/entry/2018/09/24/232125