GPU インスタンシングは、CPU レンダリングと比較して著しくパフォーマンスを向上させます。パーティクルシステムで、デフォルトの レンダリングモード の ビルボード パーティクルではなく、メッシュ パーティクルをレンダリングしたい場合に使用できます。
パーティクルシステムで GPU インスタンシングを使用できるようにするには、以下の手順を行います。
パーティクルシステムの Render Mode を Mesh に設定します。
GPU インスタンシングをサポートする Renderer の Material のシェーダーを使用します。
GPU インスタンシングをサポートするプラットフォームでプロジェクトを実行します
パーティクルシステムの GPU インスタンシングを使用可能するには、パーティクルシステムの Renderer モジュールの Enable GPU Instancing チェックボックスにチェックを入れる必要があります。
Unity には、GPU インスタンシングをサポートするビルトインのパーティクルシェーダーがありますが、デフォルトのパーティクルマテリアルはそれを使用しません。そのため、GPU インスタンシングを使用するには、これを変更する必要があります。GPU インスタンシングをサポートするパーティクルシェーダーは Particles/Standard Surface と呼ばれます。これを使用するには、独自の マテリアル を作成し、マテリアルのシェーダーを Particles/Standard Surface に設定します。次に、この新しいマテリアルをパーティクルシステムの Renderer モジュールの Material フィールドに割り当てます。
パーティクルに異なるシェーダーを使用する場合は、 ‘#pragma target 4.5’ 以上を使用する必要があります。詳細は、シェーダーコンパイルターゲットレベル を参照してください。この要件は Unity の通常の GPU インスタンシングよりも高くなります。なぜなら、パーティクルシステムは複数のドローコールにインスタンシングを分割するのではなく、すべてのインスタンスデータを 1 つの大きなバッファに書き込むからです。
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 インスタンシングを使用したカスタムシェーダーの完全な使用例です。このカスタムシェーダーは、標準のパーティクルシェーダーにはない機能である テクスチャシートアニメーション の個々のフレーム間のフェード、が可能になります。
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 つではなく) を読み込みブレンドするために、シェーダーに余分なテクスチャ座標のセットが必要だということです。
最後に、フラグメントシェーダーはテクスチャを読み込み、最終的な色を計算します。
前出の例では、パーティクルのデフォルトの頂点ストリーム設定のみを使用しています。これには、位置、法線、色、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 で使用したいストリームを選択し、それらをカスタムストリームデータ構造体の変数定義に同じ順序で加える必要があります。そのため、それらは一致します。
最初の項目 (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 構造体にデータを書き込むことができます。
この例では、カスタムの頂点ストリームで使用するカスタムシェーダーを変更する方法について説明しますが、サーフェスシェーダーにも全く同じアプローチで同じ機能を実現できます。