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

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 を参照してください。

Global Illumination support

Since Unity 2018.1, Global Illumination (GI) rendering is supported by GPU Instancing in the form of light probes, occlusion probes (in Shadowmask mode) and lightmap STs. Standard shaders and surface shaders have GI support automatically enabled.

Dynamic renderers affected by light probes and occlusion probes baked in the scene, and static renderers baked to the same lightmap texture, can be automatically batched together using GPU Instancing by Forward and Deferred render loop.

For Graphics.DrawMeshInstanced, you can enable light probe and occlusion probe rendering by setting the LightProbeUsage argument to CustomProvided and providing a MaterialPropertyBlock with probe data copied in. See API documentation on LightProbes.CalculateInterpolatedLightAndOcclusionProbes for a detailed explanation and code examples.

Global Illumination and GPU Instancing

GPU Instancing supports Global Illumination (GI) rendering in Unity. Each GPU instance can support GI coming from either different Light Probes, one lightmap (but multiple atlas regions in that lightmap), or one Light Probe Proxy Volume component (baked for the space volume containing all the instances). Standard shaders and surface shaders come with this support enabled.

You can use GPU Instancing to automatically batch dynamic Mesh Renderers affected by baked Light Probes (including their occlusion data), or static Mesh Renderers baked to the same lightmap Texture, via a Forward and Deferred render loop. See documentation on the Rendering pipeline for more information.

For Graphics.DrawMeshInstanced, you can enable the rendering of Light Probes (including their occlusion data) by setting the LightProbeUsage argument to CustomProvided and providing a MaterialPropertyBlock with probe data copied in. See API documentation on LightProbes.CalculateInterpolatedLightAndOcclusionProbes for a detailed explanation and code examples.

Alternatively, you can pass an LPPV component reference and LightProbeUsage.UseProxyVolume to Graphics.DrawMeshInstanced. When you do this, all instances sample the volume for the L0 and L1 bands of the Light Probe data. Use MaterialPropertyBlock if you want to supplement L2 data and occlusion data. For more information, see Light Probes: Technical Information.

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

Since Unity 2017.3, you need to warm up shaders to use instancing on OpenGL if you want absolutely smooth rendering when the shader renders for the first time. If you warm up shaders for instancing on a platform that doesn’t require shader warm up, nothing will happen.

詳細は 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 Use this to instruct Unity to assume that all the instances have uniform scalings (the same scale for all X, Y and Z axes).
nolodfade Use this to prevent Unity from applying GPU Instancing to LOD fade values.
nolightprobe Use this to prevent Unity from applying GPU Instancing to Light Probe values (including their occlusion data). This is useful for performance if you are absolutely sure that there are no GameObjects using both GPU Instancing and Light Probes.
nolightmap Use this to prevent Unity from applying GPU Instancing to Lightmap ST (atlas information) values. This is useful for performance if you are absolutely sure that there are no GameObjects using both GPU Instancing and lightmaps.
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) に置き換えるシェーダーのアップグレード機能を実装しています。

The console window (menu: Window > General > Console) displays performance warnings if there are still places where UNITY_MATRIX_MVP (along with UNITY_MATRIX_MV) is used.

その他の注意事項

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

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

  • For Graphics.DrawMeshInstanced, you need to enable GPU Instancing on the Material that the script is passing into this method. However, Graphics.DrawMeshInstancedIndirect does not require you to enable GPU Instancing. The indirect instancing keyword PROCEDURAL_INSTANCING_ON is not affected by stripping.

  • インスタンス化されたドローコールは フレームデバッガー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 で追加

  • Shader warm up for GPU instancing added in 2017.3 NewIn20173

  • Global Illumination (GI) support in GPU instancing added in 2018.1 NewIn20181

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