【Unity】ステンシルバッファの復習

# はじめに
ステンシルバッファへの理解を深めるため、ステンシルバッファによってオブジェクトがどのようにして描画されていくのかを整理してみました。

#環境
Windows 10
Unity 5.6.1f1

# リファレンス値とバッファ値
Unityのステンシルテストではリファレンス値とバッファ値を使って3Dオブジェクトを描画させるか否かを決定しているそうです。

画面の各ピクセルはバッファ値を持っており、これから描画させようとしている3Dオブジェクトはリファレンス値を持っている。
この二つを比較することで描画させるか否かを判定しているそうです。

参考
https://docs.unity3d.com/jp/540/Manual/SL-Stencil.html

# ステンシルバッファの挙動を見てみる
ステンシルバッファの挙動を見るため、シーンに赤色と緑色の板を以下のように置くことを考えます。

<img src = "https://qiita-image-store.s3.amazonaws.com/0/44288/ce6810c2-c980-d886-cd89-7deec5010d8f.png">

それぞれの板にはステンシルが定義されたシェーダーがアタッチされています。

# 赤の板を手前に出す
シェーダーのステンシルを以下のように設定すると、緑の板の手前に赤の板が表示されるようになります。

```:赤い板のシェーダーのステンシル
Stencil {
    Ref 2 // リファレンス値
    Comp Always  // 常にステンシルテストをパスさせます。
    Pass Replace	// リファレンス値をバッファに書き込みます。
}
```

```:緑の板のシェーダーのステンシル
Stencil {
    Ref 1 // リファレンス値
    Comp Greater // ピクセルのリファレンス値がバッファの値より大きい場合のみレンダリングします。
}
```

<img src = "https://qiita-image-store.s3.amazonaws.com/0/44288/5e2fa1b2-66a0-70b8-b915-abd48f953bf2.png">

# なぜこうなるのか整理してみる
なぜこのような描画結果になるのか私には理解できませんでした。
そこで、どのようにして板が描画されていくのかを整理してみることにしました。

## STEP 1. 赤の板の描画
赤の板のステンシルでは **Ref 2** が指定されています。
これは**リファレンスの値を2にする**、という意味の命令らしいです。
そして**Pass Replace**によってリファレンスの値が画面のバッファへと書きこまれます。
このときの画面の状態は以下のようになります。
<img height = 200 src = "https://qiita-image-store.s3.amazonaws.com/0/44288/55fd66dd-385c-edaa-b937-8655c598cdee.png">

## STEP 2. 緑の板の描画
緑の板のステンシルには **Ref 1** が指定されています。
つまり、緑の板の描画時のリファレンスの値が1になります。
<img height = 200 src = "https://qiita-image-store.s3.amazonaws.com/0/44288/9d86d04c-ac70-bca0-cdc9-bd66ff28be08.png">

<br>
ここで、緑の板のステンシルには**Comp Greater**と記述されています。
これは 
「**ピクセルのリファレンス値がバッファの値より大きい場合のみレンダリングする**」
という意味の命令らしいです。

これを可視化すると以下のようになります。
<img height = 200 src = "https://qiita-image-store.s3.amazonaws.com/0/44288/fb1390a3-345f-a03d-156c-7bc7f2436dd8.png">

<br>
結果として赤い板が描画されていない部分で緑色の板が描画されることになります。
<img height = 200 src = "https://qiita-image-store.s3.amazonaws.com/0/44288/49bffa30-8b5f-9e84-dc14-1f0365ce9fd5.png">
最初から何も描画されていない領域のバッファーの値は0になっているみたいです。


最終的な描画結果は以下のようになります。
<img height = 200 src = "https://qiita-image-store.s3.amazonaws.com/0/44288/2db6e318-c985-c0de-a885-8d6628123fd6.png">

# 緑の板をくり抜く
緑の板のシェーダーのステンシルを以下のように設定すると緑の板が赤の板でくり抜かれます。

```
  Stencil {
      Ref 2
      Comp Equal // ピクセルのリファレンス値がバッファの値と等しい場合のみレンダリングします。
  }
```

<img height = 200 src = "https://qiita-image-store.s3.amazonaws.com/0/44288/7394e7b7-30d3-4650-204a-7c54fd929381.png">

## なぜこうなるのか整理してみる
緑の板のステンシルでは**Comp Equal** が指定されているため、緑の板が描画されるのは**バッファ値 = 2** となっている領域に限定されます。
赤い板が描画されている領域はバッファ値 = 2 ですが、それ以外の領域のバッファ値は2ではありません。

<img height = 200 src = "https://qiita-image-store.s3.amazonaws.com/0/44288/b620595d-bbe2-03be-328c-902150604ecc.png">

つまり、赤い板の表示領域だけで緑の板が描画されるので以下のような描画結果になります。

<img src = "https://qiita-image-store.s3.amazonaws.com/0/44288/7394e7b7-30d3-4650-204a-7c54fd929381.png">

# 緑の板のリファレンス値を変える
緑の板のシェーダーのステンシルのリファレンス値を**0**にしてやると、赤の板以外の領域で緑の板が描画されるようになります。

```
  Stencil {
      Ref 0
      Comp Equal // ピクセルのリファレンス値がバッファの値と等しい場合のみレンダリングします。
  }
```

<img height = 200 src = "https://qiita-image-store.s3.amazonaws.com/0/44288/d8e9530b-58d5-28eb-ac71-9205bb5a733f.png">

<br>
これは、何も描画されていない領域のバッファ値が0になっているのが理由だと考えられます。
<img height = 200 src = "https://qiita-image-store.s3.amazonaws.com/0/44288/5c717e7b-359d-baae-eecc-74b59a3ddf55.png">

# 参考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
        }
    } 
}
```