Unity Fogの処理を追う。builtin_shaders調査の手順メモ

UNITY_TRANSFER_FOGの処理で調査した事のメモです。また、builtin_shaders調査の手順メモ。

builtin_shaders-2019.3.9f1をDLして調査。 unity3d.com

Create > Shader > Unlitで生成される最低限のshaderコードと比較しながら調べています。 生成されたコードはこれ。

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

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);
                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, col);
                return col;
            }
            ENDCG
        }
    }
}

UNITY_FOG_COORDSとは?

こんな感じで頂点データの出力に記述されている。

struct v2f
{
    float2 uv : TEXCOORD0;
    UNITY_FOG_COORDS(1)
    float4 vertex : SV_POSITION;
};

define UNITY_FOG_COORDS(で全ファイルを検索。下記が該当箇所。

#if defined(FOG_LINEAR) || defined(FOG_EXP) || defined(FOG_EXP2)
    #define UNITY_FOG_COORDS(idx) UNITY_FOG_COORDS_PACKED(idx, float1)

UNITY_FOG_COORDS_PACKEDの処理はすぐ上にある。

#define UNITY_FOG_COORDS_PACKED(idx, vectype) vectype fogCoord : TEXCOORD##idx;

UNITY_FOG_COORDS(1) と記述した箇所に、

float fogCoord TEXCOORD1;

を追加するのみ。 FOG_LINEAR、FOG_EXP、FOG_EXP2のどれかのフラグが立っていない場合は、何も定義は追加されない。 フラグが立っている時だけ記述を追加する最適化のため。

UNITY_TRANSFER_FOGとは?

こんな感じで、頂点shaderで使用される。ここの UNITY_TRANSFER_FOGを調べる。

v2f vert (appdata v)
{
    v2f o;
    o.vertex = UnityObjectToClipPos(v.vertex);
    o.uv = TRANSFORM_TEX(v.uv, _MainTex);
    UNITY_TRANSFER_FOG(o,o.vertex);
    return o;
}
define UNITY_TRANSFER_FOG(

で全ファイルから検索。コメント入れた部分の真下が該当箇所。

#if defined(FOG_LINEAR) || defined(FOG_EXP) || defined(FOG_EXP2)
    #define UNITY_FOG_COORDS(idx) UNITY_FOG_COORDS_PACKED(idx, float1)

    #if (SHADER_TARGET < 30) || defined(SHADER_API_MOBILE)
        // mobile or SM2.0: calculate fog factor per-vertex  
        // shader modelが3.0より下、もしくはモバイル用。頂点単位でfogかける
/* → */ #define UNITY_TRANSFER_FOG(o,outpos) UNITY_CALC_FOG_FACTOR((outpos).z); o.fogCoord.x = unityFogFactor

        #define UNITY_TRANSFER_FOG_COMBINED_WITH_TSPACE(o,outpos) UNITY_CALC_FOG_FACTOR((outpos).z); o.tSpace1.y = tangentSign; o.tSpace2.y = unityFogFactor
        #define UNITY_TRANSFER_FOG_COMBINED_WITH_WORLD_POS(o,outpos) UNITY_CALC_FOG_FACTOR((outpos).z); o.worldPos.w = unityFogFactor
        #define UNITY_TRANSFER_FOG_COMBINED_WITH_EYE_VEC(o,outpos) UNITY_CALC_FOG_FACTOR((outpos).z); o.eyeVec.w = unityFogFactor
    #else
        // SM3.0 and PC/console: calculate fog distance per-vertex, and fog factor per-pixel 
        // shader model3.0以上かつpc/console用。fog distanceは頂点ごと、fog factorはpixelごと
/* → */ #define UNITY_TRANSFER_FOG(o,outpos) o.fogCoord.x = (outpos).z

        #define UNITY_TRANSFER_FOG_COMBINED_WITH_TSPACE(o,outpos) o.tSpace2.y = (outpos).z
        #define UNITY_TRANSFER_FOG_COMBINED_WITH_WORLD_POS(o,outpos) o.worldPos.w = (outpos).z
        #define UNITY_TRANSFER_FOG_COMBINED_WITH_EYE_VEC(o,outpos) o.eyeVec.w = (outpos).z
    #endif
#else
    #define UNITY_FOG_COORDS(idx)
    // 上記以外
/**/#define UNITY_TRANSFER_FOG(o,outpos)

    #define UNITY_TRANSFER_FOG_COMBINED_WITH_TSPACE(o,outpos)
    #define UNITY_TRANSFER_FOG_COMBINED_WITH_WORLD_POS(o,outpos)
    #define UNITY_TRANSFER_FOG_COMBINED_WITH_EYE_VEC(o,outpos)
#endif

今回はmobileを調査。最初の箇所を更に深堀りしていく。 マクロの中にマクロがあるので、その中のUNITY_CALC_FOG_FACTORを調査する。

UNITY_CALC_FOG_FACTORとは?

define UNITY_CALC_FOG_FACTOR(

で検索。

#define UNITY_CALC_FOG_FACTOR(coord) UNITY_CALC_FOG_FACTOR_RAW(UNITY_Z_0_FAR_FROM_CLIPSPACE(coord))

が該当箇所。 また中にマクロがある。先にUNITY_Z_0_FAR_FROM_CLIPSPACEを調べる。

UNITY_Z_0_FAR_FROM_CLIPSPACEとは?

define UNITY_Z_0_FAR_FROM_CLIPSPACE(

で検索。下記が該当箇所。

#if defined(UNITY_REVERSED_Z)
    #if UNITY_REVERSED_Z == 1
        //D3d with reversed Z => z clip range is [near, 0] -> remapping to [0, far]
        //max is required to protect ourselves from near plane not being correct/meaningfull in case of oblique matrices.
        #define UNITY_Z_0_FAR_FROM_CLIPSPACE(coord) max(((1.0-(coord)/_ProjectionParams.y)*_ProjectionParams.z),0)
    #else
        //GL with reversed z => z clip range is [near, -far] -> should remap in theory but dont do it in practice to save some perf (range is close enough)
        #define UNITY_Z_0_FAR_FROM_CLIPSPACE(coord) max(-(coord), 0)
    #endif
#elif UNITY_UV_STARTS_AT_TOP
    //D3d without reversed z => z clip range is [0, far] -> nothing to do
    #define UNITY_Z_0_FAR_FROM_CLIPSPACE(coord) (coord)
#else
    //Opengl => z clip range is [-near, far] -> should remap in theory but dont do it in practice to save some perf (range is close enough)
    #define UNITY_Z_0_FAR_FROM_CLIPSPACE(coord) (coord)
#endif

フラグらしきUNITY_REVERSED_Zがあるので調査。

UNITY_REVERSED_Z とは?

注: DX11 / 12、PS4、XboxOne、およびMetalでは、Zバッファーの範囲は1〜0で、UNITY_REVERSED_Zが定義されています。他のプラットフォームでは、範囲は0〜1です。 docs.unity3d.com

今回はMetalを前提とする。metalはUNITY_REVERSED_Zフラグが立っている。 その中の処理に進む

        //D3d with reversed Z => z clip range is [near, 0] -> remapping to [0, far]

        //max is required to protect ourselves from near plane not being correct/meaningfull in case of oblique matrices.
        // 斜行行列の場合、ニアプレーンが正しくない/意味がないことから身を守るためにmaxが必要です。

        #define UNITY_Z_0_FAR_FROM_CLIPSPACE(coord) max(((1.0-(coord)/_ProjectionParams.y)*_ProjectionParams.z),0)

今度は_ProjectionParamsというパラメータが出てきたので調べる。

_ProjectionParamsとは?

カメラのnear, far値が取得できるようです。

型はfloat4。xは1.0(反転投影行列でレンダリングしている場合は-1.0),yはカメラのnear plane,zはカメラの far plane,wは1 / FarPlaneです. docs.unity3d.com

UNITY_CALC_FOG_FACTORまで戻ると、今度はUNITY_CALC_FOG_FACTOR_RAWマクロがあるので、これを調べる。

UNITY_CALC_FOG_FACTOR_RAWとは?

define UNITY_CALC_FOG_FACTOR_RAW( で検索。

下記が該当。

#if defined(FOG_LINEAR)
    // factor = (end-z)/(end-start) = z * (-1/(end-start)) + (end/(end-start))
    #define UNITY_CALC_FOG_FACTOR_RAW(coord) float unityFogFactor = (coord) * unity_FogParams.z + unity_FogParams.w
#elif defined(FOG_EXP)
    // factor = exp(-density*z)
    #define UNITY_CALC_FOG_FACTOR_RAW(coord) float unityFogFactor = unity_FogParams.y * (coord); unityFogFactor = exp2(-unityFogFactor)
#elif defined(FOG_EXP2)
    // factor = exp(-(density*z)^2)
    #define UNITY_CALC_FOG_FACTOR_RAW(coord) float unityFogFactor = unity_FogParams.x * (coord); unityFogFactor = exp2(-unityFogFactor*unityFogFactor)
#else
    #define UNITY_CALC_FOG_FACTOR_RAW(coord) float unityFogFactor = 0.0
#endif

fogには3つのモードがあり、 FOG_LINEAR、FOG_EXP、FOG_EXP2がある。

今回はFOG_EXPを調べる。

// factor = exp(-density*z)
#define UNITY_CALC_FOG_FACTOR_RAW(coord) float unityFogFactor = unity_FogParams.y * (coord); unityFogFactor = exp2(-unityFogFactor)

全てのマクロを展開する

UNITY_TRANSFER_FOG(o, o.vertex); は、

float z = max(((1.0-(o.vertex.z) / _ProjectionParams.y) * _ProjectionParams.z), 0);
float unityFogFactor = unity_FogParams.y * z;
unityFogFactor = exp2(-unityFogFactor);
o.fogCoord.x = unityFogFactor;

となる。

UNITY_TRANSFER_FOG(o, o.vertex);

を置き換えて、挙動が変化しない事を確認。 (window > Rendering > lighting settingsのfog modeもチェックする事)