Version: 2023.1
言語: 日本語
GPU インスタンシング
ドローコールのバッチ処理

GPU インスタンシング対応シェーダーの作成

このページでは、カスタムの Unity シェーダーに GPU インスタンシングのサポートを追加する方法について説明します。まず、カスタム Unity シェーダーが GPU インスタンシングをサポートするために必要とする、シェーダーキーワード、変数、および関数について説明します。その上で、サーフェスシェーダーと頂点/フラグメントシェーダーの両方にインスタンスごとのデータを追加する方法を示すサンプルを記載しています。

レンダーパイプラインの互換性

機能 ビルトインレンダーパイプライン ユニバーサルレンダーパイプライン (URP) HD レンダーパイプライン (HDRP) カスタムのスクリプタブルレンダーパイプライン (SRP)
カスタムの GPU インスタンシングシェーダー なし なし なし

シェーダーの修正

このセクションには、GPU インスタンシングに関連する、シェーダーの追加についての情報が記載されています。

追加コード 説明
#pragma multi_compile_instancing インスタンシングバリアントを生成します。フラグメントシェーダーと頂点シェーダーには必要です。サーフェスシェーダーの場合は任意です。
#pragma instancing_options Unity がインスタンスに使用するオプションを指定します。利用可能なオプションスイッチについては、#pragma instancing_options を参照してください。
UNITY_VERTEX_INPUT_INSTANCE_ID 頂点シェーダーの出入力構造にインスタンス ID を定義します。このマクロを使用するには、INSTANCING_ON シェーダーキーワードを有効にします。これを行わないと Unity はインスタンス ID を設定しません。
インスタンス ID にアクセスするには、#ifdef INSTANCING_ON ブロック内で vertexInput.instanceID を使用します。このブロックを使用しないと、バリアントがコンパイルに失敗します。
UNITY_INSTANCING_BUFFER_START(bufferName) bufferName という名前のインスタンスごとの定数バッファの開始を宣言します。このマクロを UNITY_INSTANCING_BUFFER_END と共に使用して、インスタンスごとに固有にしたいプロパティの宣言をラップできます。UNITY_DEFINE_INSTANCED_PROP を使用して、バッファ内でプロパティを宣言します。
UNITY_INSTANCING_BUFFER_END(bufferName) bufferName という名前のインスタンスごとの定数バッファの終了を宣言します。このマクロを UNITY_INSTANCING_BUFFER_START と共に使用して、インスタンスごとに固有にしたいプロパティの宣言をラップできます。UNITY_DEFINE_INSTANCED_PROP を使用して、バッファ内でプロパティを宣言します。
UNITY_DEFINE_INSTANCED_PROP(type, propertyName) シェーダーごとのプロパティを型と名前を指定して定義します。下記の例では、_Color プロパティが固有です。
UNITY_SETUP_INSTANCE_ID(v); シェーダー関数のインスタンス ID へのアクセスを可能にします。頂点シェーダーでは、このマクロが最初に必要です。フラグメントシェーダーでは、この追加は任意です。サンプルについては 頂点シェーダーとフラグメントシェーダーの例 を参照してください。
UNITY_TRANSFER_INSTANCE_ID(v, o); 頂点シェーダーで入力構造体から出力構造体へインスタンス ID をコピーします。フラグメントシェーダーでは、インスタンスごとのデータにアクセスする必要がある時に使用してください。
UNITY_ACCESS_INSTANCED_PROP(bufferName, propertyName) インスタンシング定数バッファ内のインスタンスごとのシェーダープロパティにアクセスします。Unity は、インスタンス ID を使用して、インスタンスデータ配列内にインデックスします。bufferName は、指定のプロパティを含む定数バッファの名前と一致する必要があります。このマクロのコンパイルは、INSTANCING_ON と非インスタンシングバリアントで異なります。

インスタンスごとのプロパティを複数使用する場合、MaterialPropertyBlock オブジェクトにその全てを記入する必要はありません。また、プロパティが欠落したインスタンスがある場合、Unity は、参照されているマテリアルからデフォルト値を取得します。マテリアルにそのプロパティのデフォルト値がない場合、Unity は値を 0 に設定します。インスタンシングされていないプロパティを MaterialPropertyBlock に入れるとインスタンシングが無効になるため、これは行わないでください。代わりに、それらのために別のマテリアルを作成してください。

Instancing_options スイッチ

[#pragma instancing_options](#pragma-instancing_options) ディレクティブは以下のスイッチを使用できます。

スイッチ 説明 
forcemaxcount:batchSizemaxcount:batchSize ほとんどのプラットフォームでは、Unity は自動的にインスタンシングデータ配列サイズを計算します (ターゲットデバイス上の最大定数バッファサイズを、全てのインスタンスごとのプロパティを含む構造体のサイズで割ります)。通常はバッチサイズに関して心配する必要はありません。ただし、一部のプラットフォームは固定の配列サイズを必要とします。それらのプラットフォームでバッチサイズを強制するには maxcount オプションを使用してください。他のプラットフォームはこのオプションを無視します。全てのプラットフォームで同じバッチサイズを強制するには forcemaxcount を使用してください。これは例えば、プロジェクトが RenderMeshInstanced を使用して 256 個のインスタンス化されたスプライトのドローコールを発行している場合などに役立ちます。この 2 つのオプションのデフォルト値は 500 です。
assumeuniformscaling Unity に、全てのインスタンスが統一されたスケールを持つ (X、Y、Z 全ての軸のスケールが同じ) ことを想定するように命令します。
nolodfade Unity が LOD フェード値に GPU インスタンシングを適用しないようにします。
nolightprobe Unity が ライトプローブ の値とそのオクルージョンデータに GPU インスタンシングを適用しないようにします。このオプションを ON に設定すると、プロジェクトに GPU インスタンシングとライトプローブの両方を使用するゲームオブジェクトが含まれていない場合に、パフォーマンスが向上する可能性があります。
nolightmap Unity がライトマップアトラス情報の値に GPU インスタンシングを適用しないようにしま す。このオプションを ON に設定すると、プロジェクトに GPU インスタンシングとライトマップの両方を使用するゲームオブジェクトが含まれていない場合に、パフォーマンスが向上する可能性があります。
procedural:FunctionName Graphics.RenderMeshIndirect で使用する追加のバリアントを生成します。
頂点シェーダーステージの最初に、Unity は、コロンの後に指定された関数を呼び出します。インスタンスデータを手動で設定するには、シェーダーにインスタンスごとのデータを追加する通常の方法で、この関数にインスタンスごとのデータを追加します。また Unity は、フェッチされたインスタンスプロパティのいずれかがフラグメントシェーダーに含まれている場合は、フラグメントシェーダーの最初にもこの関数を呼び出します。

GPU インスタンシングでのシェーダーバリアントの使用

#pragma ディレクティブに noinstancing を指定しない限り、Unity はデフォルトで、インスタンシング バリアント を使用してサーフェスシェーダーを生成します。Unity はサーフェスシェーダーでは #pragma multi_compile_instancing の使用を無視します。

Unity のスタンダードシェーダーと StandardSpecular シェーダーは、デフォルトでインスタンシングをサポートしていますが、Transform 以外のインスタンスごとのプロパティを持っていません。

GPU インスタンシングが有効にされたゲームオブジェクトがシーンにない場合、Unity はインスタンシングシェーダーバリアントをストリッピングします。ストリッピングの挙動をオーバーライドするには以下を行ってください。

  1. Project Settings (Edit > Project Settings) を開きます。
  2. Graphics を開きます。
  3. Shader Stripping セクションで、Instancing VariantsKeep All に設定します。

GPU インスタンシングシェーダーへのインスタンスごとのプロパティの追加

デフォルトでは、Unity は、インスタンス化されたドローコールごとに異なる Transform でゲームオブジェクトを GPU インスタンシングします。インスタンスにバリエーションを加えるには、シェーダーに変更を加えてインスタンスごとのプロパティ (色など) を追加します。これはサーフェスシェーダーと頂点/フラグメントシェーダーの両方で行うことができます。

カスタムシェーダーは、インスタンス単位のデータを必要としませんが、インスタンス ID は必要です (ワールドマトリックスが正しく機能するために必要なため)。サーフェスシェーダーは自動的にインスタンス ID を設定しますが、カスタムの頂点シェーダーとフラグメントシェーダーはそうではありません。カスタムの頂点シェーダーとフラグメントシェーダーで ID を設定するには、シェーダーの最初で UNITY_SETUP_INSTANCE_ID を使用します。この方法を示す例については、頂点シェーダーとフラグメントシェーダーの例 を参照してください。

インスタンス化されたプロパティを宣言すると、Unity は、ゲームオブジェクトに設定された MaterialPropertyBlock オブジェクトから全てのプロパティ値を 1 回のドローコールに集めます。MaterialPropertyBlock オブジェクトを使用してランタイムにインスタンスごとのデータを設定する方法の例については、ランタイムにインスタンスごとのデータを変更する例 を参照してください。

マルチパスシェーダーにインスタンス単位のデータを追加する場合は、以下の点に注意してください。

  • マルチパスシェーダーに 2 つ以上のパスがある場合、Unity は最初のパスのみをインスタンス化します。なぜなら、Unity は後のパスをオブジェクトごとにまとめてレンダリングするため、マテリアルの変更が強制的に行われるからです。
  • ビルトインレンダーパイプラインでフォワードレンダリングパスを使用する場合、Unity は、複数のライトの影響を受けるオブジェクトを効率的にインスタンス化できません。Unity は、ベースパスにのみインスタンシングを効果的に使用でき、追加パスには効果的に使用できません。ライティングパスについての詳細は、フォワードレンダリングとパスタグ のドキュメントを参照してください。

サーフェスシェーダーの例

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

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
        // Uses the physically based standard lighting model with shadows enabled for 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_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"
}

頂点シェーダーとフラグメントシェーダーの例

以下の例は、インスタンスごとにカラー値が異なる、インスタンス化された頂点/フラグメントシェーダーを作成する方法を示しています。サーフェスシェーダーと異なり、頂点シェーダーやフラグメントシェーダーを作成する時は、UNITY_SETUP_INSTANCE_ID を使用して手動でインスタンス ID を設定する必要があります。

Shader "Custom/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 // use this to access instanced properties in the 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);
                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
        }
    }
}

ランタイムにインスタンスごとのデータを変更する例

以下の例は、MaterialPropertyBlock オブジェクトを使用してランタイムにゲームオブジェクトのグループにインスタンスごとのデータを設定する方法を示しています。これは、上記のシェーダーサンプルの _Color プロパティをランダムな色に設定します。

重要: MaterialPropertyBlock は SRP バッチャーの互換性を破ります。詳しくは GPU インスタンシング: 要件と互換性 を参照してください。

using UnityEngine;

public class MaterialPropertyBlockExample : MonoBehaviour
{
    public GameObject[] objects;

    void Start()
    {
        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 インスタンシング
ドローコールのバッチ処理