そろそろShaderをやるパート70 オクルージョン用のShaderを作成する


そろそろShaderをやります

そろそろShaderをやります。そろそろShaderをやりたいからです。
パート100までダラダラ頑張ります。10年かかってもいいのでやります。
100記事分くらい学べば私レベルの初心者でもまあまあ理解できるかなと思っています。

という感じでやってます。

※初心者がメモレベルで記録するので
 技術記事としてはお力になれないかもしれません。

下準備

下記参考
そろそろShaderをやるパート1 Unite 2017の動画を見る(基礎知識~フラグメントシェーダーで色を変える)

オクルージョンとは

オクルージョンとは一言で説明すると"現実とバーチャルを交差させる"機能です。
ARにおいて、より現実感を高める表現として活用されています。
以下の動画のように、"現実空間の人間や物体"の後ろに仮想物体を出現させた状態を指します。

https://youtu.be/FCdxWkEj-vQ

【参考リンク】:現実とバーチャルを交差させる新機能「ARオクルージョン」実装

デモ

オクルージョン用ShaderをCubeに適用したデモです。
わかりやすいようにアウトラインを描画しています。
2つあるのはそれぞれ違うやり方を用いているからです。

Shaderサンプル

Shader "Custom/ColorMaskOcclusion"
{
    SubShader
    {
        Tags {"Queue"="geometry-1"}
        ColorMask 0
        Pass {}
    }
}

こちらは非常にシンプルです。
ColorMaskで指定チャンネルを0にし、以下の条件を満たすオブジェクトを描画しないようにしています。
・Ztest LEaualの結果が不合格となるオブジェクト
・RenderQueueが2000以上のオブジェクト


Shader "Custom/BlendOcclusion"
{
    SubShader
    {
        Tags
        {
            "Queue"="geometry-1"
        }

        //フラグメントシェーダーのAlpha値が0の場合、最終的な描画結果はカラーバッファに既に書き込まれている値になる
        //計算式 → 1 × フラグメントシェーダーの出力 + (1 - フラグメントシェーダーの出力するアルファ値) × カラーバッファに既に書き込まれている値
        //結果 → 1 × 0 + (1 - 0) × カラーバッファに既に書き込まれている値 = カラーバッファに既に書き込まれている値 つまりそのまま
        Blend One OneMinusSrcAlpha

        //フラグメントシェーダーのAlpha値が1の場合、最終的な描画結果はカラーバッファに既に書き込まれている値になる
        //計算式 → 0 × フラグメントシェーダーの出力 + フラグメントシェーダーの出力するアルファ値 × カラーバッファに既に書き込まれている値
        //結果 → 0 × 1 + 1 × カラーバッファに既に書き込まれている値 = カラーバッファに既に書き込まれている値 つまりそのまま
        //Blend Zero SrcAlpha

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fwdbase

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex:POSITION;
            };

            struct v2f
            {
                float4 pos : SV_POSITION;
            };

            v2f vert(appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                return o;
            }

            float4 frag(v2f i) : COLOR
            {
                //最終的なAlpha値が0
                return 0;
                //最終的なAlpha値が1
                //return 1;
            }
            ENDCG
        }
    }
}

続いてはBlendを用いたShaderです。

Blendに与えるパラメータは以下のように計算されます。

finalValue = sourceFactor * sourceValue operation destinationFactor * destinationValue

【引用元】:ShaderLab command: Blend

各項目の説明は以下の通りです。

項目 説明
finalValue 対象となるカラーバッファに書き込む値
sourceFactor どうブレンドするかのコマンド
sourceValue 該当Shader(Pass)においてフラグメントシェーダーが出力する値
operation ブレンド操作コマンド(デフォルトはAdd)
destinationFactor どうブレンドするかのコマンド
destinationValue カラーバッファに既に書き込まれている値

Shader内のコメントと重複しますが、計算結果の例を挙げると以下です。


例) Blend One OneMinusSrcAlpha、フラグメントシェーダーの出力するアルファ値 = 0の場合

計算式 → 1 × フラグメントシェーダーの出力 + (1 - フラグメントシェーダーの出力するアルファ値) × カラーバッファに既に書き込まれている値

結果 → 1 × 0 + (1 - 0) × カラーバッファに既に書き込まれている値 = カラーバッファに既に書き込まれている値


Blendコマンドを利用して最終的に描画される色を"カラーバッファに既に書き込まれている値"にしてあげようというのがこのShaderでやっていることです。

既知の問題

ここまでのShaderには実は問題があります。
それはGameViewで該当Shaderの箇所が真っ黒になってしまうことです。

これはSkyboxの描画タイミングがSceneViewとGameViewで違うことに起因します。

スカイボックスの描画タイミングがシーンビュー上では一番最初、ゲームビュー上では不透明オブジェクトの後

【引用元】:Scene Viewで表示されるオブジェクトがGame Viewだと表示されない

実はこれはそれほど問題ではありません。
なぜかというと、ARの体験時にSkyboxを描画するということはほとんどあり得ないからです。
ARオブジェクト以外はカメラの映像なのでこの問題による大きな影響はありません。

STYLYを用いてSkyboxが無い状態のARを作成しました。
以下リンクからモバイルアプリを開いて再生すると正常にオクルージョンしていることが確認できます。

https://gallery.styly.cc/scene/82868a29-9027-45e0-b949-0c8aed78fab2

ただ、GameViewで適切な見た目を反映してデバッグしたい
ということもあると思うので解決策もメモしておきます。

Skyboxだけをレンダリングするカメラを用意し、メインカメラの後に描画します。
逆にメインカメラはSkyboxを描画しないように設定すれば完成です。

実機での実行時にはSkybox用カメラをオフにしておけば
無駄な描画不可もかからないはずです。

参考リンク

Zバッファだけ更新するシェーダーでオクルージョンの実装
Cg Programming/Unity/Order-Independent Transparency
Cg Programming/Unity/Transparency
[OpenGL] FrameBufferとRenderBufferについてメモ
【Unity】影だけ映る地面を用意し、かつ地面の下が見えないようにする