Unityで行うGPU instancingについて


はじめに

UnityではInstancingの方法が何パターンかあります.Instancing自体はそこまでハードルが高いものではありませんが,毎回1から書くのはめんどくさいと思うので,簡単なインスタンシングを実装したプロジェクトを作りました.よかったらGitHubからcloneしてみてください.(Instancing_Surfの中身を見てみてください)また今回はComputeShader, ComputeBufferを平行して使用することを前提としております.

コード解説(C#)

Graphics.DrawMeshInstancedIndirect(Mesh instanceMesh, int subMeshIndex, Material instanceMaterial, Vector3 bounds, ComputeBuffer argsBuffer);

このメソッドを用いることでInstancingを行うDrawCallを発行するすることが出来ます.これからひとつずつ必要な引数のデータについて見て行きましょう.

  • Mesh instanceMesh : InstansingしたいMesh
  • int subMeshIndex : 何番目のsubMeshかを指定する.
  • Material instanceMaterial : Instansingするmeshに適応させるShader
  • ComputeBuffer argsBuffer : InstancingするためのMesh情報を詰め込んだ配列情報.(↓を参考にしてみてください)
argsBuffer = new ComputeBuffer (int count, int stride, ComputeBufferType type);
args[0] = (uint)instanceMesh.GetIndexCount(subMeshIndex);
args[1] = (uint)instanceCount;
args[2] = (uint)instanceMesh.GetIndexStart(subMeshIndex);
args[3] = (uint)instanceMesh.GetBaseVertex(subMeshIndex);
argsBuffer.SetData(args);

コード解説(Shader)

Shader側ではVertexShader+FragmentShaderVertexShader+Surface Shaderの2つの型があります.

VertexShaderとFragmentShaderを用いる方法

実行画像

Target

Shaderは使用するバージョンによってGeometryやComputeShaderが使える使えないなど,使用出来る機能に差があります.Targetを指定することで,どのバージョンのShaderを使用するかを決定することが出来ます.
Targetについてはここに詳しく書かれていますが,#pragma target4.5以上にすることでComputeShader,つまりComputeBufferを使用することが可能です.(今回はComputeShaderを使用するのでこの設定で)

VertexShader

変換について

VertexShaderではComputeBufferに詰められた情報をもとに座標変換を行います.またここでの変換は通常の変換通り,Projection座標系までの変換を行います.

InstanceIDの取得

Instanceをした際には個々のユニークなIDを取得することが出来ます.例えば,1万個MeshをInstanceした際には座標などを格納するComputeBufferも同じ数だけ用意し,このIDを頼りにアクセスしていけば個々の座標を動かしたり..みたいなことが出来ます.
以下の例ではVertexShaderの引数にuint instanceID : SV_InstanceIDを渡すことで参照可能となります.

instancing_frag.shader
#if SHADER_TARGET >= 45
    StructuredBuffer<float4> positionBuffer;
#endif

//====================================================
//省略
//====================================================
v2f vert(appdata_full v, uint instanceID : SV_InstanceID)
{
#if SHADER_TARGET >= 45
    float4 data = positionBuffer[instanceID];
#else
    float4 data = 0;
#endif
    v2f o;
    float3 localPosition = v.vertex.xyz * data.w;
    float3 worldPosition = data.xyz + localPosition;
    o.vertex = mul(UNITY_MATRIX_VP, float4(worldPosition, 1.0f));
    return o;
}

FragmentShader

instancing_frag.shader
fixed4 frag(v2f i) : SV_Target
{
    return _Color;
}