LoginSignup
12
7

More than 1 year has passed since last update.

UnityShader Shadowの記述備忘録

Last updated at Posted at 2020-03-13

影の書き込みに必要な記述を追う

Shadowのバッファーへの書き込みと、影を受ける側の処理を把握する為にビルトインシェーダーの記述を追ってみる。2019.3系のビルトインシェーダーを参考にします。
ひとまず記述が簡潔そうなVertexLitの影の書き込みを見てみる。

Mobile-VertexLit.shader
    // Pass to render object as a shadow caster
    Pass
    {
        Name "ShadowCaster"
        Tags { "LightMode" = "ShadowCaster" }

        ZWrite On ZTest LEqual Cull Off

        CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag
        #pragma target 2.0
        #pragma multi_compile_shadowcaster
        #include "UnityCG.cginc"

        struct v2f {
            V2F_SHADOW_CASTER;
            UNITY_VERTEX_OUTPUT_STEREO
        };

        v2f vert( appdata_base v )
        {
            v2f o;
            UNITY_SETUP_INSTANCE_ID(v);
            UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
            TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
            return o;
        }

        float4 frag( v2f i ) : SV_Target
        {
            SHADOW_CASTER_FRAGMENT(i)
        }
        ENDCG
    }

適当に整理して重要な部分だけ抜粋するとこう。

        Tags { "LightMode" = "ShadowCaster" }
        ZWrite On ZTest LEqual Cull Off

        #pragma vertex vert
        #pragma fragment frag
        #pragma multi_compile_shadowcaster
        #include "UnityCG.cginc"

        struct v2f {
            V2F_SHADOW_CASTER;
        };

        v2f vert( appdata_base v )
        {
            v2f o;
            TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
            return o;
        }

        float4 frag( v2f i ) : SV_Target
        {
            SHADOW_CASTER_FRAGMENT(i)
        }

把握するべきはV2F_SHADOW_CASTER;TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)SHADOW_CASTER_FRAGMENT(i)っぽい。
一応考えやすい様に併記しておくとappdata_baseはこう定義されている。

UnityCG.cginc
struct appdata_base {
    float4 vertex : POSITION;
    float3 normal : NORMAL;
    float4 texcoord : TEXCOORD0;
    UNITY_VERTEX_INPUT_INSTANCE_ID
};

UNITY_VERTEX_INPUT_INSTANCE_IDはGPUInstancing対応の為の記述なので、頂点座標,法線,UV座標を持っているだけの構造体と思っておけば良い。

前述の3つの記述を詳しく追ってみる。

V2F_SHADOW_CASTER;

結論

展開するとこうなる。

struct v2f {
    //V2F_SHADOW_CASTER;
    float4 pos : SV_POSITION
    float3 vec : TEXCOORD0; //これはグラフィックAPIとSHADOWS_CUBEキーワード次第
};

###マクロを読む
こんなマクロになっている。

UnityCG.cginc
// Declare all data needed for shadow caster pass output (any shadow directions/depths/distances as needed),
// plus clip space position.
#define V2F_SHADOW_CASTER V2F_SHADOW_CASTER_NOPOS UNITY_POSITION(pos)

V2F_SHADOW_CASTER_NOPOSUNITY_POSITION(pos)をそれぞれ確認してみる。
まず簡単なUNITY_POSITION(pos)。これはときどき見かける。

HLSLSupport.cginc
// On D3D reading screen space coordinates from fragment shader requires SM3.0
#define UNITY_POSITION(pos) float4 pos : SV_POSITION

単純にクリップ座標を入れる変数を宣言しているだけ。
次にV2F_SHADOW_CASTER_NOPOS

UnityCG.cginc
#if defined(SHADOWS_CUBE) && !defined(SHADOWS_CUBE_IN_DEPTH_TEX)
    // Rendering into point light (cubemap) shadows
    #define V2F_SHADOW_CASTER_NOPOS float3 vec : TEXCOORD0;
    //...省略...
#else
    //...省略...
#endif

コメントを読むとポイントライトのシャドウを表現する場合はこれが構造体に追加される場合があるようですね。
SHADOW_CUBEはCubemapでシャドウマップを表現する時に有効になるkeywordのよう?cginc内で定義されていないようなので、実行時にCPU側からEnableされていそう。
SHADOWS_CUBE_IN_DEPTH_TEXは以下のように定義されているのでプラットフォームが"depth-format cube shadow map"に対応していない場合はにfloat3 vec : TEXCOORD0;が構造体に追加されるという事になりそう。

UnityCG.cginc
#if defined(SHADER_API_D3D11) || defined(SHADER_API_PSSL) || defined(SHADER_API_METAL) || defined(SHADER_API_GLCORE) || defined(SHADER_API_GLES3) || defined(SHADER_API_VULKAN) || defined(SHADER_API_SWITCH) // D3D11, D3D12, XB1, PS4, iOS, macOS, tvOS, glcore, gles3, webgl2.0, Switch
// Real-support for depth-format cube shadow map.
#define SHADOWS_CUBE_IN_DEPTH_TEX
#endif

とはいえ結構一般的なグラフィックAPIがそろっているようなので、ほとんどの場合はfloat3 vec : TEXCOORD0;は構造体に追加されないと思っていても良さそう。こればかりは開発対象次第ですね。
ひとまず、V2F_SHADOW_CASTER;マクロを展開してみるとこうなりますね。

struct v2f {
    //V2F_SHADOW_CASTER;
    float4 pos : SV_POSITION
    float3 vec : TEXCOORD0; //これはグラフィックAPIとSHADOWS_CUBEキーワード次第
};

TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)

結論

こう展開される

        v2f vert( appdata_base v )
        {
            v2f o;
            //TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
            #if defined(SHADOWS_CUBE) && !defined(SHADOWS_CUBE_IN_DEPTH_TEX)
                o.vec = mul(unity_ObjectToWorld, v.vertex).xyz - _LightPositionRange.xyz; opos = UnityObjectToClipPos(v.vertex);
            #else
                o.pos = UnityClipSpaceShadowCasterPos(v.vertex, v.normal); 
                o.pos = UnityApplyLinearShadowBias(o.pos);
            #endif
            return o;
        }

マクロを読む

マクロはこうなっている

UnityCG.cginc
// Vertex shader part, with support for normal offset shadows. Requires
// position and normal to be present in the vertex input.
#define TRANSFER_SHADOW_CASTER_NORMALOFFSET(o) TRANSFER_SHADOW_CASTER_NOPOS(o,o.pos)

TRANSFER_SHADOW_CASTER_NOPOSを確認してみます。

UnityCG.cginc
#if defined(SHADOWS_CUBE) && !defined(SHADOWS_CUBE_IN_DEPTH_TEX)
    //省略
    #define TRANSFER_SHADOW_CASTER_NOPOS(o,opos) o.vec = mul(unity_ObjectToWorld, v.vertex).xyz - _LightPositionRange.xyz; opos = UnityObjectToClipPos(v.vertex);
    //省略

#else
    //省略
    #define TRANSFER_SHADOW_CASTER_NOPOS(o,opos) \
        opos = UnityClipSpaceShadowCasterPos(v.vertex, v.normal); \
        opos = UnityApplyLinearShadowBias(opos);
    //省略
#endif

前述の分岐がここでも出てきます。
前者の分岐先では光源から頂点に向くベクトル(ワールド座標系)と、頂点をUnityObjectToClipPos()したクリップ座標を計算していますね。後者の分岐先は、頂点座標と法線を入力にUnityClipSpaceShadowCasterPos()UnityApplyLinearShadowBias()でなにやら計算してますね。このふたつの関数を追ってみます。

UnityCG.cginc
float4 UnityClipSpaceShadowCasterPos(float4 vertex, float3 normal)
{
    float4 wPos = mul(unity_ObjectToWorld, vertex);

    if (unity_LightShadowBias.z != 0.0)
    {
        float3 wNormal = UnityObjectToWorldNormal(normal);
        float3 wLight = normalize(UnityWorldSpaceLightDir(wPos.xyz));

        // apply normal offset bias (inset position along the normal)
        // bias needs to be scaled by sine between normal and light direction
        // (http://the-witness.net/news/2013/09/shadow-mapping-summary-part-1/)
        //
        // unity_LightShadowBias.z contains user-specified normal offset amount
        // scaled by world space texel size.

        float shadowCos = dot(wNormal, wLight);
        float shadowSine = sqrt(1-shadowCos*shadowCos);
        float normalBias = unity_LightShadowBias.z * shadowSine;

        wPos.xyz -= wNormal * normalBias;
    }

    return mul(UNITY_MATRIX_VP, wPos);
}

頂点座標、法線をワールド座標に変換して頂点を法線方向に幾らか動かしてからクリップ座標に変換していますね。幾らかはLightコンポーネントで設定するBias依存っぽい。

UnityApplyLinearShadowBias()も確認します。前述のコードからUnityClipSpaceShadowCasterPos()で出力された法線方向にオフセットされたクリップ座標が引数にくることが分かっています。

UnityCG.cginc
float4 UnityApplyLinearShadowBias(float4 clipPos)

{
    // For point lights that support depth cube map, the bias is applied in the fragment shader sampling the shadow map.
    // This is because the legacy behaviour for point light shadow map cannot be implemented by offseting the vertex position
    // in the vertex shader generating the shadow map.
#if !(defined(SHADOWS_CUBE) && defined(SHADOWS_CUBE_IN_DEPTH_TEX))
    #if defined(UNITY_REVERSED_Z)
        // We use max/min instead of clamp to ensure proper handling of the rare case
        // where both numerator and denominator are zero and the fraction becomes NaN.
        clipPos.z += max(-1, min(unity_LightShadowBias.x / clipPos.w, 0));
    #else
        clipPos.z += saturate(unity_LightShadowBias.x/clipPos.w);
    #endif
#endif

#if defined(UNITY_REVERSED_Z)
    float clamped = min(clipPos.z, clipPos.w*UNITY_NEAR_CLIP_VALUE);
#else
    float clamped = max(clipPos.z, clipPos.w*UNITY_NEAR_CLIP_VALUE);
#endif
    clipPos.z = lerp(clipPos.z, clamped, unity_LightShadowBias.y);
    return clipPos;
}

とりあえずグラフィックAPI差を吸収して深度値の元になる値(clipPos.z)を調整しているっぽい。

なので、展開の結果は以下になる。特別な処理の内容は以下の2点。

  • 法線方向に頂点がバイアスでオフセットされる
  • 深度がバイアスで調整される
        v2f vert( appdata_base v )
        {
            v2f o;
            //TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
            #if defined(SHADOWS_CUBE) && !defined(SHADOWS_CUBE_IN_DEPTH_TEX)
                o.vec = mul(unity_ObjectToWorld, v.vertex).xyz - _LightPositionRange.xyz; opos = UnityObjectToClipPos(v.vertex);
            #else
                o.pos = UnityClipSpaceShadowCasterPos(v.vertex, v.normal); 
                o.pos = UnityApplyLinearShadowBias(o.pos);
            #endif
            return o;
        }

SHADOW_CASTER_FRAGMENT(i)

結論

こんな感じに展開される。(少し書きかえました)

        float4 frag( v2f i ) : SV_Target
        {
            #if defined(SHADOWS_CUBE) && !defined(SHADOWS_CUBE_IN_DEPTH_TEX)
              //省略
              float depth = (length(i.vec) + unity_LightShadowBias.x) * _LightPositionRange.w;
              #ifdef UNITY_USE_RGBA_FOR_POINT_SHADOWS
                return EncodeFloatRGBA (min(depth, 0.999));
              #else
                return depth;
              #endif
            #else
              return 0;
            #endif
        }

マクロを追う

振り返っておくと、SHADOW_CASTER_FRAGMENT(i)はフラグメントシェーダーで実行されているのでこのマクロが最終的な深度値にあたる値を出力していそうな予想はできる。

UnityCG.cginc

#if defined(SHADOWS_CUBE) && !defined(SHADOWS_CUBE_IN_DEPTH_TEX)
    //省略
    #define SHADOW_CASTER_FRAGMENT(i) return UnityEncodeCubeShadowDepth ((length(i.vec) + unity_LightShadowBias.x) * _LightPositionRange.w);
#else
    //省略
    #define SHADOW_CASTER_FRAGMENT(i) return 0;
#endif

後者の分岐先はreturn 0;なので特に何もしていなさそう。これは多分SV_Depthの値をシャドウマップに使用するからSV_Targetがどんな値を出力しても関係ないということなんだと思う。前者の分岐先においてだけSV_Targetが必要。

(length(i.vec) + unity_LightShadowBias.x) * _LightPositionRange.w

の部分ついて考える。
i.vecは光源から頂点に向かうベクトルだったのでlength(i.vec)で光源からの距離を計算して、* _LightPositionRange.wで距離を0~1に正規化しているのかな。+ unity_LightShadowBias.xは距離にバイアスを持たせているだけっぽい。この計算で0~1に収まる深度が計算されているっぽいですね。
UnityEncodeCubeShadowDepth()はfloat精度の0~1の値を必要に応じて8bitRGBAにパックしてる。"必要に応じて"というのはfloat精度のレンダリングターゲットをサポートしているか否か(UNITY_USE_RGBA_FOR_POINT_SHADOWSキーワード)で分岐されていて、defined(SHADER_API_GLES) || defined(SHADER_API_GLES3)ならRGBAにfloatを分割して保存している。
なので以下のように展開される。

        float4 frag( v2f i ) : SV_Target
        {
            #if defined(SHADOWS_CUBE) && !defined(SHADOWS_CUBE_IN_DEPTH_TEX)
                //省略
                float depth = (length(i.vec) + unity_LightShadowBias.x) * _LightPositionRange.w;
                #ifdef UNITY_USE_RGBA_FOR_POINT_SHADOWS
                    return EncodeFloatRGBA (min(depth, 0.999));
                #else
                    return depth;
                #endif
            #else
                return 0;
            #endif
        }

全体を展開してみる

長くなるので静的分岐ごとに分けて2パターンに展開します。
#if defined(SHADOWS_CUBE) && !defined(SHADOWS_CUBE_IN_DEPTH_TEX)な場合は以下のように展開されるはず。

        Tags { "LightMode" = "ShadowCaster" }
        ZWrite On ZTest LEqual Cull Off

        #pragma vertex vert
        #pragma fragment frag
        #pragma multi_compile_shadowcaster
        #include "UnityCG.cginc"

        struct v2f {
            float4 pos : SV_POSITION;
            float3 vec : TEXCOORD0;
        };

        v2f vert( appdata_base v )
        {
            v2f o;
            o.vec = mul(unity_ObjectToWorld, v.vertex).xyz - _LightPositionRange.xyz;
            o.pos = UnityObjectToClipPos(v.vertex);
            return o;
        }

        float4 frag( v2f i ) : SV_Target
        {
            float depth = (length(i.vec) + unity_LightShadowBias.x) * _LightPositionRange.w;
            
            //defined(SHADER_API_GLES) || defined(SHADER_API_GLES3)の場合はEncodeFloatRGBAでエンコード
            #ifdef UNITY_USE_RGBA_FOR_POINT_SHADOWS
                return EncodeFloatRGBA (min(depth, 0.999));
            #else
                return depth;
            #endif
        }

次に、#if defined(SHADOWS_CUBE) && !defined(SHADOWS_CUBE_IN_DEPTH_TEX)ではない場合の展開結果。多分多くの場合はこっちが使われると思う。

        Tags { "LightMode" = "ShadowCaster" }
        ZWrite On ZTest LEqual Cull Off

        #pragma vertex vert
        #pragma fragment frag
        #pragma multi_compile_shadowcaster
        #include "UnityCG.cginc"

        struct v2f {
            float4 pos : SV_POSITION;
        };

        v2f vert( appdata_base v )
        {
            v2f o;
            //バイアス依存で法線方向にオフセット+バイアス依存で深度値を調整?
            o.pos = UnityClipSpaceShadowCasterPos(v.vertex, v.normal); 
            o.pos = UnityApplyLinearShadowBias(o.pos);
            return o;
        }

        float4 frag( v2f i ) : SV_Target
        {
            return 0;
        }

備考

SHADOWS_CUBEキーワードが有効になる条件だけは確かな情報が見当たらなかったのでわからないまま...
(少なくとも以下のAPIを使用している場合は気にしなくてよい)
#if defined(SHADER_API_D3D11) || defined(SHADER_API_PSSL) || defined(SHADER_API_METAL) || defined(SHADER_API_GLCORE) || defined(SHADER_API_GLES3) || defined(SHADER_API_VULKAN) || defined(SHADER_API_SWITCH) // D3D11, D3D12, XB1, PS4, iOS, macOS, tvOS, glcore, gles3, webgl2.0, Switch

と思ったら、以下の記述を見つけたのでpoint lightでrealtime shadowする時にEnableになるのかもしれない。

AutoLight.cginc
        //point realtime shadow
        #if defined (SHADOWS_CUBE)
       //省略

影を受ける為に必要な処理を追う

影を受ける側の処理は適切なサンプルになりそうなシェーダーがみつからなかったので負った結果のサンプルを以下に記載します。静的分岐が複雑なのでマクロを追うのがしんどいです。影を受ける側でトリッキーな事をする場面は少ないと思うので深く入り込まなくても良い気はするので深くは調べませんでした。

Shader "Unlit/ReceiveShadow"
{
  Properties
  {
    _MainTex ("Texture", 2D) = "white" { }
  }
  SubShader
  {
    Name "FORWARD"
    Tags { "RenderType" = "Opaque" "LightMode" = "ForwardBase" }
    
    Pass
    {
      CGPROGRAM
      
      #pragma vertex vert
      #pragma fragment frag
      #pragma multi_compile_fwdbase_fullshadows
      
      #include "UnityCG.cginc"
      #include "AutoLight.cginc"
      
      struct appdata
      {
        float4 vertex: POSITION;
        float2 uv: TEXCOORD0;
        float2 uv1: TEXCOORD1;
      };
      
      struct v2f
      {
        float2 uv: TEXCOORD0;
        float4 pos: SV_POSITION;
        float3 worldPos: WORLDPOS;
        UNITY_LIGHTING_COORDS(3, 4)
      };
      
      sampler2D _MainTex;
      
      v2f vert(appdata v)
      {
        v2f o;
        o.pos = UnityObjectToClipPos(v.vertex);
        o.uv = v.uv;
        o.worldPos = mul(unity_ObjectToWorld, v.vertex);
        UNITY_TRANSFER_LIGHTING(o, v.uv1);
        return o;
      }
      
      fixed4 frag(v2f i): SV_Target
      {
        fixed4 col = tex2D(_MainTex, i.uv);
        UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
        col *= atten;
        return col;
      }
      ENDCG
      
    }
    
    // shadowを受けるのにもshadowcasterパスが必要
    // Pass to render object as a shadow caster
    Pass
    {
      Name "ShadowCaster"
      Tags { "LightMode" = "ShadowCaster" }
      
      ZWrite On ZTest LEqual Cull Off
      
      CGPROGRAM
      
      #pragma vertex vert
      #pragma fragment frag
      #pragma multi_compile_shadowcaster
      #include "UnityCG.cginc"
      
      struct v2f
      {
        float4 pos: SV_POSITION;
      };
      
      v2f vert(appdata_base v)
      {
        v2f o;
        //バイアス依存で法線方向にオフセット+バイアス依存で深度値を調整?
        o.pos = UnityClipSpaceShadowCasterPos(v.vertex, v.normal);
        o.pos = UnityApplyLinearShadowBias(o.pos);
        return o;
      }
      
      float4 frag(v2f i): SV_Target
      {
        return 0;
      }
      ENDCG
      
    }
  }
}

UNITY_LIGHTING_COORDS(n,m)

v2f構造体にシャドウマップをサンプリングする為のUV座標を含める必要があります。UNITY_LIGHTING_COORDS(n,m)マクロはその為のマクロで以下のように定義されている。

AutoLight.cginc
#define UNITY_LIGHTING_COORDS(idx1, idx2) DECLARE_LIGHT_COORDS(idx1) UNITY_SHADOW_COORDS(idx2)

2つのマクロに分かれていてそれぞれライトコードとシャドウコードを構造体内に宣言してる。
DECLARE_LIGHT_COORDS(idx1)UNITY_SHADOW_COORDS(idx2)も、分岐によって2or3or4次元のベクトルを宣言している。変数名は_LightCoord_ShadowCoord

UNITY_TRANSFER_LIGHTING(o, v.uv1);

頂点シェーダー内で_LightCoord_ShadowCoordにUV座標を計算し代入する処理がUNITY_TRANSFER_LIGHTING(o, v.uv1)マクロで提供されています。uv1と書いているのはStandardシェーダーないでそう書かれていたのを持ってきたからなんですが、lightmap uvを使いたいとかそういうことなのかな。あんまりその辺理解していないのでよく分からず...
考えやすい様にマクロで使用されている変数名は書き換えてますが、定義は以下のようになっています。

AutoLight.cginc
#define UNITY_TRANSFER_LIGHTING(o, coord) COMPUTE_LIGHT_COORDS(o) UNITY_TRANSFER_SHADOW(o, coord)

COMPUTE_LIGHT_COORDS(o)はライトの種類で分岐してる。が、基本的にライトをカメラとして考えてカメラから見たクリップ座標のような座標を算出している感じになっている。例えば以下のような感じ。厳密にどういう変換をしているかはunity_WorldToLight行列次第。

AutoLight.cginc
#define COMPUTE_LIGHT_COORDS(o) o._LightCoord = mul(unity_WorldToLight, mul(unity_ObjectToWorld, v.vertex)).xyz;

UNITY_TRANSFER_SHADOW(o, coord)は分岐パターンが多くて追うのが億劫なので中止。とりあえず、o._ShadowCoord変数に2ro3or4次元のベクトルが代入される。計算に使われる特別な変数はunity_WorldToShadow[0],_LightPositionRange.xyz,unity_LightmapST.xyzwくらい。

UNITY_LIGHT_ATTENUATION(atten, i, s.posWorld);

後はフラグメントシェーダーでシャドウマップを参照して影を付けるだけ。
UNITY_LIGHT_ATTENUATIONマクロも分岐が複雑なのでざっくりの理解で進める。例えばこんな分岐がある。
シャドウとかCoolieとかを含めて諸々処理した結果をfixed destName変数に代入している。

#define UNITY_LIGHT_ATTENUATION(destName, input, worldPos) \
    DECLARE_LIGHT_COORD(input, worldPos); \
    fixed shadow = UNITY_SHADOW_ATTENUATION(input, worldPos); \
    fixed destName = (lightCoord.z > 0) * UnitySpotCookie(lightCoord) * UnitySpotAttenuate(lightCoord.xyz) * shadow;

どの分岐でもUNITY_SHADOW_ATTENUATION(input, worldPos);は含まれていて、これはUnityComputeForwardShadows()関数を呼ぶか、SHADOW_ATTENUATION(a)マクロを実行してる。ほとんどの場合UnityComputeForwardShadows()関数が呼ばれている。

AutoLight.cginc
half UnityComputeForwardShadows(float2 lightmapUV, float3 worldPos, float4 screenPos)
{
    //fade value
    float zDist = dot(_WorldSpaceCameraPos - worldPos, UNITY_MATRIX_V[2].xyz);
    float fadeDist = UnityComputeShadowFadeDistance(worldPos, zDist);
    half  realtimeToBakedShadowFade = UnityComputeShadowFade(fadeDist);

    //baked occlusion if any
    half shadowMaskAttenuation = UnitySampleBakedOcclusion(lightmapUV, worldPos);

    half realtimeShadowAttenuation = 1.0f;
    //directional realtime shadow
    #if defined (SHADOWS_SCREEN)
        #if defined(UNITY_NO_SCREENSPACE_SHADOWS) && !defined(UNITY_HALF_PRECISION_FRAGMENT_SHADER_REGISTERS)
            realtimeShadowAttenuation = unitySampleShadow(mul(unity_WorldToShadow[0], unityShadowCoord4(worldPos, 1)));
        #else
            //Only reached when LIGHTMAP_ON is NOT defined (and thus we use interpolator for screenPos rather than lightmap UVs). See HANDLE_SHADOWS_BLENDING_IN_GI below.
            realtimeShadowAttenuation = unitySampleShadow(screenPos);
        #endif
    #endif

    #if defined(UNITY_FAST_COHERENT_DYNAMIC_BRANCHING) && defined(SHADOWS_SOFT) && !defined(LIGHTMAP_SHADOW_MIXING)
    //avoid expensive shadows fetches in the distance where coherency will be good
    UNITY_BRANCH
    if (realtimeToBakedShadowFade < (1.0f - 1e-2f))
    {
    #endif

        //spot realtime shadow
        #if (defined (SHADOWS_DEPTH) && defined (SPOT))
            #if !defined(UNITY_HALF_PRECISION_FRAGMENT_SHADER_REGISTERS)
                unityShadowCoord4 spotShadowCoord = mul(unity_WorldToShadow[0], unityShadowCoord4(worldPos, 1));
            #else
                unityShadowCoord4 spotShadowCoord = screenPos;
            #endif
            realtimeShadowAttenuation = UnitySampleShadowmap(spotShadowCoord);
        #endif

        //point realtime shadow
        #if defined (SHADOWS_CUBE)
            realtimeShadowAttenuation = UnitySampleShadowmap(worldPos - _LightPositionRange.xyz);
        #endif

    #if defined(UNITY_FAST_COHERENT_DYNAMIC_BRANCHING) && defined(SHADOWS_SOFT) && !defined(LIGHTMAP_SHADOW_MIXING)
    }
    #endif

    return UnityMixRealtimeAndBakedShadows(realtimeShadowAttenuation, shadowMaskAttenuation, realtimeToBakedShadowFade);
}

return UnityMixRealtimeAndBakedShadows(...)とあるのでこの関数内でシャドウの処理をまとめて行っている。で、フラグメントシェーダーまで巻き戻るとUNITY_LIGHT_ATTENUATION(atten, i, s.posWorld);という記述の中で上記の関数が呼ばれなんやかんや影の処理が行われた結果がフラグメントシェーダー上でfixed atten;変数として宣言された状態になる。このatten変数をフラグメントシェーダーの結果に乗算すれば影が落ちたような状態になる。その後にEmissionを加算すればOK。長ぁ。
余談ですが、StandardシェーダーではFragmentGI()関数にattenを引数として渡していて中でグローバルイルミネーションのライトカラーにattenが乗算されるようになっている。

備考

変数名の制約

UNITY_LIGHT_ATTENUATIONが内部でSV_POSITIONセマンティクスをバインドした変数を使っています。そのコードでは変数名がposとされている前提で書かれているのでそれに合わせる必要があります。

もっと簡潔なReceive Shadow

Standardシェーダーの処理を追って調べていたのでUNITY_LIGHTING_COORDSマクロを使いましたが、ArktoonShaderのコードを追ってみたところ、以下のようにUNITY_LIGHTING_COORDSSHADOW_COORDSに、UNITY_TRANSFER_LIGHTINGTRANSFER_SHADOWにしても影を受けられるようです。

      struct v2f
      {
        float2 uv: TEXCOORD0;
        float4 pos: SV_POSITION;
        float3 worldPos: WORLDPOS;
        //UNITY_LIGHTING_COORDS(3, 4)
        SHADOW_COORDS(3)
      };
      
      sampler2D _MainTex;
      
      v2f vert(appdata v)
      {
        v2f o;
        o.pos = UnityObjectToClipPos(v.vertex);
        o.uv = v.uv;
        o.worldPos = mul(unity_ObjectToWorld, v.vertex);
        //UNITY_TRANSFER_LIGHTING(o, v.uv1);
        TRANSFER_SHADOW(o);
        return o;
      }

たしかに、UNITY_LIGHTING_COORDS_LightCoord_ShadowCoordをまとめて扱っているからシャドウだけに絞ればそれでいいのか。なるほど:bow:

12
7
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
7