【UnityShader】ステンシルバッファでマスクをかける #23
前回の成果
溶けるようなShaderを制作しました
今回やること
ステンシルバッファについて理解しようと思います。
事前準備
Scene上にCubeとQuadを配置し、Quadが手前に来るようにします。
Sceneから見た図
Gameから見た図
ソースコード
マスクされるオブジェクト
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 |
ステンシルバッファ
これを使用すると、
【ゲーム】ステンシル:より引用
この画像のように特定の範囲のみ描画することができます。
もう少し詳しく言うと、ピクセルにモデルがレンダリングされるかの許可を判断するための基準値を設定することができます。
その基準値を元に、レンダリングを行うかどうかを決めることをステンシルテストと呼ばれます。
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バッファについてはこちらに記載してあります。
結果
少しわかりにくいですが、maskがかかっている部分のみ表示されるようになりました。
Sceneから見た図
Gameから見た図
ソースコードにコメントを付与
マスクされるオブジェクト
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"
だと思っていたのがよくなかった。
このサイト様を見ると解決することができた。
マスクする側は、先にステンシル値を書き込む必要がある
考えてみると、当然で
// 2000 "Queue" = "Geometry" // 3000 "Queue" = "Transparent"
なので、このように描画されなくなってしまう。
結果
これでマスクもオブジェクトも見えるようになります。
描画順をオブジェクトの方が後にしているのでマスクの色に合わせたりってのはまた別の話・・・。