うにてぃブログ

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

【Unity】Graphics.RenderMeshIndirect を利用してみる

Graphics.RenderMeshIndirect を利用することで、CPUを経由して描画するのではなく、GPUを直接利用して描画することができるため、大量の草などを軽く描画することができます

今回は10個の草を描画してみます

以下がスクリプトで、バッファを複数利用して描画処理を行っています

using UnityEngine;
 
public class GrassRenderer : MonoBehaviour
{
    [SerializeField]
    private Material _material;
    [SerializeField]
    private Mesh _mesh;
    
    private const int commandCount = 1;
    
    /// <summary>
    /// 描画用パラメータのバッファ
    /// </summary>
    private GraphicsBuffer _indirectBuf;
    /// <summary>
    /// 描画する座標等のデータ用バッファ
    /// </summary>
    private GraphicsBuffer _transformsBuf;
    /// <summary>
    /// Mesh 描画の設定
    /// </summary>
    private GraphicsBuffer.IndirectDrawIndexedArgs[] _commandData;
    /// <summary>
    /// マテリアル等の設定
    /// </summary>
    private RenderParams _renderParams;
      
    private static readonly int Matrix = Shader.PropertyToID("_GrassMatrix");
  
    private void Start()
    {
        _renderParams = new RenderParams(_material)
        {
            worldBounds = new Bounds(Vector3.zero, 10000 * Vector3.one),
            matProps = new MaterialPropertyBlock()
        };
  
        var transforms = new Matrix4x4[10];
        for (var i = 0; i < transforms.Length; i++)
        {
            var position = new Vector3(i, 0f, 0f);
            var rotate = Quaternion.Euler(new Vector3(0f, Random.Range(0f, 360f), 0f)); 
            transforms[i] = Matrix4x4.TRS(position, rotate, Vector3.one);
        }
         
        _transformsBuf = new GraphicsBuffer(GraphicsBuffer.Target.Structured, transforms.Length, 4 * 4 * sizeof(float));
        _transformsBuf.SetData(transforms);
  
        _renderParams.matProps.SetBuffer(Matrix, _transformsBuf);
        
        _commandData = new GraphicsBuffer.IndirectDrawIndexedArgs[commandCount];
        _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, commandCount, GraphicsBuffer.IndirectDrawIndexedArgs.size);
        _indirectBuf.SetData(_commandData);
    }
  
    private void Update()
    {
        Graphics.RenderMeshIndirect(_renderParams, _mesh, _indirectBuf, commandCount);
    }
  
    private void OnDestroy()
    {
        _transformsBuf?.Dispose();
        _transformsBuf = null;
        _indirectBuf?.Dispose();
        _indirectBuf = null;
    }
}

こちらは Material に利用する Shader です

IndirectDrawIndexedArgs に対応している Shader じゃないと複数描画されないので注意してください

Shader "Custom/Grass"
{
    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"
            #define UNITY_INDIRECT_DRAW_ARGS IndirectDrawIndexedArgs
            #include "UnityIndirect.cginc"

            struct v2f
            {
                float4 pos : SV_POSITION;
                float4 color : COLOR0;
            };
            
            uniform StructuredBuffer<float4x4> _GrassMatrix;
            
            v2f vert(appdata_base i, uint instanceID : SV_InstanceID)
            {
                InitIndirectDrawArgs(0);
                v2f o;
                
                float4 wpos = mul(_GrassMatrix[instanceID], i.vertex);
                o.pos = mul(UNITY_MATRIX_VP, wpos);
                half v = instanceID / float(GetIndirectInstanceCount());
                o.color = float4(0, v, 0, 0);
                return o;
            }
 
            float4 frag(v2f i) : SV_Target
            {
                return i.color;
            }
            ENDCG
        }
    }
}