UnityのURPでCustom Vertex Streams + GPUインスタンシングに対応したParticleSystem用シェーダを書く方法についてまとめました。
- やりたいこと
- Custom Vertex Streams + GPUインスタンシング対応シェーダ
- 挙動を確認する
- フラグメントシェーダでインスタンシングされた情報を取得する
- Flip-Book BlendingとGPUインスタンシング対応
- 関連
Unity2020.3.15f2
やりたいこと
今、Universal Render Pipeline(URP)のParticleSystemで、Noiseの値をCustom Vertex Streamsで渡して色として出力したいとします。
そしてさらにこれをGPU Instancingに対応することを考えます。
本記事ではこの実装方法についてまとめます。
GPUインスタンシングの基礎知識は以下の記事を参照してください。
また、Custom Vertex Streamsの基礎知識は以下の記事にまとめています。
Custom Vertex Streams + GPUインスタンシング対応シェーダ
さてそれではCustom Vertex StreamsとGPUインスタンシングに対応したシェーダを書いていきます。
Custom Vertex Streamsで与えた3次元の値をそのまま色として出力するシンプルなシェーダです。
Shader "InstancingExample" { Properties { [MainTexture] _BaseMap("Base Map", 2D) = "white" {} [MainColor] _BaseColor("Base Color", Color) = (1, 1, 1, 1) } SubShader { Tags { "RenderType" = "Opaque" "IgnoreProjector" = "True" "PreviewType" = "Plane" "PerformanceChecks" = "False" "RenderPipeline" = "UniversalPipeline" } Pass { HLSLPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_instancing #pragma instancing_options procedural:ParticleInstancingSetup // ParticlesInstancing.cgincのDefaultParticleInstanceDataの代わりにこちらを使う設定 // ParticlesInstancing.cgincのインクルードよりも先に書くこと #define UNITY_PARTICLE_INSTANCE_DATA MyDefaultParticleInstanceData struct MyDefaultParticleInstanceData { float3x4 transform; float3 foo; }; #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" // ParticleのシェーダでGPUインスタンシング対応する場合にはこれをインクルードする #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/ParticlesInstancing.hlsl" struct Attributes { float4 positionOS : POSITION; #ifndef UNITY_PARTICLE_INSTANCING_ENABLED float3 foo : TEXCOORD0; #endif UNITY_VERTEX_INPUT_INSTANCE_ID }; struct Varyings { float4 positionHCS : SV_POSITION; float3 foo : TEXCOORD0; }; Varyings vert(Attributes input) { Varyings output; UNITY_SETUP_INSTANCE_ID(input); output.positionHCS = TransformObjectToHClip(input.positionOS.xyz); #ifdef UNITY_PARTICLE_INSTANCING_ENABLED // インスタンスのデータを取得する UNITY_PARTICLE_INSTANCE_DATA data = unity_ParticleInstanceData[unity_InstanceID]; output.foo = data.foo; #else output.foo = input.foo; #endif return output; } half4 frag(Varyings input) : SV_Target { return float4(abs(input.foo), 1); } ENDHLSL } } }
ポイントはコメントの形で記述しました。
Custom Vertex Streamsで設定した値を、GPUインスタンシングが有効なときにはインスタンス情報として、
GPUインスタンシングが無効なときには頂点情報として取得しています。
挙動を確認する
それではここまでの実装の挙動を確認します。
まずParticle SystemのNoiseモジュールを有効にして適当に設定をしておきます。
次にCustom Vertex Streamsを以下のように設定します。
さらにGPUインスタンシングを確認できるようにRender ModeはMeshにして適当なメッシュを設定しておきます。
この状態で再生すると以下の結果が得られます。
GPUインスタンシングをON/OFFしても同様の結果が得られることを確認できました。
フラグメントシェーダでインスタンシングされた情報を取得する
さて前節では頂点シェーダでインスタンスの情報を取得しました。
フラグメントシェーダにインスタンス情報を渡す場合には以下のようにもう少し対応が必要です。
Shader "InstancingExampleFragment" { Properties { [MainTexture] _BaseMap("Base Map", 2D) = "white" {} [MainColor] _BaseColor("Base Color", Color) = (1, 1, 1, 1) } SubShader { Tags { "RenderType" = "Opaque" "IgnoreProjector" = "True" "PreviewType" = "Plane" "PerformanceChecks" = "False" "RenderPipeline" = "UniversalPipeline" } Pass { HLSLPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_instancing #pragma instancing_options procedural:ParticleInstancingSetup #define UNITY_PARTICLE_INSTANCE_DATA MyDefaultParticleInstanceData struct MyDefaultParticleInstanceData { float3x4 transform; float3 foo; }; #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/ParticlesInstancing.hlsl" struct Attributes { float4 positionOS : POSITION; #ifndef UNITY_PARTICLE_INSTANCING_ENABLED float3 foo : TEXCOORD0; #endif UNITY_VERTEX_INPUT_INSTANCE_ID }; struct Varyings { float4 positionHCS : SV_POSITION; #ifndef UNITY_PARTICLE_INSTANCING_ENABLED float3 foo : TEXCOORD0; #endif // インスタンスID受け渡し用 UNITY_VERTEX_INPUT_INSTANCE_ID }; Varyings vert(Attributes input) { Varyings output; UNITY_SETUP_INSTANCE_ID(input); // VertexからFragmentへインスタンスIDを渡す UNITY_TRANSFER_INSTANCE_ID(input, output); output.positionHCS = TransformObjectToHClip(input.positionOS.xyz); #ifndef UNITY_PARTICLE_INSTANCING_ENABLED output.foo = input.foo; #endif return output; } half4 frag(Varyings input) : SV_Target { // インスタンスID初期化 UNITY_SETUP_INSTANCE_ID(input); #ifdef UNITY_PARTICLE_INSTANCING_ENABLED // インスタンスデータを取得 UNITY_PARTICLE_INSTANCE_DATA data = unity_ParticleInstanceData[unity_InstanceID]; return float4(abs(data.foo), 1); #else return float4(abs(input.foo), 1); #endif } ENDHLSL } } }
主な変更点はコメントに記述しました。
要は頂点シェーダからフラグメントシェーダにインスタンスIDを受け渡しているだけですが、
そのためのマクロがUnity側でいくつか用意されているのでそれを使っています。
Flip-Book BlendingとGPUインスタンシング対応
本記事では任意のデータをCustom Vertex Streamsとしてシェーダに渡す方法について紹介しました。
これに対してFlip-Book Blendingをする場合、AnimBlendやAnimFrameといった情報をCustom Vertex Streamsに渡す必要があります。
さらにGPUインスタンシングと組み合わせると非常にややこしくなるため、これは別記事として書きました。