今日まなび008:ステンシルバッファを使って画面内の一部にだけポストプロセスをかける2(達成!!)

「今日まなび」は最低1日1h、ゲーム作るために学びたいことなんでもいいから学んでいくコーナー。
完全自分用でまとめることは考えなくてOK.1記事1h。(1hでどんだけ学べるかのスピード感もみていきたいので)
1hのうちに次回学ぶことも決定しておくこと

環境:unity2021.3.40f


今日の学び

今の課題を整理

ここのところステンシルバッファを少し学んできたわけだが、ここでやりたいことを整理します。
キャラだけそのままの色、キャラ以外の部分にだけモノクロをかけたい!がやりたいことです。

今時点で学んだことを自作ゲームに反映してみたら、こういう四角の形でマスクされてしまった…
キャラの画像はQuadに張って描画している。こういうことではなく、透過部分も考慮してモノクロする部分とそうでない部分を分けたいのだ。

これはすでにUIのMaskで行われているように見受けるのでみてみたところ、
描画したくない部分をclipなどで描画しないようにしているのではなく、透明なまま描画していることが原因であることがわかった。

clip関数はマスクによく使われるもので、引数が0以下であればフラグメントシェーダをその時点でスキップというか描画しないようにするものである。

clip(col.a - 0.001);

意図した表示にできた!!

Shader "Test/test20240902write"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _Tint("Tint",Color) = (1,1,1,1)
    }
    SubShader
    {
        Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
        LOD 100

        Pass {

            Stencil
            {
                Ref 1
                Comp Always
                Pass Replace
            }

            Tags { "LightMode" = "ForwardBase" }
            Blend SrcAlpha OneMinusSrcAlpha

            CGPROGRAM
           #pragma vertex vert
           #pragma fragment frag
           #pragma target 2.0

            #include "UnityCG.cginc"

            sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed4 _Tint;

            struct appdata_t {
                float4 vertex : POSITION;
                float2 texcoord : TEXCOORD0;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            struct v2f {
                float4 vertex : SV_POSITION;
                float2 texcoord : TEXCOORD0;
                float4 pos : TEXCOORD1;

                UNITY_FOG_COORDS(1)
                UNITY_VERTEX_OUTPUT_STEREO
            };

            v2f vert (appdata_t v)
            {
                v2f o;
                UNITY_SETUP_INSTANCE_ID(v);
                UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);
                return o;
            }

            fixed4 frag (v2f_img i) : COLOR
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                clip(col.a - 0.001); //透過部分は描画しない ステンシルバッファにも書き込みたくないので透過ではなく描画しないようにする

                return col * _Tint;
            }
            ENDCG
        }
    }
}

ここまでわかったステンシルバッファ回りのことまとめ

・ステンシルバッファは画素単位で設定できる数値。
・この数値に値を書き込み、別のShaderでその値を参照、比較することでフラグメントシェーダの処理を分岐できる。
・ステンシルバッファへの書き込み+参照はStencilタグを使い、これはPassタグ内にもかけるし、SubShaderタグ単位でもかける
・ステンシルバッファへの書き込みはStencilタグ内でreplaceを指定することで書き込みが行われる。Compは書き込む条件を指定できる。

//このshaderパスのフラグメントシェーダで描画した画素のステンシルバッファに2を書き込む例
Stencil {
        Ref 2
        Comp always  //条件なし
        Pass replace  //ステンシルバッファを書き換える
}

・ステンシルバッファへの書き込みは画素単位に行われ、フラグメントシェーダで描画した画素が書き込まれる対象となる。
 →つまりフラグメントシェーダでclip関数などを使い描画しない画素と判定された画素はステンシルバッファへの書き込みも行われない。
 →透明(アルファ値=0)で描画しているのと描画しないことは見た目は同じに見えるが、ステンシルバッファへの書き込む有無の違いもあるので注意。

・ステンシルバッファの参照および比較はStencilタグ内のCompで行うことができる。

//このshaderパスのフラグメントシェーダは条件に一致した場合のみ行う
Stencil {
        Ref 2
        Comp Equal  //Refで指定した値とステンシルバッファが同じという条件
}

・Pass単位にステンシルバッファを参照比較できるため、ステンシルバッファの値によってPass単位に分岐できる。
 →1つのPass内でステンシルバッファの値をif文のように比較はできないため、複数Passとして定義する必要がある。