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

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

スクリプトから GPU インスタンシングを実行するには、Graphics.DrawMeshInstancedGraphics.DrawMeshInstancedIndirect の呼び出しを利用することもできます。

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
        // 物理ベースの Standard ライティングモデル。すべてのライトタイプで影を有効にします
        #pragma surface surf Standard fullforwardshadows
        //Shader model 3.0 対応を使用します
        #pragma target 3.0
        sampler2D _MainTex;
        struct Input {
            float2 uv_MainTex;
        };
        half _Glossiness;
        half _Metallic;
        UNITY_INSTANCING_BUFFER_START(Props)
           UNITY_DEFINE_INSTANCED_PROP(fixed4, _Color)
        UNITY_INSTANCING_BUFFER_END(Props)
        void surf (Input IN, inout SurfaceOutputStandard o) {
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * UNITY_ACCESS_INSTANCED_PROP(Props, _Color);
            o.Albedo = c.rgb;
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

_Color をインスタンス化したプロパティーとして宣言すると、Unity はゲームオブジェクトに設定された MaterialPropertyBlock オブジェクトの _Color 値を集め、1 つのドローコールに取り込みます。


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);
}

通常 (インスタンスシェーダーを使用していない場合、または、_Color がインスタンスごとのプロパティーでない場合)、MaterialPropertyBlock には様々な値があるため、ドローコールバッチは分かれています。

この変更を実際に反映させるには、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 / フラグメントシェーダーのインスタンス化したプロパティーにアクセスしたい場合にのみ必要
             };

            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
        }
    }
}

シェーダーの変更

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

注意

  • 複数のインスタンスごとのプロパティーを使用する場合は、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 フレーム分のメッシュを描画します。

Graphics.DrawMeshInstancedIndirect

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

シェーダーのウォームアップ

Unity 2017.3 以降、シェーダーが初めてレンダリングするときに完全に滑らかなレンダリングを行いたい場合には、OpenGL のインスタンシングを利用するためにシェーダーをウォームアップする必要があります。シェーダーウォームアップが必要ないプラットフォームのインスタンシングのためにシェーダーをウォームアップしても、何も問題は発生しません。

詳細は ShaderVariantCollection.WarmUpShader.WarmupAllShaders を参照してください。

pragma instancing_options

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

Switch 機能
forcemaxcount:batchSizemaxcount:batchSize ほとんどのプラットフォームで、ターゲットデバイス上の最大定数バッファーサイズをすべてのインスタンスごとのプロパティーを含む構造体のサイズで割ることで、Unity は自動的にインスタンスデータ配列サイズを計算します。 一般的に、バッチサイズを心配する必要はありません。 ただし、一部のプラットフォーム (Vulkan、Xbox One、Switch) では、まだ決められた配列サイズが必要です。 maxcount オプションを使って、これらのプラットフォームのバッチサイズを指定することができます。 このオプションは他のプラットフォームでは完全に無視されます。 すべてのプラットフォームにバッチサイズを強制したいのであれば、 forcemaxcount を使用してください (例えば、DrawMeshInstanced を使って 256 のインスタンス化したスプライトで描画を行うことが分かっている場合)。 デフォルト値は、2 つのオプションとも 500 です。
assumeuniformscaling すべてのインスタンスが統一されたスケーリング (X, Y, Z 軸すべてに対して) を持つと仮定するように Unity に命令します。
lodfade LOD のフェード値をインスタンス化可能にします。これは、LOD フェード機能を使う SpeedTree や他の LOD 技術に有用です。
procedural:FunctionName Graphics.DrawMeshInstancedIndirect で使用する追加のバリアントを生成するよう、Unity に命令します。
頂点シェーダーステージの最初に、Unity はコロンの後に指定された関数を呼び出します。インスタンスデータを手動で設定するには、インスタンスごとのデータを普通にシェーダーに追加するのと同じ方法で、この関数にインスタンスごとのデータを追加します。フェッチされたインスタンスプロパティーのいずれかがフラグメントシェーダーに含まれている場合は、Unity はフラグメントシェーダーの最初にも、この関数を呼び出します。

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–10–24 編集レビュー を行って修正されたページ

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

  • GPU インスタンスのためのシェーダーウォームアップは 2017.3.(https://docs.unity3d.com/2017.3/Documentation/Manual/30_search.html?q=newin20173) で追加NewIn20173

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