0
Help us understand the problem. What are the problem?

posted at

updated at

【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

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

上述の通り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

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

使用した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
        }
    }
}

参考

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
0
Help us understand the problem. What are the problem?