Version: 2021.3
言語: 日本語
パーティクルシステム - 頂点ストリームとスタンダードシェーダーサポート
パーティクルシステム C# Job System インテグレーション

パーティクルシステム GPU インスタンス

GPU インスタンシングは、CPU レンダリングと比較して著しくパフォーマンスを向上させます。パーティクルシステムで、デフォルトの レンダリングモードビルボード パーティクルではなく、メッシュ パーティクルをレンダリングしたい場合に使用できます。

パーティクルシステムで GPU インスタンシングを使用できるようにするには、以下の手順を行います。

  • パーティクルシステムの Render Mode を Mesh に設定します。

  • GPU インスタンシングをサポートする Renderer の Material のシェーダーを使用します。

  • GPU インスタンシングをサポートするプラットフォームでプロジェクトを実行します

パーティクルシステムの GPU インスタンシングを使用可能するには、パーティクルシステムの Renderer モジュールの Enable GPU Instancing チェックボックスにチェックを入れる必要があります。

Renderer モジュールでパーティクルシステムの GPU インスタンシングを有効にするオプション
Renderer モジュールでパーティクルシステムの GPU インスタンシングを有効にするオプション

Unity には、GPU インスタンシングをサポートするビルトインのパーティクルシェーダーがありますが、デフォルトのパーティクルマテリアルはそれを使用しません。そのため、GPU インスタンシングを使用するには、これを変更する必要があります。GPU インスタンシングをサポートするパーティクルシェーダーは Particles/Standard Surface と呼ばれます。これを使用するには、独自の マテリアル を作成し、マテリアルのシェーダーを Particles/Standard Surface に設定します。次に、この新しいマテリアルをパーティクルシステムの Renderer モジュールの Material フィールドに割り当てます。

パーティクルシステム GPU インスタンシングと互換性のあるビルトインシェーダー
パーティクルシステム GPU インスタンシングと互換性のあるビルトインシェーダー

パーティクルに異なるシェーダーを使用する場合は、 ‘#pragma target 4.5’ 以上を使用する必要があります。詳細は、シェーダーコンパイルターゲットレベル を参照してください。この要件は Unity の通常の GPU インスタンシングよりも高くなります。なぜなら、パーティクルシステムは複数のドローコールにインスタンシングを分割するのではなく、すべてのインスタンスデータを 1 つの大きなバッファに書き込むからです。

カスタムシェーダーの例

GPU インスタンシングを使用するカスタムシェーダーも作成できます。詳細は、以下のセクションを参照してください。

サーフェスシェーダーのパーティクルシステム GPU インスタンシング

以下は、パーティクルシステム GPU インスタンシングを使用したサーフェスシェーダーの完全な使用例です。


Shader "Instanced/ParticleMeshesSurface" {
    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
        // 物理計算に基づいた標準のライティングもです。すべてのライトタイプに影を作ることが可能。
        // インスタンシングサポートでシャドウパスを作ります
        #pragma surface surf Standard nolightmap nometa noforwardadd keepalpha fullforwardshadows addshadow vertex:vert
        // このシェーダーのインスタンシングを可能にします
        #pragma multi_compile_instancing
        #pragma instancing_options procedural:vertInstancingSetup
        #pragma exclude_renderers gles
        #include "UnityStandardParticleInstancing.cginc"
        sampler2D _MainTex;
        struct Input {
            float2 uv_MainTex;
            fixed4 vertexColor;
        };
        fixed4 _Color;
        half _Glossiness;
        half _Metallic;
        void vert (inout appdata_full v, out Input o)
        {
            UNITY_INITIALIZE_OUTPUT(Input, o);
            vertInstancingColor(o.vertexColor);
            vertInstancingUVs(v.texcoord, o.uv_MainTex);
        }

        void surf (Input IN, inout SurfaceOutputStandard o) {
            // 着色されるテクスチャのアルベド
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * IN.vertexColor * _Color;
            o.Albedo = c.rgb;
            // Metallic and smoothness come from slider variables
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

上記の例には、通常の サーフェスシェーダー と比べいくつかの小さな違いがあり、それによってパーティクルのインスタンス化と共に使用可能になります。

まず、手続き型のインスタンシングを使用可能にするために以下の 2 行を追加し、ビルトインの頂点設定の関数を指定する必要があります。この関数は UnityStandardParticleInstancing.cginc にあり、インスタンスごとの (パーティクルごとの) 位置データをロードします。

        #pragma instancing_options procedural:vertInstancingSetup
                #include "UnityStandardParticleInstancing.cginc"

この例のもう 1 つの変更は Vertex 関数 に行われています。 Vertex 関数に 2 行が加えられ、インスタンスごとの属性を適用します。具体的には、パーティクルの色と テクスチャシートアニメーション テクスチャの座標です。

            vertInstancingColor(o.vertexColor);
                        vertInstancingUVs(v.texcoord, o.uv_MainTex);

カスタムシェーダーのパーティクルシステム GPU インスタンシング

以下は、パーティクルシステムの GPU インスタンシングを使用したカスタムシェーダーの完全な使用例です。このカスタムシェーダーは、標準のパーティクルシェーダーにはない機能である テクスチャシートアニメーション の個々のフレーム間のフェード、が可能になります。

Shader "Instanced/ParticleMeshesCustom"
{
    Properties
    {
        _MainTex("Albedo", 2D) = "white" {}
        [Toggle(_TSANIM_BLENDING)] _TSAnimBlending("Texture Sheet Animation Blending", Int) = 0
    }
    SubShader
    {
        Tags{ "RenderType" = "Opaque" }
        LOD 100
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile __ _TSANIM_BLENDING
            #pragma multi_compile_instancing
            #pragma instancing_options procedural:vertInstancingSetup
            #include "UnityCG.cginc"
            #include "UnityStandardParticleInstancing.cginc"
            struct appdata
            {
                float4 vertex : POSITION;
                fixed4 color : COLOR;
                float2 texcoord : TEXCOORD0;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };
            struct v2f
            {
                float4 vertex : SV_POSITION;
                fixed4 color : COLOR;
                float2 texcoord : TEXCOORD0;
# ifdef _TSANIM_BLENDING
                float3 texcoord2AndBlend : TEXCOORD1;   
# endif
            };
            sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed4 readTexture(sampler2D tex, v2f IN)
            {
                fixed4 color = tex2D(tex, IN.texcoord);
# ifdef _TSANIM_BLENDING
                fixed4 color2 = tex2D(tex, IN.texcoord2AndBlend.xy);
                color = lerp(color, color2, IN.texcoord2AndBlend.z);
# endif
                return color;
            }
            v2f vert(appdata v)
            {
                v2f o;
                UNITY_SETUP_INSTANCE_ID(v);
                o.color = v.color;
                o.texcoord = v.texcoord;
                vertInstancingColor(o.color);
# ifdef _TSANIM_BLENDING
                vertInstancingUVs(v.texcoord, o.texcoord, o.texcoord2AndBlend);
# else
                vertInstancingUVs(v.texcoord, o.texcoord);
# endif
                o.vertex = UnityObjectToClipPos(v.vertex);
                return o;
            }
            fixed4 frag(v2f i) : SV_Target
            {
                half4 albedo = readTexture(_MainTex, i);
                return i.color * albedo;
            }
            ENDCG
        }
    }
}

この例には、位置データをロードするためにサーフェスシェーダーと同じ設定コードが含まれています。

        #pragma instancing_options procedural:vertInstancingSetup
                #include "UnityStandardParticleInstancing.cginc"

頂点関数の変更はサーフェスシェーダーと非常によく似ています。

                vertInstancingColor(o.color);
                # ifdef _TSANIM_BLENDING
                                vertInstancingUVs(v.texcoord, o.texcoord, o.texcoord2AndBlend);
                # else
                                vertInstancingUVs(v.texcoord, o.texcoord);
                # endif

前出の最初の例と比較してここでの唯一の違いは、テクスチャシートアニメーションのブレンディングです。つまり、テクスチャシートアニメーションの 2 つのフレーム (1 つではなく) を読み込みブレンドするために、シェーダーに余分なテクスチャ座標のセットが必要だということです。

最後に、フラグメントシェーダーはテクスチャを読み込み、最終的な色を計算します。

カスタム頂点ストリームとパーティクルシステム GPU インスタンシング

前出の例では、パーティクルのデフォルトの頂点ストリーム設定のみを使用しています。これには、位置、法線、色、1 つの UV が含まれます。ただし、カスタム頂点ストリーム を使用することによって、速度、回転、サイズなど、他のデータをシェーダーに送信できます。

次の例では、シェーダーは特別な効果を示するように設計されています。より速いパーティクルをより明るく表示し、遅いパーティクルをより暗くします。速度の頂点ストリームを使用して、速度に応じてパーティクルを明るくするコードが加えられています。また、このシェーダーはテクスチャシートアニメーションを使用しない効果を想定しているため、テクスチャシートアニメーションはカスタムストリーム構造体からは削除されています。

以下は、完全なシェーダーです。

Shader "Instanced/ParticleMeshesCustomStreams"
{
    Properties
    {
        _MainTex("Albedo", 2D) = "white" {}
    }
    SubShader
    {
        Tags{ "RenderType" = "Opaque" }
        LOD 100
        Pass
        {
            CGPROGRAM
# pragma exclude_renderers gles
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_instancing
            #pragma instancing_options procedural:vertInstancingSetup
            #define UNITY_PARTICLE_INSTANCE_DATA MyParticleInstanceData
            #define UNITY_PARTICLE_INSTANCE_DATA_NO_ANIM_FRAME
            struct MyParticleInstanceData
            {
                float3x4 transform;
                uint color;
                float speed;
            };
            #include "UnityCG.cginc"
            #include "UnityStandardParticleInstancing.cginc"
            struct appdata
            {
                float4 vertex : POSITION;
                fixed4 color : COLOR;
                float2 texcoord : TEXCOORD0;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };
            struct v2f
            {
                float4 vertex : SV_POSITION;
                fixed4 color : COLOR;
                float2 texcoord : TEXCOORD0;
            };
            sampler2D _MainTex;
            float4 _MainTex_ST;
            v2f vert(appdata v)
            {
                v2f o;
                UNITY_SETUP_INSTANCE_ID(v);
                o.color = v.color;
                o.texcoord = v.texcoord;
                vertInstancingColor(o.color);
                vertInstancingUVs(v.texcoord, o.texcoord);
# if defined(UNITY_PARTICLE_INSTANCING_ENABLED)
                UNITY_PARTICLE_INSTANCE_DATA data = unity_ParticleInstanceData[unity_InstanceID];
                o.color.rgb += data.speed;
# endif
                o.vertex = UnityObjectToClipPos(v.vertex);
                return o;
            }
            fixed4 frag(v2f i) : SV_Target
            {
                half4 albedo = tex2D(_MainTex, i.texcoord);
                return i.color * albedo;
            }
            ENDCG
        }
    }
}

シェーダーは UnityStandardParticleInstancing.cginc を含み、カスタム頂点ストリームが使用されていないときのデフォルトのインスタンシングデータレイアウトを含んでいます。そのため、カスタムストリームを使用する場合は、そのヘッダーで定義されているデフォルトのいくつかをオーバーライドする必要があります。これらのオーバーライドは include の に置く必要があります。上の例では、以下のカスタムオーバーライドを設定しています。

最初に、UNITY_PARTICLE_INSTANCE_DATA マクロを使用して、カスタムストリームデータのための ‘MyParticleInstanceData’ というカスタム構造体を使用するよう指示しています。

            #define UNITY_PARTICLE_INSTANCE_DATA MyParticleInstanceData

次に、もう 1 つの定義で、Anim Frame Stream (アニメーションフレームストリーム) がこのシェーダーで必要でないことをインスタンシングシステムに伝えています。なぜなら、この例の効果はテクスチャシートアニメーションでの使用を意図していないためです。

            #define UNITY_PARTICLE_INSTANCE_DATA_NO_ANIM_FRAME

第 3 に、カスタムストリームデータの構造体が宣言されます。

            struct MyParticleInstanceData
                        {
                            float3x4 transform;
                            uint color;
                            float speed;
                        };

これらのオーバーライドはすべて UnityStandardParticleInstancing.cginc が含まれる前に置かれるため、シェーダーはこれらの定義にシェーダー自体のデフォルトを使用しません。

構造体を記述するとき、変数はパーティクルシステムの Renderer モジュールのインスペクターに列挙される頂点ストリームと一致する必要があります。つまり、Renderer モジュールの UI で使用したいストリームを選択し、それらをカスタムストリームデータ構造体の変数定義に同じ順序で加える必要があります。そのため、それらは一致します。

Renderer モジュールの UI に表示されたカスタムの頂点ストリーム。いくつかのインスタンス化されたストリームといくつかのインスタンス化されていないストリームを表示
Renderer モジュールの UI に表示されたカスタムの頂点ストリーム。いくつかのインスタンス化されたストリームといくつかのインスタンス化されていないストリームを表示

最初の項目 (Position) は必須なので、削除することはできません。プラス (+) とマイナス (-) のボタンを使って他のエントリーを自由に追加/削除することができ、頂点ストリームデータをカスタマイズできます。

リスト内で INSTANCED と示されるエントリーにはインスタンスデータが含まれているため、パーティクルインスタンスのデータ構造体に加える必要があります。INSTANCED という語のすぐ後に加えられた数字 (例えば、INSTANCED0 の 0 と INSTANCED1 の 1) は、最初の “transform” 変数の に、変数を構造体に置く順番を示します。末尾の文字 (.x .xy .xyz .xyzw) は、変数の型を示し、シェーダーコードの float、float2、float3、float4 変数の型に対応します。

リストに表示されていて、INSTANCED示されていない 他の頂点ストリームデータは、パーティクルインスタンスのデータ構造体から削除できます。なぜなら、それらは、シェーダーによって処理されるインスタンス化されたデータではないからです。このようなデータは、UV、法線、接線などのソースメッシュに属しています。

例を完成させる最後の手順は、頂点シェーダー内のパーティクルの色に速度を適用することです。

# if defined(UNITY_PARTICLE_INSTANCING_ENABLED)
                UNITY_PARTICLE_INSTANCE_DATA data = unity_ParticleInstanceData[unity_InstanceID];
                o.color.rgb += data.speed;
# endif

すべてのインスタンシングコードを UNITY_PARTICLE_INSTANCING_ENABLED のチェック内にラップする必要があります。そのようにすると、インスタンス化が使用されていないときにもコンパイルできます。

この時点で、フラグメントシェーダーにデータを渡したい場合は、他のシェーダーデータの場合と同様に、v2f 構造体にデータを書き込むことができます。

この例では、カスタムの頂点ストリームで使用するカスタムシェーダーを変更する方法について説明しますが、サーフェスシェーダーにも全く同じアプローチで同じ機能を実現できます。


  • 2018–03–28 公開ページ

  • パーティクルシステム GPU インスタンシングは Unity 2018.1 で追加NewIn20181

パーティクルシステム - 頂点ストリームとスタンダードシェーダーサポート
パーティクルシステム C# Job System インテグレーション