影の書き込みに必要な記述を追う
Shadowのバッファーへの書き込みと、影を受ける側の処理を把握する為にビルトインシェーダーの記述を追ってみる。2019.3系のビルトインシェーダーを参考にします。
ひとまず記述が簡潔そうなVertexLitの影の書き込みを見てみる。
// 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
はこう定義されている。
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キーワード次第
};
###マクロを読む
こんなマクロになっている。
// 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_NOPOS
とUNITY_POSITION(pos)
をそれぞれ確認してみる。
まず簡単なUNITY_POSITION(pos)
。これはときどき見かける。
// On D3D reading screen space coordinates from fragment shader requires SM3.0
#define UNITY_POSITION(pos) float4 pos : SV_POSITION
単純にクリップ座標を入れる変数を宣言しているだけ。
次にV2F_SHADOW_CASTER_NOPOS
。
#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;
が構造体に追加されるという事になりそう。
#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;
}
マクロを読む
マクロはこうなっている
// 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
を確認してみます。
#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()
でなにやら計算してますね。このふたつの関数を追ってみます。
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()
で出力された法線方向にオフセットされたクリップ座標が引数にくることが分かっています。
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)
はフラグメントシェーダーで実行されているのでこのマクロが最終的な深度値にあたる値を出力していそうな予想はできる。
#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になるのかもしれない。
//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)
マクロはその為のマクロで以下のように定義されている。
#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を使いたいとかそういうことなのかな。あんまりその辺理解していないのでよく分からず...
考えやすい様にマクロで使用されている変数名は書き換えてますが、定義は以下のようになっています。
#define UNITY_TRANSFER_LIGHTING(o, coord) COMPUTE_LIGHT_COORDS(o) UNITY_TRANSFER_SHADOW(o, coord)
COMPUTE_LIGHT_COORDS(o)
はライトの種類で分岐してる。が、基本的にライトをカメラとして考えてカメラから見たクリップ座標のような座標を算出している感じになっている。例えば以下のような感じ。厳密にどういう変換をしているかはunity_WorldToLight
行列次第。
#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()
関数が呼ばれている。
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_COORDS
をSHADOW_COORDS
に、UNITY_TRANSFER_LIGHTING
をTRANSFER_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
をまとめて扱っているからシャドウだけに絞ればそれでいいのか。なるほど