PostProcessingStack、便利ですよね~。 僕はUnity新しいプロジェクト始めるとき、大抵最初にMain Cameraにセットして、Bloom、Depth of Field (DoF)、色収差、ビネット、ACESトーンマップを有効にしてます。
ただ、最近の案件で、透過画像のスプライトにDoFが効かないという現象に出くわして苦労しました。 なんとか対処したので、対処法を書いておきます。
半透明部分の表示がおかしいなど、完全な解決策ではないので、詳しい方はぜひ教えてください~
問題のシーン
今回問題になるのは、このような半透明部分を含んだ透過png画像です。
表面の光沢も表現したいので、Standardマテリアルで Renderting ModeをFadeにしてみます。
この状態でシーンに配置してみましょう。
うーん、ピントが合わない……
シーン上には、キューブと半透明のスプライトを等間隔に並べています。
左のキューブにはフォーカスが当たっていますが、右側のスプライトは全てボヤケてしまっています。
半透明な部分がない画像の場合、StandardマテリアルでRendering ModeをCutoutにしてしまえばピントが合うのですが、今回のような画像だと半透明の部分が消えてしまいます。
Transparentなオブジェクトはデプスを書き込まない
unity transparent dof
でググってみると、PostProsessingStackのissueが見つかりました。
Transparentなオブジェクトはデプスバッファに書き込まないため、デプスを利用するDoFでは無視されてしまう。 なので、明示的にデプスを書いてあげれば良いようです。
また、このissueでは、デプスを書き込むシェーダーのサンプルが紹介されています。
Dummy transparent shader that writes to depth (per request) · GitHub
シェーダーを読んで見ると、どうやら Tags { "LightMode"="ShadowCaster" }
なPassを追加し、フラグメントシェーダー内で SHADOW_CASTER_FRAGMENT(i)
を呼んでやれば、デプスバッファに書き込めるみたいです。
ShadowCasterのシェーダーは名前の通り影の計算に用いられますが、デプスバッファにも反映されます。
さっそく問題のスプライトでもやってみましょう。
シェーダーを Create > Shader > Standard Surface Shader
から作成し、ShadowCasterのSubShaderを追加して、スプライトのマテリアルに指定します。
SubShader { Pass { Name "ShadowCaster" Tags { "LightMode"="ShadowCaster" } CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_shadowcaster #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { V2F_SHADOW_CASTER; float2 uv : TEXCOORD1; }; sampler2D _MainTex; float4 _MainTex_ST; float _Cutout; v2f vert(appdata v) { v2f o; o.uv = TRANSFORM_TEX(v.uv, _MainTex); TRANSFER_SHADOW_CASTER(o) return o; } float4 frag( v2f i ) : COLOR { fixed4 texcol = tex2D( _MainTex, i.uv ); if (texcol.a < _Cutout) { discard; } SHADOW_CASTER_FRAGMENT(i) } ENDCG } }
実行!
あれー
不透明な部分だけデプスバッファに書きこむ
どうやら、半透明な部分もデプスが書き込まれ、黒として描画されてしまったようです。
仕方ないので、半透明な部分にピントを合わせるのを諦め、ShadowCasterのPassではdiscardするようにします。
幸い、今回の画像で半透明な部分はドロップシャドウだけなので、ボヤケててもあまり気にならないはず……
float4 frag( v2f i ) : COLOR { fixed4 texcol = tex2D( _MainTex, i.uv ); if (texcol.a < _Cutout) { discard; } SHADOW_CASTER_FRAGMENT(i) }
最終結果
やったー
よく見ると半透明部分の描画がおかしいけど、許容範囲ということで……
まとめ
必要な手順をまとめると以下のとおりです:
- カメラをDeferredにする
- シェーダーに
ShadowCaster
のパスを追加 - 画像のアルファ値によって、透明部分をdiscardする
今回作ったSurfaceシェーダーはこちらに置いておきます。
https://gist.github.com/fand/31bcfa5cd9008a270efb628503200871
もっと良いやり方知ってる人教えてください、頼む!!!!!!