はじめに
ステンシルバッファへの理解を深めるため、ステンシルバッファによってオブジェクトがどのようにして描画されていくのかを整理してみました。
#環境
Windows 10
Unity 5.6.1f1
リファレンス値とバッファ値
Unityのステンシルテストではリファレンス値とバッファ値を使って3Dオブジェクトを描画させるか否かを決定しているそうです。
画面の各ピクセルはバッファ値を持っており、これから描画させようとしている3Dオブジェクトはリファレンス値を持っている。
この二つを比較することで描画させるか否かを判定しているそうです。
参考
https://docs.unity3d.com/jp/540/Manual/SL-Stencil.html
ステンシルバッファの挙動を見てみる
ステンシルバッファの挙動を見るため、シーンに赤色と緑色の板を以下のように置くことを考えます。
![](https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.amazonaws.com%2F0%2F44288%2Fce6810c2-c980-d886-cd89-7deec5010d8f.png?ixlib=rb-4.0.0&auto=format&gif-q=60&q=75&s=aec8dcc322fca370910976d7786ab05c)
それぞれの板にはステンシルが定義されたシェーダーがアタッチされています。
赤の板を手前に出す
シェーダーのステンシルを以下のように設定すると、緑の板の手前に赤の板が表示されるようになります。
Stencil {
Ref 2 // リファレンス値
Comp Always // 常にステンシルテストをパスさせます。
Pass Replace // リファレンス値をバッファに書き込みます。
}
Stencil {
Ref 1 // リファレンス値
Comp Greater // ピクセルのリファレンス値がバッファの値より大きい場合のみレンダリングします。
}
![](https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.amazonaws.com%2F0%2F44288%2F5e2fa1b2-66a0-70b8-b915-abd48f953bf2.png?ixlib=rb-4.0.0&auto=format&gif-q=60&q=75&s=2fa4f2097a7f23e2707b2570049db868)
なぜこうなるのか整理してみる
なぜこのような描画結果になるのか私には理解できませんでした。
そこで、どのようにして板が描画されていくのかを整理してみることにしました。
STEP 1. 赤の板の描画
赤の板のステンシルでは Ref 2 が指定されています。
これはリファレンスの値を2にする、という意味の命令らしいです。
そしてPass Replaceによってリファレンスの値が画面のバッファへと書きこまれます。
このときの画面の状態は以下のようになります。
STEP 2. 緑の板の描画
緑の板のステンシルには Ref 1 が指定されています。
つまり、緑の板の描画時のリファレンスの値が1になります。
ここで、緑の板のステンシルには**Comp Greater**と記述されています。 これは 「**ピクセルのリファレンス値がバッファの値より大きい場合のみレンダリングする**」 という意味の命令らしいです。
結果として赤い板が描画されていない部分で緑色の板が描画されることになります。
![](https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.amazonaws.com%2F0%2F44288%2F49bffa30-8b5f-9e84-dc14-1f0365ce9fd5.png?ixlib=rb-4.0.0&auto=format&gif-q=60&q=75&s=17882bb175820dde64ff231b46fe3f41)
緑の板をくり抜く
緑の板のシェーダーのステンシルを以下のように設定すると緑の板が赤の板でくり抜かれます。
Stencil {
Ref 2
Comp Equal // ピクセルのリファレンス値がバッファの値と等しい場合のみレンダリングします。
}
![](https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.amazonaws.com%2F0%2F44288%2F7394e7b7-30d3-4650-204a-7c54fd929381.png?ixlib=rb-4.0.0&auto=format&gif-q=60&q=75&s=f89f5981c10373bbd3e763e787176895)
なぜこうなるのか整理してみる
緑の板のステンシルではComp Equal が指定されているため、緑の板が描画されるのはバッファ値 = 2 となっている領域に限定されます。
赤い板が描画されている領域はバッファ値 = 2 ですが、それ以外の領域のバッファ値は2ではありません。
![](https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.amazonaws.com%2F0%2F44288%2Fb620595d-bbe2-03be-328c-902150604ecc.png?ixlib=rb-4.0.0&auto=format&gif-q=60&q=75&s=391c8d8545eac3190d64117457c1c96c)
つまり、赤い板の表示領域だけで緑の板が描画されるので以下のような描画結果になります。
![](https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.amazonaws.com%2F0%2F44288%2F7394e7b7-30d3-4650-204a-7c54fd929381.png?ixlib=rb-4.0.0&auto=format&gif-q=60&q=75&s=f89f5981c10373bbd3e763e787176895)
緑の板のリファレンス値を変える
緑の板のシェーダーのステンシルのリファレンス値を0にしてやると、赤の板以外の領域で緑の板が描画されるようになります。
Stencil {
Ref 0
Comp Equal // ピクセルのリファレンス値がバッファの値と等しい場合のみレンダリングします。
}
![](https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.amazonaws.com%2F0%2F44288%2Fd8e9530b-58d5-28eb-ac71-9205bb5a733f.png?ixlib=rb-4.0.0&auto=format&gif-q=60&q=75&s=f950fe8c325415433772287712f7bc61)
これは、何も描画されていない領域のバッファ値が0になっているのが理由だと考えられます。
![](https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.amazonaws.com%2F0%2F44288%2F5c717e7b-359d-baae-eecc-74b59a3ddf55.png?ixlib=rb-4.0.0&auto=format&gif-q=60&q=75&s=208353c58e7d49492ba5103b35a53595)
参考URL
Unity - ShaderLab: ステンシル
https://docs.unity3d.com/jp/540/Manual/SL-Stencil.html
[Unity] UnityのShaderでステンシルバッファを試す
http://qiita.com/edo_m18/items/95a7f350d1164486e03b
シェーダーコード
実際に使用したシェーダーです。
Shader "Red" {
SubShader {
Tags { "RenderType"="Opaque" "Queue"="Geometry"}
Stencil {
Ref 2 // リファレンス値
Comp Always // 常にステンシルテストをパスさせます。
Pass Replace // リファレンス値をバッファに書き込みます。
}
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
struct appdata {
float4 vertex : POSITION;
};
struct v2f {
float4 pos : SV_POSITION;
};
v2f vert(appdata v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
return o;
}
half4 frag(v2f i) : SV_Target {
return half4(1,0,0,1);
}
ENDCG
}
}
}
Shader "Green" {
SubShader {
Tags { "RenderType"="Opaque" "Queue"="Geometry+1"}
Stencil {
Ref 0 // リファレンス値
Comp Equal // ピクセルのリファレンス値がバッファの値と等しい場合のみレンダリングします。
}
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
struct appdata {
float4 vertex : POSITION;
};
struct v2f {
float4 pos : SV_POSITION;
};
v2f vert(appdata v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
return o;
}
half4 frag(v2f i) : SV_Target {
return half4(0,1,0,1);
}
ENDCG
}
}
}