知識0からのUnityShader勉強

知識0からのUnityShader勉強

UnityのShaderをメインとして、0から学んでいくブログです。

【UnityShader】ステンシルバッファでマスクをかける #23

前回の成果

溶けるようなShaderを制作しました

soramamenatan.hatenablog.com

今回やること

ステンシルバッファについて理解しようと思います。

www.shaderslab.com

事前準備

Scene上にCubeとQuadを配置し、Quadが手前に来るようにします。

Sceneから見た図

f:id:soramamenatan:20190920173652p:plain

Gameから見た図

f:id:soramamenatan:20190920173648p:plain

ソースコード

マスクされるオブジェクト

Shader "Custom/stencilObject" {
    Properties {
        _MainTex ("Base(RGB)", 2D) = "white" {}
    }

    SubShader {
        Tags { "RenderType" = "Opaque" }
        Stencil {
            Ref 1
            Comp equal
            Pass keep
        }

        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"
}

マスクするオブジェクト

Shader "Custom/stencilMask" {
    Properties {
        _MainTex ("Base(RGB)", 2D) = "white" {}
    }
    SubShader {
        Tags { "RenderType" = "Transparent" }
        Stencil {
            Ref 1
            Comp always
            Pass replace
        }

        CGPROGRAM
        #pragma surface surf Lambert alpha

        struct Input {
            float2 uv_MainTex;
        };

        void surf (Input IN, inout SurfaceOutput o) {
            o.Albedo = fixed3(0, 0, 0);
            o.Alpha = 0.5f;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

PlaneとQuadの違い

\ 平面方向 メッシュ数 用途
Plane XZ平面 121個の頂点と200個の三角形 床や壁のような平面
Quad XY平面 4個の頂点と2個の三角形 画像や動画のディスプレイ、シンプルなGUI

tsuwabuki.hatenablog.com

ステンシルバッファ

これを使用すると、

f:id:soramamenatan:20190920181441p:plain

【ゲーム】ステンシル:より引用

この画像のように特定の範囲のみ描画することができます。

もう少し詳しく言うと、ピクセルにモデルがレンダリングされるかの許可を判断するための基準値を設定することができます。
その基準値を元に、レンダリングを行うかどうかを決めることをステンシルテストと呼ばれます。

Stencil

ステンシルバッファを使用するための宣言です。

シンタックスは以下のようになっています。

Stencil {
    Ref
    ReadMask
    WriteMask
    Comp
    Pass
    Fail
    ZFail
}

Ref

ここで指定した、0~255の整数の値がバッファに書き込まれます。

ReadMask

0~255の整数の8ビットマスクで、Refで指定した値とandでビット演算をするものです。

// 例
// Ref(255)
11111111
// ReadMask(170)
10101010
// Result(170)
10101010

デフォルト値は255です。

WriteMask

0~255の整数の8ビットマスクで、バッファに書き込む時にマスクするものです。
ReadMaskとの違いは、

  • ReadMaskは比較する前にマスク
  • WriteMaskはバッファに書き込む前にマスク

です。

デフォルト値は255です。

Comp

これは現在のバッファの内容と基準値の比較にしようされるものです。

比較関数名 レンダリング条件
Greater refで指定した値が現在のバッファ値より大きいピクセルのみレンダリング
GEqual refで指定した値が現在のバッファ値と等しい、または大きいピクセルのみレンダリング
Less refで指定した値が現在のバッファ値より小さいピクセルのみレンダリング
LEqual refで指定した値が現在のバッファ値と等しい、または小さいピクセルのみレンダリング
Equal refで指定した値が現在のバッファ値と等しいピクセルのみレンダリング
NotEqual refで指定した値が現在のバッファ値と等しくないピクセルのみレンダリング
Always 常にレンダリング
Never 常にレンダリングしない

デフォルト値はAlwaysです。

Pass

ステンシルテストが成功した場合(レンダリングに成功した)のバッファの扱いです。

Pass名 用途
Keep バッファに現在書き込まれている値を保持する
Zero バッファに0を書き込む
Replace バッファに参照値(Ref)を書き込む
IncrSat バッファの現在値をインクリメント(+1)させる。値がすでに255の場合は変化させない
DecrSat バッファの現在値をデクリメント(-1)させる。値がすでに0の場合は変化させない
Invert 全てのビットを無効にする
IncrWrap バッファの現在値をインクリメントさせる。値がすでに255の場合は0にする
DecrWrap バッファの現在地をデクリメントさせる。値がすでに0の場合は255にする

デフォルト値はKeepです。

Fail

ステンシルテストが失敗した場合のバッファの扱いになります。
指定するのはPassと同じで、デフォルト値はKeepです。

ZFail

ステンシルテストが成功し、デプステストが失敗した場合のバッファの扱いになります。
指定するのはPassと同じで、デフォルト値はKeepです。

デプステスト

デプステストとは、ピクセルの深度とZバッファの深度を比較して、ピクセルを書き込むかを判定するものです。

Zバッファについてはこちらに記載してあります。

soramamenatan.hatenablog.com

結果

少しわかりにくいですが、maskがかかっている部分のみ表示されるようになりました。

Sceneから見た図

f:id:soramamenatan:20190924165726p:plain

Gameから見た図

f:id:soramamenatan:20190924165240p:plain

ソースコードにコメントを付与

マスクされるオブジェクト

Shader "Custom/stencilObject" {
    // プロパティ
    Properties {
        // テクスチャ
        _MainTex ("Base(RGB)", 2D) = "white" {}
    }

    // Shaderの中身を記述
    SubShader {
        // 一般的なShaderを使用
        Tags { "RenderType" = "Opaque" }
        // ステンシル
        Stencil {
            // バッファに書き込む値
            Ref 1
            // バッファが等しいか
            Comp equal
            // バッファに保持
            Pass keep
        }

        // cg言語記述
        CGPROGRAM
        // 拡散
        #pragma surface surf Lambert

        // テクスチャ
        sampler2D _MainTex;

        // Input構造体
        struct Input {
            // テクスチャ
            float2 uv_MainTex;
        };

        // surf関数
        void surf (Input IN, inout SurfaceOutput o) {
            // テクスチャのピクセルの色を返す
            half4 c = tex2D(_MainTex, IN.uv_MainTex);
            o.Albedo = c.rgb;
            o.Alpha = c.a;
        }
        // Shaderの記述終了
        ENDCG
    }
    // SubShaderが失敗した時に呼ばれる
    FallBack "Diffuse"
}

マスクするオブジェクト

Shader "Custom/stencilMask" {
    // プロパティ
    Properties {
        // テクスチャ
        _MainTex ("Base(RGB)", 2D) = "white" {}
    }

    // Shaderの中身を記述
    SubShader {
        // 不透明なオブジェクト
        Tags { "RenderType" = "Transparent" }
        // ステンシル
        Stencil {
            // バッファに書き込む値
            Ref 1
            // 常に
            Comp always
            // バッファに書き込む
            Pass replace
        }

        // cg言語記述
        CGPROGRAM
        // 拡散、透過
        #pragma surface surf Lambert alpha

        // Input構造体
        struct Input {
            // テクスチャ
            float2 uv_MainTex;
        };

        // surf関数
        void surf (Input IN, inout SurfaceOutput o) {
            o.Albedo = fixed3(0, 0, 0);
            o.Alpha = 0.5f;
        }
        // Shaderの記述終了
        ENDCG
    }
    // SubShaderが失敗した時に呼ばれる
    FallBack "Diffuse"
}

今回は以上となります。
ご視聴ありがとうございました!

マスクがあまりにもわかりにくいのでマスクを見えるようにした。

ソースコード

マスクされるオブジェクト

// 一般的なShaderを使用
// "Queue" = "Transparent+1"を追加
Tags { "RenderType" = "Opaque" "Queue" = "Transparent+1" }

マスクするオブジェクト

// 不透明なオブジェクト
// "Queue" = "Transparent"を追加
Tags { "RenderType" = "Transparent" "Queue" = "Transparent" }

ソースコードとしては、各TagsにQueueを追加しただけだ。

詰まった点

原因としては、マスクされるオブジェクトは不透明なオブジェクトなので

"Queue" = "Geometry"

だと思っていたのがよくなかった。

www.shibuya24.info

このサイト様を見ると解決することができた。

マスクする側は、先にステンシル値を書き込む必要がある

考えてみると、当然で

// 2000
"Queue" = "Geometry"
// 3000
"Queue" = "Transparent"

なので、このように描画されなくなってしまう。

f:id:soramamenatan:20190924184334p:plain

結果

f:id:soramamenatan:20190924184420p:plain

これでマスクもオブジェクトも見えるようになります。
描画順をオブジェクトの方が後にしているのでマスクの色に合わせたりってのはまた別の話・・・。