グラフィックスコマンドバッファ
スパーステクスチャ

GPU インスタンシング

はじめに

GPU インスタンシングを使うと、少ない ドローコール で、同じメッシュの複数のコピーをいっぺんに描画 (またはレンダリング) できます。 これは、建物、樹木、草などのオブジェクトを描画したり、シーンに繰り返し登場するものを描画する場合に便利です。

GPU インスタンシングは、各ドローコールで同じメッシュをレンダリングするだけですが、各インスタンスは変化を加えるため、パラメーター (例えば、色やスケール) を変えて、繰り返しの回数を減らすことができます。

GPU インスタンシングを使うと、シーンごとに使用されるドローコールの数を減らすことができます。 これにより、プロジェクトのレンダリングパフォーマンスが大幅に向上します。

マテリアルにインスタンシングを加える

GPU インスタンシングをマテリアル上で有効にするには、Project ウィンドウでマテリアルを選択し、Inspector で Enable Instancing をチェックします。

Material Inspector ウィンドウの Enable Instancing チェックボックス
Material Inspector ウィンドウの Enable Instancing チェックボックス

このチェックボックスは、マテリアルシェーダーが GPU インスタンシングをサポートする場合のみ表示されます。これには、Standard、StandardSpecular、そしてすべてのサーフェス シェーダー が含まれます。詳しくは スタンダードシェーダー を参照してください。

以下のスクリーンショットは、複数のゲームオブジェクトを持つ同じシーンを示しています。最初の画像では GPU インスタンシングが有効になっており、2 番目の画像では有効になっていません。 FPSBatches Saved by batching の違いに注目してください。

GPU インスタンシングが有効: GPU インスタンシングを有効にした複数の同じゲームオブジェクトを持つ単純なシーン
GPU インスタンシングが有効: GPU インスタンシングを有効にした複数の同じゲームオブジェクトを持つ単純なシーン
GPU インスタンシングが無効: GPU インスタンシングを無効にした複数の同じゲームオブジェクトを持つ単純なシーン
GPU インスタンシングが無効: GPU インスタンシングを無効にした複数の同じゲームオブジェクトを持つ単純なシーン

GPU インスタンシングを使用するる場合、以下の制限があります。

  • Unity は インスタンシングのために、自動的に MeshRenderer コンポーネントと Graphics.DrawMesh の呼び出しを選択します。SkinnedMeshRenderer はサポートされないことに注意してください。

  • Unity は、1 回の GPU インスタンシングドローコールで、同じメッシュとマテリアルを共有するゲームオブジェクトだけをバッチ処理します。インスタンシングの効率を上げるために、少数のメッシュとマテリアルを使用するようにします。バリエーションを作成するには、シェーダースクリプトを変更してインスタンスごとのデータを追加します (詳細は次のセクションを参照してください)。

You can also use the calls Graphics.DrawMeshInstanced and Graphics.DrawMeshInstancedIndirect. to perform GPU Instancing from your scripts.

GPU インスタンシングは、以下のプラットフォームおよび API で利用可能です。

  • Windows の DirectX 11DirectX 12

  • Windows、macOS、Linux、iOS、Android の OpenGL Core 4.1+/ES3.0+

  • macOS と iOS の Metal

  • Windows と Android の Vulkan

  • PlayStation 4Xbox One

  • WebGL (requires WebGL 2.0 API)

インスタンスごとのデータを加える

デフォルトでは、インスタンス化された各ドローコールで、異なる Transform を持つゲームオブジェクトのインスタンスだけをバッチします。インスタンス化したゲームオブジェクトをさらに相違させるには、シェーダーを変更してマテリアルカラーなどのインスタンスごとのプロパティーを加えます。

以下の例は、インスタンスごとに異なるカラー値を持つインスタンス化したシェーダーを作成する方法を示しています。

Shader "Custom/InstancedColorSurfaceShader" {
    Properties {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0
    }

    SubShader {
        Tags { "RenderType"="Opaque" }
        LOD 200
        CGPROGRAM
        // Physically based Standard lighting model, and enable shadows on all light types
        #pragma surface surf Standard fullforwardshadows
        // Use Shader model 3.0 target
        #pragma target 3.0
        sampler2D _MainTex;
        struct Input {
            float2 uv_MainTex;
        };
        half _Glossiness;
        half _Metallic;
        UNITY_INSTANCING_CBUFFER_START(Props)
           UNITY_DEFINE_INSTANCED_PROP(fixed4, _Color)
        UNITY_INSTANCING_CBUFFER_END
        void surf (Input IN, inout SurfaceOutputStandard o) {
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * UNITY_ACCESS_INSTANCED_PROP(_Color);
            o.Albedo = c.rgb;
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

When you declare _Color as an instanced property, Unity takes all _Color GameObjects that share a Mesh and Material and includes them in a single draw call.

MaterialPropertyBlock props = new MaterialPropertyBlock();
MeshRenderer renderer;

foreach (GameObject obj in objects)
{
   float r = Random.Range(0.0f, 1.0f);
   float g = Random.Range(0.0f, 1.0f);
   float b = Random.Range(0.0f, 1.0f);
   props.SetColor("_Color", new Color(r, g, b));

   renderer = obj.GetComponent<MeshRenderer>();
   renderer.SetPropertyBlock(props);
}

この変更を実際に反映させるには、GPU Instancing を使用可能にしなくてはなりません。それには、Project ウィンドウでシェーダーを選択し、Inspector で Enable Instancing をチェックします。

シェーダーの Inspector ウインドウに表示された Enable Instancing チェックボックス
シェーダーの Inspector ウインドウに表示された Enable Instancing チェックボックス

頂点シェーダーとフラグメントシェーダーにインスタンシングを加える

以下の例は簡単な Unlit シェーダーを様々な色でインスタンシング可能にします。

Shader "SimplestInstancedShader"
{
    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 // necessary only if you want to access instanced properties in fragment Shader.
            };

            UNITY_INSTANCING_CBUFFER_START(MyProperties)
                UNITY_DEFINE_INSTANCED_PROP(float4, _Color)
            UNITY_INSTANCING_CBUFFER_END

            v2f vert(appdata v)
            {
                v2f o;

                UNITY_SETUP_INSTANCE_ID(v);
                UNITY_TRANSFER_INSTANCE_ID(v, o); // necessary only if you want to access instanced properties in the fragment Shader.

                o.vertex = UnityObjectToClipPos(v.vertex);
                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                UNITY_SETUP_INSTANCE_ID(i); // necessary only if any instanced properties are going to be accessed in the fragment Shader.
                return UNITY_ACCESS_INSTANCED_PROP(_Color);
            }
            ENDCG
        }
    }
}

シェーダーの変更

追加コード 機能
#pragma multi_compile_instancing Unity にインスタンシングバリアントを生成するように命令するために使用します。サーフェスシェーダーには必要ありません。
UNITY_VERTEX_INPUT_INSTANCE_ID 頂点シェーダー入力/出力構造体で使用してインスタンス ID を定義します。 詳細は SV_InstanceID を参照してください。
UNITY_INSTANCING_CBUFFER_START(name) / UNITY_INSTANCING_CBUFFER_END インスタンスごとのプロパティーはすべて、予約された定数バッファで定義する必要があります。この一組になったマクロを使って、各インスタンスに固有化したいプロパティーをラップします。
UNITY_DEFINE_INSTANCED_PROP(float4, _Color) 型と名前を使ってインスタンスごとのシェーダープロパティーを定義します。この例では、_Color プロパティーが固有のものです。
UNITY_SETUP_INSTANCE_ID(v); インスタンス ID がシェーダー関数がにアクセスできるようにします。これは頂点シェーダーの最初に使用する必要があります。また、フラグメントシェーダーでは、オプションとして使用されます。
UNITY_TRANSFER_INSTANCE_ID(v, o); インスタンス ID を頂点シェーダの入力構造体から出力構造体にコピーします。これは、フラグメントシェーダーでは、インスタンスごとのデータにアクセスするときのみ必要です。
UNITY_ACCESS_INSTANCED_PROP(color) Use this to access a per-instance Shader property. It uses an instance ID to index into the instance data array.

注意

  • 複数のインスタンスごとのプロパティーを使用する場合は、MaterialPropertyBlocks でそれらすべてを満たす必要はありません。

  • 1 つのインスタンスにプロパティーがない場合、Unity は参照されているマテリアルからデフォルト値を取ります。マテリアルが指定したプロパティーのデフォルト値を持たない場合、Unity は値を 0 に設定します。MaterialPropertyBlock にインスタンス化していないプロパティーを使用しないでください。なぜなら、インスタンシングを無効にするからです。代わりに、他のマテリアルを作成してください。

上級者向けの GPU インスタンシングのヒント

バッチの優先度

バッチを行う場合、Unity はインスタンシングより 静的バッチ を優先します。ゲームオブジェクトの 1 つを静的バッチに指定し Unity がそのバッチ処理を行うと、たとえレンダラーがインスタンシングのシェーダーを使用していても、Unity はそのゲームオブジェクトのインスタンシングを無効にします。この場合、警告メッセージが Inspector ウィンドウに表示され、静的バッチ処理を無効にするよう指示します。静的バッチ処理を無効にするには、Player Settings (Edit > Project Settings > Player) に移動し、 Other Settings を開き、Rendering セクションの Static Batching チェックボックスの選択を解除します。

Unity は、動的バッチ処理よりもインスタンシングを優先します。Unity がメッシュをインスタンス化できる場合、メッシュの動的バッチ処理を無効にします。

Graphics.DrawMeshInstanced

ゲームオブジェクトを自動的にひとまとめにインスタンス化するのを妨げる要因がいくつかあります。これらの要因には、マテリアル変更と深度ソートなどがあります。Unity に GPU のインスタンシングを使用してオブジェクトを描画するよう強制するには、Graphics.DrawMeshInstanced を使用します。Graphics.DrawMesh と同様に、この関数は不要なゲームオブジェクトを作成せずに 1 フレーム分のメッシュを描画します。

Do not submit batches of instances that exceed the maxcount specified in your Shader script (500 by default). When using graphics tools from OpenGL or Metal, Unity divides the maxcount by 4, and uses the result as the maximum number of batches you can submit. The recommended practice for drawing arbitrary number of instances is to maintain a pool of pre-allocated 500-sized arrays (and MaterialPropertyBlocks if needed) and reuse these arrays as much as possible. See documentation on Automatic Memory Management for more information about object pooling.

Graphics.DrawMeshInstancedIndirect

インスタンスの数を含む、インスタンシングドローコールのパラメーターを計算バッファから読み込むには、スクリプトで DrawMeshInstancedIndirect を使用します。これは、GPU からすべてのインスタンスデータを取得する必要があり、CPU が描画するインスタンスの数を知らない場合 (たとえば、GPU カリングを実行するとき) に便利です。詳細な説明とコード例については、スクリプトリファレンスの Graphics.DrawMeshInstancedIndirect を参照してください。

pragma instancing_options

#pragma instancing_options ディレクティブは以下の switch を使用できます。

Switch 説明
maxcount: batchSize Use this to specify the maximum number of instances to draw in one instanced draw call. By default this value is 500. When using OpenGL or Metal, Unity divides the maxcount by 4, and uses the result as the maximum number of batches you can submit. Make this value as small as possible to match the amount of instances you want to draw. For example, to draw a maximum of 1000 instances, add maxcount: 1000 to your shader script. Larger values increase Shader compilation time, and can reduce GPU performance.
force_same_maxcount_for_gl Use this to force Unity to stop dividing the maxcount by 4 on graphics tools from OpenGL or Metal.
assumeuniformscaling Use this to instruct Unity to assume that all the instances have uniform scalings (the same scale for all X, Y and Z axes).
lodfade Use this to make LOD fade values instanceable. This is useful for SpeedTree and other LOD techniques that use the LOD fading feature.
procedural:FunctionName Use this to instruct Unity to generate an additional variant for use with Graphics.DrawMeshInstancedIndirect.
At the beginning of the vertex Shader stage, Unity calls the function specified after the colon. To set up the instance data manually, add per-instance data to this function in the same way you would normally add per-instance data to a Shader. Unity also calls this function at the beginning of a fragment Shader if any of the fetched instance properties are included in the fragment Shader.

UnityObjectToClipPos

シェーダースクリプトを作成する場合、mul(UNITY_MATRIX_MVP,v.vertex) ではなく、常に UnityObjectToClipPos(v.vertex) を使用します。

インスタンス化されたシェーダーでは、通常どおり UNITY_MATRIX_MVP を使用できますが、UnityObjectToClipPos は頂点の位置をオブジェクト空間からクリップスペースに変換する最も効率的な方法です。Unity はまた、プロジェクト内のすべてのシェーダーをスキャンし、 mul(UNITY_MATRIX_MVP, v) が発生すると自動的に UnityObjectToClipPos(v) に置き換えるシェーダーのアップグレード機能を実装しています。

UNITY_MATRIX_MVP (UNITY_MATRIX_MV と共に) がまだ使用されている場合は、コンソールウィンドウ (Window > Console) にパフォーマンスの警告が表示されます。

その他の注意事項

  • #pragma サーフェスディレクティブで noinstancing を指定しない限り、サーフェスシェーダーにはデフォルトでインスタンシングバリアントが生成されます。Standard および StandardSpecular シェーダーはインスタンスシングが適用されるように既に変更されていますが、transform 以外は、インスタンスごとのプロパティーは定義されていません。Unity は、サーフェスシェーダーで#pragma multi_compile_instancing が使用されても無視します。

  • シーン内のゲームオブジェクトで GPU インスタンシングが有効になっていない場合、Unity はインスタンシングバリアントを削除します。 ストリッピングの挙動を無効にするには、 Graphics Settings ( Edit > Project Settings > Graphics) を開き、 Shader stripping セクションに移動し、Instancing Variants を変更します。

  • Graphics.DrawMeshInstanced を使用するには、このメソッドに渡すマテリアルの GPU インスタンシングを有効にする必要があります。ただし、Graphics.DrawMeshInstancedIndirect では GPU インスタンシングをを有効にする必要はありません。間接的なインスタンシングのキーワード PROCEDURAL_INSTANCING_ON はストリッピングに影響されません。

  • インスタンス化されたドローコールは フレームデバッガーDraw Mesh (instanced) として表示されます。

  • インスタンスごとのプロパティーを必ずしも定義する必要はありません。ただし、インスタンス ID の設定は必須です。なぜなら、ワールド行列が正しく機能するために必要だからです。サーフェスシェーダーは自動的にインスタンス ID を設定します。カスタム頂点シェーダーとフラグメントシェーダのインスタンス ID は手動で設定する必要があります。これを行うには、シェーダーのはじめにUNITY_SETUP_INSTANCE_ID を使用します。

  • フォワードレンダリングを使用する場合、Unity は複数のライトの影響を受けるオブジェクトを効率的にインスタンス化できません。加算パスではなく、ベースパスのみがインスタンス化を有効に活用できます。ライティングパスの詳細については、フォワードレンダリングPass 内の Tags を参照してください。

  • ライトマップを使用するオブジェクトや、異なるライトや リフレクションプローブ の影響を受けるオブジェクトはインスタンス化できません。

  • マルチパスシェーダーのパスが 2 つ以上の場合は、最初のパスだけがインスタンス化されます。これは、Unity が強制的に後からのパスをオブジェクトごとに一緒にレンダリングして、マテリアルの変更を強制するためです。

  • 上記の例で使用されているシェーダーのマクロはすべて、 UnityInstancing.cginc で定義されています。 このファイルは、[Unity installation folder]\Editor\Data\CGIncludes ディレクトリにあります。


  • 2017–05–18 編集レビュー を行って修正されたページ

  • Enable Instancing チェックボックスの説明、DrawMeshInstancedIndirect、#pragma multi-compile に関しては 5.6 で追加

グラフィックスコマンドバッファ
スパーステクスチャ