【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 にします。
この作品はユニティちゃんライセンス条項の元に提供されています

Add a Comment

メールアドレスが公開されることはありません。 が付いている欄は必須項目です