おもちゃラボ

Unityで遊びを作ってます

【Unityシェーダ入門】ステンシルバッファを使って隠れた部分を描く

プレイヤがゲームステージに配置されている障害物などの後ろ側に回り込むと、見えなくて操作しづらくなってしまいます。そこで、障害物によって隠れた部分は影だけ描いたりします。この記事では、Unityで隠れた部分を影で描画する方法を紹介します。

f:id:nn_hokuson:20170502184709p:plain:w550

今回の記事は次のようになります。

ステンシルバッファの原理

ステンシルバッファといっても、特殊なものではなく、一般的な画像と同じと考えて問題ありません。画像と違うのは、ステンシルバッファは画面に表示するためのものではなく、描画する際に使用する「裏方の値」ということです。

では、どのようにしてスプライトの裏側の画像を描画するかを説明します。

まずは画面上にブロックを描くと同時に、ステンシルバッファには「1」を書き込みます。これによりブロックの位置のステンシルバッファは「1」になります。

f:id:nn_hokuson:20170502181425p:plain:w550

続いて、ユニティちゃんを描画する際には、ステンシルバッファを参照します。描画しようとしている位置のステンシルバッファの値が「1」だった場合は、そのピクセルを黒く塗りつぶし、ステンシルバッファの値が「0」だった場合は、ユニティちゃんのテクスチャを描画します。

f:id:nn_hokuson:20170502182031p:plain:w550

実際にはステンシルバッファの値をif文で分けることは出来ないので、Stencilプロパティを利用して2回描画します。詳しくは下のプログラムを参考にしてみて下さい。

ステンシルバッファ用のシェーダを作る

ここでは、ブロック用のシェーダとユニティちゃん用のシェーダを作ります。プロジェクトビューで右クリックし、「Create」→「Shader」→「Standard Surface Shader」を選択、「Unitychan」という名前で保存します。同様に「Block」シェーダも作成しましょう。

f:id:nn_hokuson:20170502182746p:plain:w150

作成した「Unitychan」シェーダを選択した状態で、右クリック→「Create」→「Material」でシェーダに対応するマテリアル(Custom_Unitychan)を作ります。同様に「Custom_Block」も作りましょう。

f:id:nn_hokuson:20170502182757p:plain:w150

それぞれのマテリアルをユニティちゃんとブロックにアタッチして、それぞれのテクスチャをセットして下さい。

ブロックのシェーダを作る

では、まずBlockシェーダから見ていきましょう。

Shader "Unlit/Block"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            Stencil
            {
                Ref 1
                Comp Always
                Pass Replace
            }

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

            sampler2D _MainTex;

            fixed4 frag (v2f_img i) : COLOR
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                return col;
            }
            ENDCG
        }
    }
}

Blockシェーダの中では、ブロックを描画する位置のステンシルバッファを「1」で塗りつぶすように設定しています。ステンシルバッファの操作はStencilのプロパティを使用します。Stencilのプロパティの宣言は次のようになります。
 

Ref ステンシルに書き込む値
Comp 使用する比較関数
Pass 比較関数が真のときにの操作

 
ここでは、「Ref 1」としてステンシルバッファを「1」で塗りつぶすことを宣言しています。また、「Comp Always」「Pass Replace」としてブロックを描画する位置は常にステンシルバッファを強制的に「1」にすることを指定しています。

ブロックを描画し終わった時点でステンシルバッファは次のようになります。

f:id:nn_hokuson:20170502183109p:plain:w280

ユニティちゃんのシェーダを作る

続いて、ユニティちゃんのシェーダを作ります。

Shader "Unlit/Unitychan"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Transparent"
                "Queue" = "Transparent"
                "IgnoreProjector"="True" }
        LOD 100
        Blend SrcAlpha OneMinusSrcAlpha 
        ZWrite Off
        Cull off

        Pass
        {
            Stencil
            {
                Ref 1
                Comp Equal
            }

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

            sampler2D _MainTex;

            fixed4 frag (v2f_img i) : SV_Target
            {
                float alpha = tex2D(_MainTex, i.uv).a;
                fixed4 col = fixed4(0,0,0,alpha);
                return col;
            }
            ENDCG
        }

        Pass
        {
            Stencil
            {
                Ref 0
                Comp Equal
            }

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

            sampler2D _MainTex;

            fixed4 frag (v2f_img i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                return col;
            }
            ENDCG
        }
    }
}

ユニティちゃんシェーダの方はSubShaderの中に2つのPassがあります。このPassブロック一つが「塗り」の1回に相当します。つまりPassが2つあるので、一度塗って、乾いてから更に色を乗せる二度塗りになります。

上にも書いたように、ステンシルバッファの値を元にif文で分けることは出来ません。次のように「影」と「テクスチャ」の描画を分けて2度塗りすることでオブジェクトに隠れた部分も描画します。

  1. 影の部分を描画する
  2. ユニティちゃんのテクスチャ部分を描画する

まずは1Pass目では「Ref 1」「Comp Equal」としてステンシルバッファが「1」の部分だけを描画します。ステンシルバッファが「1」の部分とはブロックを描画した部分でしたね。

f:id:nn_hokuson:20170502183818p:plain:w550

描画はフラグメントシェーダの中で行っています。1Pass目のフラグメントシェーダでは、テクスチャの透明度を参照して黒く塗りつぶしています。

続いて2Pass目では「Ref 0」「 Comp Equal」としてステンシルバッファが「0」の部分だけを描画します。ステンシルバッファが「1」の部分とはブロック以外の部分です。

f:id:nn_hokuson:20170502184134p:plain:w550

フラグメントシェーダの中では、単純にユニティちゃんのテクスチャを描画しているだけです。

このように、ステンシルバッファを使って2度塗りをすることで、オブジェクトの後ろに回り込んだときは影を描画する「背景透過シェーダ」を作ることが出来ました。

f:id:nn_hokuson:20170502184545g:plain