Unityにおけるステンシルバッファの使い方についてまとめてみます。
ステンシルバッファとは?
ステンシルバッファは、DIYなどで使うステンシルシートのように、特定の領域にのみ描画を行いたいときなどに使います。
アウトラインやシルエットの表示など、他にもいろんな用途はありますが、
この記事では説明をわかりやすくするために上記のイメージで進めます。
実装のイメージ
次節からステンシルバッファを使った実装例を紹介しますが、
実装は下記のように3段階で進めます。
- 描画可能な領域をマークする
- テクスチャなどを描画して表示する
- 1の部分のみ描画するようにする
1. 描画可能な領域をマークする
まずは描画可能な領域を決めます。
描画したい部分に3Dオブジェクトを適当に並べます。
今回はQuadをグリッド状に並べています。
現状ではStandardシェーダで描画が行われてしまっていますが、ここでは必要はないのでシェーダを変えて透明にします。
Shader "WriteStencil" { SubShader { Tags { "RenderType"="Transparent" "Queue"="Transparent" } ZWrite Off Blend SrcAlpha OneMinusSrcAlpha Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; }; struct v2f { float4 vertex : SV_POSITION; }; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); return o; } fixed4 frag (v2f i) : SV_Target { // 透明にする return fixed4(0, 0, 0, 0); } ENDCG } } }
透明で描画されるようになりました(見えなくなりました)。
次に、このオブジェクトが透明で描画されているピクセルを「描画可能な領域であるとマーク」します。
ここで出てくるのがステンシルバッファです。
ステンシルバッファの実体は1ピクセルに1つ用意されているメモリ領域であり、0〜255の値を書き込めます。
この値を後ほど参照して、描画可能な領域かどうかの判定に使います。
ステンシルバッファに書き込むにはシェーダで次のように実装します。
Shader "WriteStencil" { SubShader { Tags { "RenderType"="Transparent" "Queue"="Transparent" } ZWrite Off Blend SrcAlpha OneMinusSrcAlpha Pass { // ステンシルバッファの設定 Stencil{ // ステンシルの番号 Ref 2 // Always: このシェーダでレンダリングされたピクセルのステンシルバッファを「対象」とするという意味 Comp Always // Replace: 「対象」としたステンシルバッファにRefの値を書き込む、という意味 Pass Replace } CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; }; struct v2f { float4 vertex : SV_POSITION; }; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); return o; } fixed4 frag (v2f i) : SV_Target { return fixed4(0, 0, 0, 0); } ENDCG } } }
説明はコメントの通りです。
今回は描画可能な領域の目印として、ステンシルバッファに2を代入しています。
2. テクスチャなどを描画して表示する
次に、1の領域に描画するためのオブジェクトを適当に配置します。
今回はQuadを大きく配置して虹色のテクスチャを貼りました。
シェーダはこちらです。
半透明パスでテクスチャを貼ってるだけです。
Shader "ReadStencil" { Properties { _MainTex ("Texture", 2D) = "white" {} } SubShader { Tags { "RenderType"="Transparent" "Queue"="Transparent" } ZWrite Off Blend SrcAlpha OneMinusSrcAlpha Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" sampler2D _MainTex; float4 _MainTex_ST; struct appdata { float4 vertex : POSITION; float2 texcoord : TEXCOORD0; }; struct v2f { float4 vertex : SV_POSITION; float2 uv : TEXCOORD0; }; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.texcoord, _MainTex); return o; } fixed4 frag (v2f i) : SV_Target { return tex2D(_MainTex, i.uv); } ENDCG } } }
このとき、レンダリングの順序に注意する必要があります。
このオブジェクトは前節で設定したステンシルバッファを見るため、1のオブジェクトがレンダリングされた後にレンダリングする必要があります。
今回は半透明パスで描画するため、単純によりカメラに近い位置に配置すれば後にレンダリングされます。
3. 1の部分のみ描画するようにする
最後に、2で配置したのオブジェクトのシェーダを変更して、1でステンシルバッファに書き込みを行なったピクセルにだけ描画を行うようにします。
シェーダは次のようにします。
Shader "ReadStencil" { Properties { _MainTex ("Texture", 2D) = "white" {} } SubShader { Tags { "RenderType"="Transparent" "Queue"="Transparent" } ZWrite Off Blend SrcAlpha OneMinusSrcAlpha Pass { // ステンシルバッファの設定 Stencil{ // ステンシルの番号 Ref 2 // Equal: ステンシルバッファの値がRefと同じであれば描画を行う Comp Equal } CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" sampler2D _MainTex; float4 _MainTex_ST; struct appdata { float4 vertex : POSITION; float2 texcoord : TEXCOORD0; }; struct v2f { float4 vertex : SV_POSITION; float2 uv : TEXCOORD0; }; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.texcoord, _MainTex); return o; } fixed4 frag (v2f i) : SV_Target { return tex2D(_MainTex, i.uv); } ENDCG } } }
コメントの部分がステンシルの処理です。
現在のバッファとRefで指定した値を比べて、同じであれば描画を行い、それ以外は描画を行いません。
レンダリング結果は次のようになります。
ステンシルバッファに値が入っている部分のみ描画されることが確認できました。
Comparison Functionについて
上記の例では描画する条件をComp Equalとしてステンシルバッファの値がRefと同じであると設定しました。
このCompは他にもGreater / Less / NotEqualなど色々な条件に変更できます。
Compの条件に関してはよく使うので覚えておくといいかもしれません。
詳細に関してはマニュアルに書かれているので、そちらを参照してください。