本日はShader枠です。
〇環境
・Windows11PC
・Unity6000.3.2f1
・URP
〇ステンシルとは?
Shaderにおけるステンシル(Stencil)は、特定のピクセルを描画するかどうかを制御するための機能です。
シェーダープログラムでは最終的な出力をRGBの色としていますが、例えばアルファ値などのように直接画素に影響するわけではなく、ほかの描画計算と影響しあい最終描画を行うために用いられるパラメータのように様々な値を出力できます。
ステンシルはステンシルバッファと呼ばれるリソースに格納される出力であり、ステンシルバッファを使用することで、複雑なマスク処理やオブジェクトの描画順序を制御することができます。
例えばステンシルを用いることで、あるオブジェクトを通さないと描画されないオブジェクトということが実装できます。

ステンシルバッファを書き込むことで、描画準に応じて、同じピクセルにおいてすでに書き込まれているステンシルバッファの値と比較して処理を行うことができ、これを活用するシェーダーをステンシルの処理と呼びます。
〇ステンシルを使用したマスク表現
ステンシルはPass内、CGPROGRAM~ENDCGブロックの前に記述します。
Stencil
{
Ref 1
Comp Always
Pass Replace
}
パラメータは今回は3つ定義しています。
それぞれ次のような意味になります。
・Ref: ステンシルバッファに書き込む参照値
・Comp: ステンシルテストの比較関数
・Pass: ステンシルテストが成功した場合の操作
Refは数値を用いて、この数値が書き込まれるステンシル値になります。
Compではすでに描画されているピクセルのステンシル値と比較する際の挙動を定義します。
Alwaysはステンシルの比較を常に行うという意味です。
つまりすでにステンシル値が定義されていた場合は常にステンシルテストに合格するという意味です。
最後のPassでは、ステンシルテストに成功した際の処理を定義します。
例ではReplace…つまりすでに書き込まれている描画色と現在のシェーダーの計算色を置き換えて現在のシェーダーの色を採用するという意味です。
今回はこれを用いて2つのシェーダーを作成しました。
Shader "Custom/StencilMask"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "Queue" = "Geometry" }
Pass
{
Stencil
{
Ref 1
Comp Always
Pass Replace
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex;
struct appdata_t
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
v2f vert (appdata_t v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
return tex2D(_MainTex, i.uv);
}
ENDCG
}
}
}
Shader "Custom/StencilMasked"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "Queue" = "Geometry+1" }
Pass
{
Stencil
{
Ref 1
Comp Equal
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex;
struct appdata_t
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
v2f vert (appdata_t v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
return tex2D(_MainTex, i.uv);
}
ENDCG
}
}
}
一つ目のシェーダーは最もシンプルなUnlitシェーダーにステンシルを定義しています。

2つ目のシェーダーは通常時は見えません。

しかし1つ目のシェーダーのマテリアルを通してであれば描画されます。

これはステンシルを含む2つのテクニックがあります。
〇今回のシェーダーのテクニック
今回2つ目の通常時は見えないシェーダーではステンシルの他にRenderQueueにも秘密があります。
Tags { "Queue" = "Geometry+1" }
Geometryとは2000を意味し、このシェーダーは2001のキューを持ちます。

RenderQueueは描画順番であり、通常若い順番で描画されます。
つまり、このオブジェクトはほかの不透明オブジェクトよりも後に描画されます。
後に描画された状態で
Stencil
{
Ref 1
Comp Always
Pass Replace
}
のステンシル値が書き込まれているピクセルに
Stencil
{
Ref 1
Comp Equal
}
のピクセルが描画される際に、ステンシルテストが行われます。具体的には、最初のシェーダーがステンシルバッファに値 1 を書き込みます。
この値が書き込まれたピクセルに対して、次のシェーダーが描画される際に、ステンシルテストが実行されます。
ステンシルテストの比較関数 Comp Equal は、ステンシルバッファの値が Ref で指定された値(この場合は 1)と等しい場合にのみ描画を許可します。したがって、最初のシェーダーによってステンシルバッファに値 1 が書き込まれたピクセルに対してのみ、次のシェーダーの描画が行われます。
この仕組みでシェーダー1を使用したマテリアルを通してのみ描画される表現ができるのです。
本日は以上です。