Version: 2020.2
言語: 日本語
コンピュートシェーダー
スパーステクスチャ

GPU インスタンシング

はじめに

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

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

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

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

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

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

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

以下のスクリーンショットは、複数のゲームオブジェクトを持つ同じシーンを示しています。最初の画像では GPU インスタンシングが有効になっており、2 番目の画像では有効になっていません。 FPSBatchesSaved 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、Android の OpenGL Core 4.1+/ES3.0+

  • macOS と iOS の Metal

  • Windows、Linux、Android の Vulkan

  • PlayStation 4Xbox One

  • WebGL (requires WebGL 2.0 API)

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

デフォルトでは、Unity はインスタンス化されたドローコールごとに、Transforms が異なるゲームオブジェクトのインスタンスのみをバッチ処理します。インスタンス化されたゲームオブジェクトのバリエーションを増やすには、シェーダーを修正して Material カラーなどのインスタンスごとのプロパティを追加します。

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


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

            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); // 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); // インスタンス化されたプロパティがフラグメントシェーダーでアクセスされる場合にのみ必要です。
                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 設定 (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 2018.1 以降、グローバルイルミネーション (GI) レンダリングは、ライトプローブ、オクルージョンのプローブ (Shadowmask モード)、ライトマップ ST の形で、GPU インスタンシングによってサポートされています。スタンダードシェーダーとサーフェスシェーダーは自動的に GI サポートを有効にします。

シーンでベイクされたライトプローブとオクルージョンプローブによって影響を受ける動的レンダラー、そして同じライトマップテクスチャにベイクされた静的レンダラーは、フォワードとディファードのレンダリングループによって、GPU インスタンシングを使用して自動的にまとめて処理されます。

Graphics.DrawMeshInstanced に対しては、LightProbeUsage 引数を CustomProvided に設定し、プローブデータをコピーした MaterialPropertyBlock を与えることで、ライトプローブとオクルージョンプローブのレンダリングを行なえます。詳細な説明とコードサンプルは、スクリプトリファレンスの LightProbes.CalculateInterpolatedLightAndOcclusionProbes を参照してください。

グローバルイルミネーションと GPU インスタンシング

GPU インスタンシングは、Unity のグローバルイルミネーション (GI) レンダリングをサポートします。各 GPU インスタンスは、異なる ライトプローブ、1 つの ライトマップ(ただし、ライトマップの複数のアトラス領域)、1 つのライトプローブプロキシボリューム コンポーネントのいずれかからの GI をサポートできます (すべてのインスタンスを含む空間ボリュームに対してベイクします)。スタンダードシェーダーとサーフェスシェーダーでは、このサポートが有効になっています。

GPU インスタンシングを使用すると、フォワードDeferred のレンダリングループを使って、ベイクしたライトプローブ (オクルージョンデータを含む) によって影響を受ける動的な メッシュレンダラー や、同じライトマップテクスチャにベイクされた静的メッシュレンダラーを自動的にまとめて処理できます。詳細は、レンダリングパイプライン を参照してください。

Graphics.DrawMeshInstanced に対しては、LightProbeUsage 引数を CustomProvided に設定し、プローブデータをコピーした MaterialPropertyBlock を与えることで、ライトプローブとオクルージョンプローブのレンダリングを使用できます。詳細な説明とコードサンプルは、スクリプトリファレンスの LightProbes.CalculateInterpolatedLightAndOcclusionProbes を参照してください。

代わりに、LPPV コンポーネントの参照と LightProbeUsage.UseProxyVolumeGraphics.DrawMeshInstanced に渡すこともできます。これを行うと、すべてのインスタンスが Light Probe データの L0 バンドと L1 バンドのボリュームをサンプリングします。L2 データとオクルージョンデータを補うには、MaterialPropertyBlock を使用します。詳細は、ライトプローブ - 技術的な情報 を参照してください。

シェーダーのウォーミングアップ

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 に命令します。
nolodfade Unity が LOD のフェード値に GPU インスタンシングを適用しないようにします。
nolightprobe Unity が GPU インスタンシングを ライトプローブ 値 (オクルージョンデータを含む) に適用しないようにします。これは、GPU インスタンシングとライトプローブの両方を使用するゲームオブジェクトがないことが確かである場合に、パフォーマンスの向上に役立ちます。
nolightmap Unity が GPU インスタンシングを Lightmap ST (アトラス情報) の値に適用しないようにします。これは、GPU インスタンシングとライトマップの両方を使用するゲームオブジェクトがないことが確かである場合に、パフォーマンスの向上に役立ちます。
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 > General > Console) にパフォーマンスの警告が表示されます。

その他の注意事項

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

  • シーン内のゲームオブジェクトで GPU インスタンシングが有効になっていない場合、Unity はインスタンシングバリアントを削除します。 ストリッピングの挙動を無効にするには、 Graphics 設定 ( 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 で追加 NewIn20173

  • GPU インスタンシングでのグローバルイルミネーション (GI) サポートは 2018.1 で追加 NewIn20181

コンピュートシェーダー
スパーステクスチャ