【Unity】ステンシルシャドウの丸影の覚書【ロストテクノロジー】
はじめに
今日ではシャドウボリュームをメインで使うこともなくなってきてますが、いざ使おうとしたら忘れてたので覚書。
段差でも破綻しない丸影のシェーダーです。
球をつける
丸影をつけたいオブジェクトに球体のモデルを付けます。UnityのディフォルトのSphere で問題ないです。
ステンシルパスのシェーダーをつける
1Pass 目
面を裏返した状態で、深度が奥なら合格でステンシルを書き込む
球が埋まっている部分だげ書き込まれます。
Pass
{
ColorMask 0
Cull Front
ZTest GEqual
Stencil
{
Ref 0
Comp Equal
Pass IncrSat
ZFail Zero
}//Stencil
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
float4 vert( float4 vertex : POSITION) : SV_POSITION
{
return UnityObjectToClipPos( vertex);
}
half4 frag() : SV_Target
{
return half4( 1, 0, 0, 1);
}
ENDCG
}//Pass
ColorMask 0 は色を書き込みません。
Cull Front で面を反転。
ZTest GEqual で奥だったら合格。
Stencil 内は
Ref 0 は 参照値 0
Comp Equal は Ref と等しかったら合格
Pass IncrSat だったら ステンシルに +1 します。
ZFail Zero は深度テストに失敗したら参照値を 0 クリア
これで球が埋まっている部分だけ ステンシル値が +1 されます。
2Pass 目
面を戻して深度値が手前、通常の深度テストだったら描画する球を書きます。
ステンシルテストは すでに 1 だったら +1 して2にする 処理を書きます。
Pass
{
ColorMask 0
Cull Back
ZTest LEqual
Stencil
{
Ref 1
Comp Equal
Pass IncrSat
Fail Zero
ZFail Zero
}//Stencil
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
float4 vert( float4 vertex : POSITION) : SV_POSITION
{
return UnityObjectToClipPos( vertex);
}
half4 frag() : SV_Target
{
return half4( 0, 1, 0, 1);
}
ENDCG
}//Pass
Cull Back は面を通常に戻す
ZTest LEqual は深度値が手前だったら合格
Stencil内は
Ref 1 は ステンシルの参照値 1
Comp Equal は 参照値と同じであれば合格。今回は 1で、1Pass目で赤が書かれている部分。
Pass IncrSat は 合格だったら更に +1。1Pass目と2Pass目が合格だったら +1 になる
Fail Zero は ステンシルテストか深度テストが不合格だったら 0クリア
ZFail Zero は深度テストが失敗だったら 0クリア
1Pass目と2Pass目が合格の部分が緑になっているのがわかります。
3Pass 目
最後は実際に色を塗ります。1Pass 2Pass が合格していれば ステンシル値が 2 になっているのでその部分を 影の色でレンダリングします。
青い部分がすべて合格した部分。
Pass
{
Blend SrcAlpha OneMinusSrcAlpha // 昔ながらの透明
ZTest Off
Cull Back
Stencil
{
Ref 2
Comp Equal
Pass Keep
ZFail Zero
}//Stencil
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
sampler2D _MainTex;
float4 _MainTex_ST;
float4 vert( float4 vertex : POSITION) : SV_POSITION
{
return UnityObjectToClipPos( vertex);
}
half4 frag() : SV_Target
{
return float4( 0, 0, 0, 0.5);
}
ENDCG
}//Pass
今回は黒の半透明で描画します。必要に応じてテクスチャーや頂点計算で色を塗るといいかもしれないです。
排他的処理
実はこのままでは問答無用でどこでも描画してしまいます。
今回の場合は、階段と床だけに描画してユニティちゃんには書きたくないわけです。
回避策として、落とす部分に目印をつけるか、落とさないところに目印をつけるかですが、経験則では落とすところに目印をつけるのが効率的です。
影を落としていいところに ステンシル値を1を入れておいて、上で書いた Pass の ref 値を全体的に 1 多くします。
影を落としたくない ユニティちゃん部分などは 1Pass 目で失格になるので影が落ちなくなります。
というわけで全体的に書き直した shader がこちら
Shader "Neko/StencilShadow"
{
Properties
{
}//Properties
CGINCLUDE
#include "UnityCG.cginc"
ENDCG
SubShader
{
Tags
{
"Queue" = "Transparent"
"IgnoreProjector" = "True"
}
ZWrite Off
Pass
{
ColorMask 0
Cull Front
ZTest GEqual
Stencil
{
Ref 1
Comp Equal
Pass IncrSat
ZFail Zero
}//Stencil
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
float4 vert( float4 vertex : POSITION) : SV_POSITION
{
return UnityObjectToClipPos( vertex);
}
half4 frag() : SV_Target
{
return half4( 1, 0, 0, 1);
}
ENDCG
}//Pass
Pass
{
ColorMask 0
Cull Back
ZTest LEqual
Stencil
{
Ref 2
Comp Equal
Pass IncrSat
Fail Zero
ZFail Zero
}//Stencil
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
float4 vert( float4 vertex : POSITION) : SV_POSITION
{
return UnityObjectToClipPos( vertex);
}
half4 frag() : SV_Target
{
return half4( 0, 1, 0, 1);
}
ENDCG
}//Pass
Pass
{
Blend SrcAlpha OneMinusSrcAlpha // 昔ながらの透明
ZTest Off
Cull Back
Stencil
{
Ref 3
Comp Equal
Pass Keep
ZFail Zero
}//Stencil
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
float4 vert( float4 vertex : POSITION) : SV_POSITION
{
return UnityObjectToClipPos( vertex);
}
half4 frag() : SV_Target
{
return float4( 0, 0, 0, 0.5);
}
ENDCG
}//Pass
}//SubShader
}//Shader
各 Pass の Ref 値が 1増えてます。
最終的に ステンシル値が 3 なら影を描画します。
必要なら Passを追加して ステンシルをクリアするなど臨機応変に対応お願いします。
床や階段など影を落とす部分には ステンシル値 1 を設定するようにしてください。
ステンシル早見表
比較関数 ZTest Comp
Greater ピクセルのレファレンス値がバッファの値より大きい場合のみレンダリングします。
GEqual ピクセルのレファレンス値がバッファの値より大きいか等しい場合のみレンダリングします。
Less ピクセルのレファレンス値がバッファの値より小さい場合のみレンダリングします。
LEqual ピクセルのレファレンス値がバッファの値より小さいか等しい場合のみレンダリングします。
Equal ピクセルのレファレンス値がバッファの値と等しい場合のみレンダリングします。
NotEqual ピクセルのレファレンス値がバッファの値と等しくない場合のみレンダリングします。
Always 常にステンシルテストをパスさせます。
Never 常にステンシルテストを通しません。
ステンシルの操作 Stencil 内の Pass Fail ZFail
Keep バッファの現在コンテンツを保持します。
Zero バッファに 0 を書き込みます。
Replace リファレンス値をバッファに書き込みます。
IncrSat バッファの現在値をインクリメントさせます。値がすでに 255 の場合は 255 のままです。
DecrSat バッファの現在値をデクリメントさせます。値がすでに 0 の場合は 0 のままです。
Invert すべてのビットを反転させます。
IncrWrap バッファの現在値をインクリメントさせます。値がすでに 255 の場合は 0 にします。
DecrWrap バッファの現在値をデクリメントさせます。値がすでに 0 の場合は 255 にします。