【Unity】Fogの使い方・シェーダの書き方(Forwardレンダリング)

UnityのFowardRenderingにおけるFogの設定方法と、カスタムシェーダの書き方です。

Unity2018.3

注意点

この記事ではUnityのLighting Settingsから設定できるFog機能について説明します。
これはForward Renderingにしか対応していないのでご注意ください。

Deferredの場合はPost Processingによるポストエフェクトを使うとよさそうです。

Fog?

Fogはその名の通り霧を表現するための機能です。
空気感の演出に役立ちます。

例えばこんなSceneにFogをかけると、

f:id:halya_11:20190122231930p:plain

こうなります。

f:id:halya_11:20190122231946p:plain

設定方法

FogはLighting SettingsからScene毎に設定できます。

f:id:halya_11:20190122232353p:plain

基本的にはFogにチェックを入れて色を調整するだけです。
Modeを変えるとFogの色を計算するアルゴリズムが変わりますが、これについては後述します。

カスタムシェーダに適用する

Fog周りはマクロが用意されているため、カスタムシェーダでも簡単に掛けることができます。
以下はシンプルにフォグだけを掛けたシェーダの例です。

Shader "Fog"
{
    SubShader
    {
        Pass
        {
            CGPROGRAM
           #pragma vertex vert
           #pragma fragment frag
            // フォグ用のバリアントを生成
           #pragma multi_compile_fog

           #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
                // フォグ用のTEXCOORDのindexを指定
                UNITY_FOG_COORDS(1)
            };
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                
                // 頂点シェーダでの計算はこのマクロで
                UNITY_TRANSFER_FOG(o, o.vertex);

                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = 1;

                // フラグメントシェーダでの計算はこのマクロで
                UNITY_APPLY_FOG(i.fogCoord, col);

                return col;
            }
            ENDCG
        }
    }
}

説明はコメントに書いた通りです。
中身で何をしているのかをもうちょっと知りたいので、マクロの中身を次節で展開してみます。

マクロを展開してみる

前節のシェーダのマクロの中身を少し展開してみます。
Fog周りのマクロはUnityCG.cgincに定義されていました。
※ モバイルかどうかの分岐があったので、モバイル前提で展開しています

Shader "Fog2"
{
    SubShader
    {
        Pass
        {
            CGPROGRAM
           #pragma vertex vert
           #pragma fragment frag
            // #pragma multi_compile_fog
           #pragma multi_compile FOG_EXP FOG_EXP2 FOG_LINEAR

           #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
                // UNITY_FOG_COORDS(1)
                float fogCoord : TEXCOORD1;
            };
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                
                // UNITY_TRANSFER_FOG(o, o.vertex);
                UNITY_CALC_FOG_FACTOR(o.vertex.z);
                o.fogCoord.x = unityFogFactor;

                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = 1;

                // UNITY_APPLY_FOG(i.fogCoord, col);
               #ifdef UNITY_PASS_FORWARDADD
                    col.rgb = lerp(fixed4(0, 0, 0, 0).rgb, col.rgb, saturate(i.fogCoord));
               #else
                    col.rgb = lerp(unity_FogColor.rgb, col.rgb, saturate(i.fogCoord));
               #endif

                return col;
            }
            ENDCG
        }
    }
}

まずマクロではないですが、#pragma multi_compile_fogは下記の通りに書き換えました。

#pragma multi_compile FOG_EXP FOG_EXP2 FOG_LINEAR

これはFogのModeに応じた三つのバリアントを作っています。
Scene毎にModeを変えるようなことがなければshader_featureのほうがいい気はします。

次にUNITY_FOG_COORDS(1)はfogCoordという名前でInterpolatorを定義しているだけです。

また、頂点シェーダのマクロUNITY_TRANSFER_FOGは展開先でUNITY_CALC_FOG_FACTORというマクロを読んでいます。
実はこのマクロの中でFogのMode毎の処理を分けているのですが、これは次節で説明します。
UNITY_CALC_FOG_FACTORunityFogFactorという変数に値を格納してくれるので、これをInterpolatorに代入します。

最後にフラグメントシェーダのUNITY_APPLY_FOGです。
これはLighting Settingsで設定したFogの色と物体色を頂点シェーダで計算したunityFogFactorで補間するだけです。
結果、unityFogFactorの値が低いところほどFogの色が反映されます。
また、ForwardAddパスの場合は値が低いほど黒に近づき、加算効果が弱くなります。

Mode別のアルゴリズム

前節のUNITY_CALC_FOG_FACTORマクロをさらに展開していくとFogのMode毎のアルゴリズムが見えてきます。
まずMode毎にunityFogFactor(以下factor)はそれぞれ以下の式で求められます。

Mode 説明
Linear  { factor=(end-z)(end-start)} zはクリッピング空間の深度
startとendはLighting Settingsの値
Exponential  { factor = exp(-density*z)} zはクリッピング空間の深度
densityはLighting Settingsの値
Exponential Squared  { factor = exp(-(density*z)^{2})} zはクリッピング空間の深度
densityはLighting Settingsの値

それでは一つ一つ見ていきます。

Linearは単純に、startから線形に掛かり始めてendで完全にFogの色になります。
下図はstart = 0, end = 100としたときの図です(前節の通り。この値が低いほどFogの色になります)。

f:id:halya_11:20190123003604p:plain
Linear / start = 0, end = 100

次にExponentialです。
これはLinearよりも、奥に行けば行くほどFogが強くなります。
下はdensity = 0.06のときの図です。

f:id:halya_11:20190123004130p:plain
Exponential / density = 0.06

densityの値を上げるとよりFogがかかりやすくなります。

f:id:halya_11:20190123004618p:plain
Exponential / density = 0.3

最後にExponential Squaredです。
これはExponentialより手前にFogがかかりづらい感じになりそうです。
使いやすそうな気がします。

f:id:halya_11:20190123004836p:plain
Exponential Squared / density = 0.1

こちらもdensityを上げるとFogがかかりやすくなります。

f:id:halya_11:20190123005025p:plain
Exponential Squared / density = 0.3

以上です。

参考

docs.unity3d.com