うにてぃブログ

主にUnityとC#に関する記事を書いていきます

【Unity】ArgumentOutOfRangeException: Count must be in the range of 0 to 1023.

Note: You can only draw a maximum of 1023 instances at once.

ドキュメントにもあるように Graphics.DrawMeshInstanced で一度に生成できるインスタンス数の上限は 1023 となっており、1023を超える数を生成すると以下のエラーが表示される

ArgumentOutOfRangeException: Count must be in the range of 0 to 1023.

スクリプトサンプル

Graphics.DrawMeshInstanced で渡すインスタンス数は自前で管理する必要があるため、以下のような実装が必要になる

using System.Collections.Generic;
using UnityEngine;
 
public class GrassRenderer : MonoBehaviour
{
    [SerializeField]
    private Material _material;
    [SerializeField]
    private Mesh _mesh;
    [SerializeField]
    private float _range = 5;
    [SerializeField]
    private int _amount = 1000;
 
    private List<Matrix4x4[]> _transforms;
    private int instanceMax = 1023;
 
    private void Start()
    {
        var drawCount = Mathf.CeilToInt(_amount / (float) 1023);
        _transforms = new List<Matrix4x4[]>();
        for (var i = 0; i < drawCount; i++)
        {
            var length = i == drawCount - 1 ? _amount % instanceMax : instanceMax;
            var m = new Matrix4x4[length];
            for (var j = 0; j < m.Length; j++)
            {
                var x = Random.Range(-_range, _range);
                var z = Random.Range(-_range, _range);
                var position = new Vector3(x, 0f, z);
                var rotate = Quaternion.Euler(new Vector3(0f, Random.Range(0f, 360f), 0f)); 
                m[j] = Matrix4x4.TRS(position, rotate, Vector3.one);
            }
            _transforms.Add(m);
        }
    }
  
    private void Update()
    {
        for (var i = 0; i < _transforms.Count; i++)
        {
            Graphics.DrawMeshInstanced(_mesh, 0, _material, _transforms[i], _transforms[i].Length);
        }
    }
}

Unity2021

Unity2021では同じ機能の拡張である Graphics.RenderMeshInstanced が追加されている

    public static unsafe void RenderMeshInstanced<T>(
      in RenderParams rparams,
      Mesh mesh,
      int submeshIndex,
      T[] instanceData,
      [DefaultValue("-1")] int instanceCount = -1,
      [DefaultValue("0")] int startInstance = 0)
      where T : unmanaged

そのため、上記のように自前で複数リストに分ける必要が無いため、先程のスクリプトは以下のように書き換えられる

using UnityEngine;
 
public class GrassRenderer : MonoBehaviour
{
    [SerializeField]
    private Material _material;
    [SerializeField]
    private Mesh _mesh;
    [SerializeField]
    private float _range = 5;
    [SerializeField]
    private int _amount = 1000;
    
    /// <summary>
    /// 描画用パラメータのバッファ
    /// </summary>
    private GraphicsBuffer _indirectBuf;
    /// <summary>
    /// Mesh 描画の設定
    /// </summary>
    private GraphicsBuffer.IndirectDrawIndexedArgs[] _commandData;

    private RenderParams _renderParams;
    private Matrix4x4[] _transforms = new Matrix4x4[10];
    
    private int instanceMax = 1023;

    private void Start()
    {
        _renderParams = new RenderParams(_material)
        {
            worldBounds = new Bounds(Vector3.zero, 10000 * Vector3.one),
            matProps = new MaterialPropertyBlock()
        };
        
        _transforms = new Matrix4x4[10];
        for (var i = 0; i < _transforms.Length; i++)
        {
            var x = Random.Range(-_range, _range);
            var z = Random.Range(-_range, _range);
            var position = new Vector3(x, 0f, z);
            var rotate = Quaternion.Euler(new Vector3(0f, Random.Range(0f, 360f), 0f)); 
            _transforms[i] = Matrix4x4.TRS(position, rotate, Vector3.one);
        }

        _commandData = new GraphicsBuffer.IndirectDrawIndexedArgs[1];
        _commandData[0].indexCountPerInstance = _mesh.GetIndexCount(0);
        _commandData[0].baseVertexIndex = _mesh.GetBaseVertex(0);
        _commandData[0].startIndex = _mesh.GetIndexStart(0);
        _commandData[0].instanceCount = (uint)_transforms.Length;
        
        _indirectBuf = new GraphicsBuffer(GraphicsBuffer.Target.IndirectArguments, 1, GraphicsBuffer.IndirectDrawIndexedArgs.size);
        _indirectBuf.SetData(_commandData);
    }
 
    private void Update()
    {
        var drawCount = Mathf.CeilToInt(_amount / (float) instanceMax);
        for (var i = 0; i < drawCount; i++)
        {
            var length = i == drawCount - 1 ? _amount % instanceMax : instanceMax;
            Graphics.RenderMeshInstanced(_renderParams, _mesh, 0, _transforms, length, i * instanceMax);
        }
    }
 
    private void OnDestroy()
    {
        _indirectBuf?.Dispose();
        _indirectBuf = null;
    }
}