Unity でスポットライト的な表現をする

f:id:ryoheyc:20220301183929g:plain

どうも亀山です。Unity でステンシルバッファを使って冒頭の動画のスポットライトのような演出を実装したので、その方法について解説します。

ステンシルバッファ

Unity では 3D オブジェクトを描画するためにシェーダーという GPU 上で実行されるプログラムを利用します。ステンシルバッファはシェーダー上で利用できる、切り抜き表現を行うためのデータ領域です。

まずステンシルを使って次図のような平面を平面で切り抜いた表現を実装します。シーンには2つの Quad が配置してあり、それぞれ内側の切り抜かれる領域、外側の暗くなっている領域を担当します。

image.png (105.2 kB)

まず内側の Quad のシェーダを用意します。

内側の Quad のシェーダ

Shader "Custom/MarkerArea"
{
    Properties
    {
    }
    SubShader
    {
        Tags {
            "RenderType"="Opaque"
            "Queue"="Geometry-1"
        }

        Pass
        {
            ColorMask 0
            ZTest Always
            ZWrite Off

            Stencil
            {
                Ref 1
                Comp Always
                Pass Replace
            }

            CGPROGRAM

            #pragma target 3.0
            #pragma vertex vert
            #pragma fragment frag

            float4 vert (float4 vertex : POSITION) : SV_POSITION
            {
                return UnityObjectToClipPos(vertex);
            }
 
            fixed4 frag () : SV_Target
            {
                return half4(0, 0, 0, 0);
            }
            ENDCG
    }
    }
}

このシェーダの次の箇所でステンシルバッファを書き込んでいます。

Stencil
{
    Ref 1
    Comp Always
    Pass Replace
}

Quad の描画される画面上の位置に 1 を書き込んでいます。

Ref はステンシルに書き込む値、Comp はステンシルの値との比較関数、Pass は比較関数が真のときに行う操作を定義します。この場合、Comp Always, Pass Replace は常に値を書き込むことを意味し、書き込む値は1となります。

次に外側の Quad のシェーダを実装します。

外側の Quad のシェーダ

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

        Pass
        {
            Stencil
            {
                Ref 1
                Comp NotEqual 
                Pass IncrSat
            }

            Blend SrcAlpha OneMinusSrcAlpha

            CGPROGRAM

            #pragma target 3.0
            #pragma vertex vert
            #pragma fragment frag
 
            fixed4 _Color;

            float4 vert (float4 vertex : POSITION) : SV_POSITION
            {
                return UnityObjectToClipPos(vertex);
            }
 
            fixed4 frag () : SV_Target
            {
                return _Color;
            }
            ENDCG
    }
    }
}

このシェーダのステンシルの定義は次のようになります。

Stencil
{
    Ref 1
    Comp NotEqual
    Pass IncrSat
}

この定義は、ステンシルが1ではない部分のみに後続のシェーダーの描画処理を行うという意味になります。残りのコードは指定された色で塗りつぶすだけの描画処理となっています。Pass IncrSat はたぶん無くても大丈夫です。

これら2つのシェーダーを、内側の Quad と外側の Quad に設定します。ここで、内側の Quad のシェーダがステンシルバッファに先に書き込みする必要があるため、描画順序を指定する Queue をそれぞれ Transparent、Geometry-1 と指定しています。

アニメーション

まず外側の Quad、内側の Quad を同じ Scale に設定します。次に、Animator を用いて内側の Quad が画面サイズより大きいサイズから 1 に小さくなっていくようなアニメーションをつけます。これで絞り込まれていくような表現ができます。

f:id:ryoheyc:20220301185507p:plain

また、いきなり黒い枠が出るとおかしいので、外側の Quad の alpha 値もアニメーションさせてフェードインさせます。

f:id:ryoheyc:20220301185555p:plain

以上でスポットライト的な表現を実装することができました。形状は Quad に限らないので、円形や 3D オブジェクトでやると面白い演出が作れそうですね。