UnityのURPで、Flip-Book Blendingに対応したParticle用シェーダを書く方法についてまとめました。
Unity2020.3.15f2
やりたいこと
ParticleのTexture Sheet Animationを使うとFlip-Book(パラパラ漫画)アニメーションができます。
さらにFlip-Book Blendingに対応したシェーダを使うとアニメーション間を補間して滑らかにできます。
本記事ではこのFlip-Book Blendingに対応したシェーダを記述する方法についてまとめます。
レンダリングパイプラインはUniversal Render Pipeline(URP)とします。
またGPUインスタンシングにも対応したシェーダを書くことを目標とします。
Flip-Book Blending対応シェーダを書く
まずGPUインスタンシングには対応せず、Flip-Book Blendingにのみ対応したシェーダを記述します。
この場合実装は単純で、Texture Sheet Animationを有効にすると、
- Custom Vertex StreamsのUVでブレンド対象の1枚目のUV値を受け渡せる
- Custom Vertex StreamsのUV2でブレンド対象の2枚目のUV値を受け渡せる
- Custom Vertex StreamsのAnimBlendで上記2枚のブレンド率の値を受け渡せる
となります。
すなわちCustom Vertex Streamsを以下のように設定すれば任意のTEXCOORDを使って上記の値をシェーダに渡せます。
次にこの仕様に沿ってシェーダを書いていきます。
以下の通り、Flip-Book Blendingをマテリアルプロパティで有効/無効にできるシェーダを記述しました。
Shader "FlipBookBlendingExample" { Properties { [MainTexture] _BaseMap("Base Map", 2D) = "white" {} [MainColor] _BaseColor("Base Color", Color) = (1, 1, 1, 1) [Toggle] _FlipBookBlending ("Flip-Book Blending", Float) = 0 } SubShader { Tags { "RenderType" = "Opaque" "IgnoreProjector" = "True" "PreviewType" = "Plane" "PerformanceChecks" = "False" "RenderPipeline" = "UniversalPipeline" } Pass { HLSLPROGRAM #pragma vertex vert #pragma fragment frag #pragma shader_feature_local _FLIPBOOKBLENDING_ON #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" struct Attributes { float4 positionOS : POSITION; #if defined(_FLIPBOOKBLENDING_ON) float4 texcoords : TEXCOORD0; float animBlend : TEXCOORD1; #else float2 texcoord : TEXCOORD0; #endif }; struct Varyings { float4 positionHCS : SV_POSITION; #if defined(_FLIPBOOKBLENDING_ON) float4 texcoords : TEXCOORD0; float animBlend : TEXCOORD1; #else float2 texcoord : TEXCOORD0; #endif }; sampler2D _BaseMap; float4 _BaseMap_ST; half4 _BaseColor; Varyings vert(Attributes input) { Varyings output; output.positionHCS = TransformObjectToHClip(input.positionOS.xyz); #if defined(_FLIPBOOKBLENDING_ON) output.texcoords.xy = TRANSFORM_TEX(input.texcoords.xy, _BaseMap); output.texcoords.zw = TRANSFORM_TEX(input.texcoords.zw, _BaseMap); output.animBlend = input.animBlend; #else output.texcoord = TRANSFORM_TEX(input.texcoord, _BaseMap); #endif return output; } half4 frag(Varyings input) : SV_Target { #if defined(_FLIPBOOKBLENDING_ON) // 二つのテクスチャの値をanimBlendで補間 const float4 color1 = tex2D(_BaseMap, input.texcoords.xy) * _BaseColor; const float4 color2 = tex2D(_BaseMap, input.texcoords.zw) * _BaseColor; return lerp(color1, color2, input.animBlend); #else return tex2D(_BaseMap, input.texcoord) * _BaseColor; #endif } ENDHLSL } } }
これを適用すると以下のような結果となります。
マテリアルプロパティによりブレンドの有無が切り替わっていることが確認できます。
GPUインスタンシングに対応する
さて次にこれをGPUインスタンシングにも対応します。
GPUインスタンシングを使う場合には、上記のAnimBlendではなく、現在のテクスチャのインデックスを表すAnimFrameを使って処理する必要があります。
場合分けが多くのなるためシェーダは以下のようにかなり複雑になります。
Shader "InstancedFlipBookBlendingExample" { Properties { [MainTexture] _BaseMap("Base Map", 2D) = "white" {} [MainColor] _BaseColor("Base Color", Color) = (1, 1, 1, 1) [Toggle] _FlipBookBlending ("Flip-Book Blending", Float) = 0 } 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 #pragma shader_feature_local _FLIPBOOKBLENDING_ON #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" // UNITY_PARTICLE_INSTANCE_DATAを使いたいのでこれをインクルードする #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/ParticlesInstancing.hlsl" struct Attributes { float4 positionOS : POSITION; // Flip-Book Blendingが有効かつGPUインスタンシングが無効の場合のみAnimBlendの値を使う #if defined(_FLIPBOOKBLENDING_ON) && !defined(UNITY_PARTICLE_INSTANCING_ENABLED) float4 texcoords : TEXCOORD0; float texcoordBlend : TEXCOORD1; #else float2 texcoords : TEXCOORD0; #endif UNITY_VERTEX_INPUT_INSTANCE_ID }; struct Varyings { float4 positionHCS : SV_POSITION; float2 texcoord : TEXCOORD0; #if defined(_FLIPBOOKBLENDING_ON) float3 texcoord2AndBlend : TEXCOORD1; #endif }; TEXTURE2D(_BaseMap); SAMPLER(sampler_BaseMap); float4 _BaseMap_ST; half4 _BaseColor; // Particles.hlslからコピーしたもの void GetParticleTexcoords(out float2 outputTexcoord, out float3 outputTexcoord2AndBlend, in float4 inputTexcoords, in float inputBlend) { #if defined(UNITY_PARTICLE_INSTANCING_ENABLED) if (unity_ParticleUVShiftData.x != 0.0) { UNITY_PARTICLE_INSTANCE_DATA data = unity_ParticleInstanceData[unity_InstanceID]; float numTilesX = unity_ParticleUVShiftData.y; float2 animScale = unity_ParticleUVShiftData.zw; #ifdef UNITY_PARTICLE_INSTANCE_DATA_NO_ANIM_FRAME float sheetIndex = 0.0; #else float sheetIndex = data.animFrame; #endif float index0 = floor(sheetIndex); float vIdx0 = floor(index0 / numTilesX); float uIdx0 = floor(index0 - vIdx0 * numTilesX); float2 offset0 = float2(uIdx0 * animScale.x, (1.0 - animScale.y) - vIdx0 * animScale.y); outputTexcoord = inputTexcoords.xy * animScale.xy + offset0.xy; #ifdef _FLIPBOOKBLENDING_ON float index1 = floor(sheetIndex + 1.0); float vIdx1 = floor(index1 / numTilesX); float uIdx1 = floor(index1 - vIdx1 * numTilesX); float2 offset1 = float2(uIdx1 * animScale.x, (1.0 - animScale.y) - vIdx1 * animScale.y); outputTexcoord2AndBlend.xy = inputTexcoords.xy * animScale.xy + offset1.xy; outputTexcoord2AndBlend.z = frac(sheetIndex); #endif } else #endif { outputTexcoord = inputTexcoords.xy; #ifdef _FLIPBOOKBLENDING_ON outputTexcoord2AndBlend.xy = inputTexcoords.zw; outputTexcoord2AndBlend.z = inputBlend; #endif } #ifndef _FLIPBOOKBLENDING_ON outputTexcoord2AndBlend.xy = inputTexcoords.xy; outputTexcoord2AndBlend.z = 0.5; #endif } void GetParticleTexcoords(out float2 outputTexcoord, in float2 inputTexcoord) { float3 dummyTexcoord2AndBlend = 0.0; GetParticleTexcoords(outputTexcoord, dummyTexcoord2AndBlend, inputTexcoord.xyxy, 0.0); } Varyings vert(Attributes input) { Varyings output; UNITY_SETUP_INSTANCE_ID(input); output.positionHCS = TransformObjectToHClip(input.positionOS.xyz); // インスタンシングとFlip-Book Blendingの条件により使用するオーバーロードを変える #if defined(_FLIPBOOKBLENDING_ON) #if defined(UNITY_PARTICLE_INSTANCING_ENABLED) GetParticleTexcoords(output.texcoord, output.texcoord2AndBlend, input.texcoords.xyxy, 0.0); #else GetParticleTexcoords(output.texcoord, output.texcoord2AndBlend, input.texcoords, input.texcoordBlend); #endif #else GetParticleTexcoords(output.texcoord, input.texcoords.xy); #endif return output; } half4 frag(Varyings input) : SV_Target { float3 blendUv = 0; #if defined(_FLIPBOOKBLENDING_ON) blendUv = input.texcoord2AndBlend; #endif half4 color = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.texcoord); #ifdef _FLIPBOOKBLENDING_ON // Flip-Book Blendingが有効な時のみブレンドする half4 color2 = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, blendUv.xy); color = lerp(color, color2, blendUv.z); #endif return color; } ENDHLSL } } }
ポイントはGetParticleTexcoordsです。
これはParticles.hlslからコピーしたものですが、この中でインスタンシングの場合にAnimFrameなどを使ってFlip-Book Blendingを行う処理を行なっています。
このシェーダを使ってGPUインスタンシングを効かせた状態でFlip-Book Blendingするには以下の設定を行います。
- マテリアルプロパティからFlip-Book Blendingを有効にする
- Particle SystemのRendererモジュールのRender ModeをMeshにして適当なメッシュをアサイン
- Particle SystemのRendererモジュールのEnable Mesh GPU Instancingを有効にする
- Custom Vertex StreamsにAnimFrameをInstanced.xとして渡す
この設定で再生した結果が以下となります。
正常に描画されていることが確認できました。