【Unity】【シェーダ】オブジェクトの遮蔽されている部分をシルエット描画する(ステンシルバッファ)

f:id:halya_11:20180508225902g:plain

あるオブジェクトの、ほかのオブジェクトと重なって隠れてしまっている部分を違う色で描画する方法です。
障害物に遮蔽されているときにシルエットを描画したい場合などに使えます。

考え方

  1. ステンシルバッファに適当な値を書き込みつつ、障害物のオブジェクトを描画する
  2. ステンシルバッファが1の値じゃない部分について、シルエット描画したいオブジェクトを描画する
  3. ステンシルバッファが1の値である部分について、シルエット描画したいオブジェクトをシルエット色で描画する

f:id:halya_11:20180508225132p:plain

これだけです。

実装

まず障害物用のシェーダです。

Shader "Obstacle" {
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
    }
    SubShader
    {
        Tags { "RenderType"="Transparent" "Queue"="Transparent" }

        Pass
        {
            Stencil{
                Ref 1
                Comp always
                Pass replace
            }

            Tags { "LightMode"="ForwardBase" }

            ZWrite Off

            CGPROGRAM
           #pragma vertex vert
           #pragma fragment frag
            
           #include "UnityCG.cginc"

            struct appdata
            {
                half4 vertex : POSITION;
                half3 normal : NORMAL;
            };

            struct v2f
            {
                half4 pos : SV_POSITION;
                half3 normal: TEXCOORD1;
            };

            half4 _Color;
            half4 _LightColor0;
            
            v2f vert (appdata v)
            {
                v2f o = (v2f)0;

                o.pos = UnityObjectToClipPos(v.vertex);
                o.normal = UnityObjectToWorldNormal(v.normal);

                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                half3 diff = max(0, dot(i.normal, _WorldSpaceLightPos0.xyz)) * _LightColor0;

                fixed4 col;
                col.rgb = _Color * diff;
                return col;
            }
            ENDCG
        }
    }
}

ステンシルバッファに1を入れているのがポイントです。
色はDiffuseだけ。

次にシルエット描画したいオブジェクトのシェーダです。

Shader "Diffuse" {
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
        _CoveredColor ("Covered Color", Color) = (0,0,0,1)
    }
    SubShader
    {
        Tags { "RenderType"="Transparent" "Queue"="Transparent+1" }
        
        // 隠れていない部分を描画するパス
        Pass
        {
            Stencil{
                Ref 1
                Comp NotEqual
            }
            
            Tags { "LightMode"="ForwardBase" }

            ZWrite Off

            CGPROGRAM
           #pragma vertex vert
           #pragma fragment frag
            
           #include "UnityCG.cginc"

            struct appdata
            {
                half4 vertex : POSITION;
                half3 normal : NORMAL;
            };

            struct v2f
            {
                half4 pos : SV_POSITION;
                half3 normal: TEXCOORD1;
            };

            half4 _Color;
            half4 _LightColor0;
            
            v2f vert (appdata v)
            {
                v2f o = (v2f)0;

                o.pos = UnityObjectToClipPos(v.vertex);
                o.normal = UnityObjectToWorldNormal(v.normal);

                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                half3 diff = max(0, dot(i.normal, _WorldSpaceLightPos0.xyz)) * _LightColor0;

                fixed4 col;
                col.rgb = _Color * diff;
                return col;
            }
            ENDCG
        }
        
        // 隠れている部分を描画するパス
        Pass
        {
            Stencil{
                Ref 1
                Comp Equal
            }

            ZWrite Off

            CGPROGRAM
           #pragma vertex vert
           #pragma fragment frag
            
           #include "UnityCG.cginc"

            struct appdata
            {
                half4 vertex : POSITION;
            };

            struct v2f
            {
                half4 pos : SV_POSITION;
            };

            half4 _CoveredColor;
            
            v2f vert (appdata v)
            {
                v2f o = (v2f)0;
                o.pos = UnityObjectToClipPos(v.vertex);
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                return _CoveredColor;
            }
            ENDCG
        }
    }
}

まず、今回の考え方だと障害物よりも後に描画しなければ成立しないため、
QueueをTransparent+1にしています。

また2つのパスで描画しています。
1つ目のパスではステンシルが1でない部分にDiffuseライティングでオブジェクトを描画しています。
2つ目のパスではステンシルが1の部分に単色で描画しています。

これら二つのシェーダを持ったマテリアルをそれぞれ作り、異なるオブジェクトにセットすれば実装完了です。

結果

こんな感じでシルエット描画されます。

f:id:halya_11:20180508225902g:plain

関連

light11.hatenadiary.com

light11.hatenadiary.com