[Unity] モデルをマスクするボックスを作るシェーダ


今作ろうと思っているもので、モデルを徐々に出現させたり、逆に徐々に消す、みたいないわゆるフェードイン・アウトを実装したいと思いました。

2Dであればその処理はとても簡単ですが、3Dではレンダリングが複雑で簡単には行きません。
モデルには当然、それを表現するシェーダがセットになっていますし、そのシェーダがいじれない場合なんかもあります。
(とはいえ、本当にリアルな映像を作るならシェーダをいじらずになにかをするのは不可能ですが)

ごく簡単な演出であれば、こちらで用意したシェーダでもそうした表現が可能です。

結論から言うと、深度テストの仕組みをうまく使うことで実現しました。
(もしかしたらステンシルバッファを使ってもできるかもしれませんが、ちょっと分かりませんでした;)
本当は半透明な効果も使ってフェードイン・アウトをしたいんですがスキル不足です・・。

ちなみに今回実装したやつを表示するとこんな感じになります↓
(使用しているモデルはUnityちゃん。シェーダは特にいじっていません)

イメージ図

そのシェーダを適用したボックスが重なっているところだけ、マスクされるようにUnityちゃんが消えているのが分かると思います。

動かした状態

Gyazo

仕組み

種明かしをしてしまうと、実はボックスだけではなくて地面のシェーダも少しだけいじっています。
要は地面をなるべく最初にレンダリングし、次にマスクのためのボックスをレンダリングさせます。
そしてそれらのレンダリングのあとにモデルをレンダリングすることで、今回の結果を得ている、というわけです。

地面のシェーダ

地面のシェーダは新規作成して出来たSurface Shaderにひとつタグを追加しただけです。

ground
Shader "Custom/Ground" {
    Properties {
        _MainTex ("Base (RGB)", 2D) = "white" {}
    }
    SubShader {
        Tags { "RenderType"="Opaque" "Queue"="Geometry-2" }
        LOD 200

        CGPROGRAM
        #pragma surface surf Lambert

        sampler2D _MainTex;

        struct Input {
            float2 uv_MainTex;
        };

        void surf (Input IN, inout SurfaceOutput o) {
            half4 c = tex2D (_MainTex, IN.uv_MainTex);
            o.Albedo = c.rgb;
            o.Alpha = c.a;
        }
        ENDCG
    } 
    FallBack "Diffuse"
}

具体的には、

added
"Queue"="Geometry-2"

だけですね。

このQueueは該当シェーダを指定されたモデルのレンダリング順を指定します。
ここには簡単な数式が書けるので、標準で用意されているGeometryの順番からマイナス2した順番でレンダリングしろ、という意味になります。

つまり、普通のモデルよりも若干先にレンダリング処理が行われる、というわけです。

ボックスのシェーダ

box
Shader "Stealth" {
    SubShader {
        Tags { "RenderType"="Opaque" "Queue"="Geometry-1"}
        Pass {

            ColorMask 0

            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 = mul(UNITY_MATRIX_MVP, v.vertex);
                return o;
            }
            half4 frag(v2f i) : COLOR {
                return half4(0, 0, 0, 0);
            }

            ENDCG
        }
    } 
}

問題のボックスのシェーダです。
大事なポイントは3つあります。

  • `"Queue"="Geometry-1"を指定する
  • 頂点シェーダで位置を返す。
  • ColorMask 0を指定する

Queueについては地面シェーダでも書きましたが、地面のあとにレンダリングされるような数値を指定します。
そしてマスクさせるだけなので色情報は必要なく、位置情報だけを適切に返すように頂点シェーダを記述します。
そして最後は、色の出力がいらないのでColorMask 0を指定してすべての色の出力をオフにします。

つまり、このシェーダを適用したオブジェクトは実質的に深度値のみを更新する、という意味になります。
(もしかしたらもっといい方法があるかも。知っている人がいたら教えてください;)

Queue

キューについて、Unityのドキュメントを引用すると以下のように説明されています。

For special uses in-between queues can be used. Internally each queue is represented by integer index; Background is 1000, Geometry is 2000, AlphaTest is 2450, Transparent is 3000 and Overlay is 4000. If a shader uses a queue like this:

つまり、それぞれのキーワードに数値が割り当てられていて、その順番にレンダリングが行われる、ということです。

キーワード 数値
Background 1000
Geometry 2000
AlphaTest 2450
Transparent 3000
Overlay 4000

ちなみにSkyboxはおよそ2500程度のようです。("Queue"="Transparent-500"でレンダリング結果が変わった)

ちなみに今回の手法を使うと、Skyboxがうまくレンダリングされません。
Skyboxを使う場合は全体的にシェーダを調整する必要があります。

結論

さて、これで準備が整いました。
あとは準備したシェーダを使用したマテリアルを作り、それをそれぞれ適用してやれば冒頭の画像のようなレンダリング結果を得ることができます。

ボックスに使っているマテリアルを他の形状をしたモデルに適用すれば、マスクする形もある程度制御することができます。

シェーダが書けると色々な演出が実現できるので本当に面白いですね。
まだまだ勉強不足なので、もっとがっつりやりたいところです。