一、ShadowCaster
// ------------------------------------------------------------------// Shadow rendering passPass {Name "ShadowCaster"Tags { "LightMode" = "ShadowCaster" }ZWrite On ZTest LEqualCGPROGRAM#pragma target 3.0// -------------------------------------#pragma shader_feature_local _ _ALPHATEST_ON _ALPHABLEND_ON _ALPHAPREMULTIPLY_ON#pragma shader_feature_local _METALLICGLOSSMAP#pragma shader_feature_local_fragment _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A#pragma shader_feature_local _PARALLAXMAP#pragma multi_compile_shadowcaster#pragma multi_compile_instancing// Uncomment the following line to enable dithering LOD crossfade. Note: there are more in the file to uncomment for other passes.//#pragma multi_compile _ LOD_FADE_CROSSFADE#pragma vertex vertShadowCaster#pragma fragment fragShadowCaster#include "UnityStandardShadow.cginc"ENDCG}
引用了UnityStandardShadow.cginc
中的vertShadowCaster
和fragShadowCaster
以下是UnityStandardCoreForward.cginc的源碼,
二、vertShadowCaster
// 我們必須分別在頂點著色器中輸出 SV_POSITION,并在像素著色器中輸入 VPOS,
// 因為它們在某些平臺上都映射到 "POSITION" 語義,這樣會導致問題。void vertShadowCaster (VertexInput v, out float4 opos : SV_POSITION#ifdef UNITY_STANDARD_USE_SHADOW_OUTPUT_STRUCT, out VertexOutputShadowCaster o#endif#ifdef UNITY_STANDARD_USE_STEREO_SHADOW_OUTPUT_STRUCT, out VertexOutputStereoShadowCaster os#endif
)
{// 設置實例ID,以便在多實例渲染時正確處理每個實例的數據UNITY_SETUP_INSTANCE_ID(v);// 如果啟用了立體陰影輸出結構,則初始化立體陰影輸出結構#ifdef UNITY_STANDARD_USE_STEREO_SHADOW_OUTPUT_STRUCTUNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(os);#endif// 將頂點數據轉換為陰影投射所需的格式,并輸出 SV_POSITIONTRANSFER_SHADOW_CASTER_NOPOS(o, opos)// 如果啟用了陰影UVs#if defined(UNITY_STANDARD_USE_SHADOW_UVS)// 將紋理坐標從對象空間轉換到紋理空間o.tex = TRANSFORM_TEX(v.uv0, _MainTex);// 如果啟用了視差貼圖#ifdef _PARALLAXMAP// 計算切線空間旋轉矩陣TANGENT_SPACE_ROTATION;// 計算視差貼圖所需的視圖方向o.viewDirForParallax = mul(rotation, ObjSpaceViewDir(v.vertex));#endif#endif
}
1.VertexInput
struct VertexInput
{float4 vertex : POSITION;half3 normal : NORMAL;float2 uv0 : TEXCOORD0;float2 uv1 : TEXCOORD1;
#if defined(DYNAMICLIGHTMAP_ON) || defined(UNITY_PASS_META)float2 uv2 : TEXCOORD2;
#endif
#ifdef _TANGENT_TO_WORLDhalf4 tangent : TANGENT;
#endifUNITY_VERTEX_INPUT_INSTANCE_ID
};
2.VertexOutputShadowCaster
#ifdef UNITY_STANDARD_USE_SHADOW_OUTPUT_STRUCT
struct VertexOutputShadowCaster
{V2F_SHADOW_CASTER_NOPOS#if defined(UNITY_STANDARD_USE_SHADOW_UVS)float2 tex : TEXCOORD1;#if defined(_PARALLAXMAP)half3 viewDirForParallax : TEXCOORD2;#endif#endif
};
#endif
3.VertexOutputStereoShadowCaster
#ifdef UNITY_STANDARD_USE_STEREO_SHADOW_OUTPUT_STRUCT
struct VertexOutputStereoShadowCaster
{UNITY_VERTEX_OUTPUT_STEREO
};
#endif
4.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
5.UnityClipSpaceShadowCasterPos
float4 UnityClipSpaceShadowCasterPos(float4 vertex, float3 normal)
{// 將頂點從對象空間轉換到世界空間float4 wPos = mul(unity_ObjectToWorld, vertex);// 如果啟用了法線偏置(normal bias)if (unity_LightShadowBias.z != 0.0){// 將法線從對象空間轉換到世界空間float3 wNormal = UnityObjectToWorldNormal(normal);// 獲取世界空間中的光照方向float3 wLight = normalize(UnityWorldSpaceLightDir(wPos.xyz));// 應用法線偏置(沿法線向內偏移位置)// 偏置需要按法線和光照方向之間的正弦值進行縮放// (http://the-witness.net/news/2013/09/shadow-mapping-summary-part-1/)//// unity_LightShadowBias.z 包含用戶指定的法線偏置量,// 已按世界空間紋理像素大小進行了縮放。// 計算法線和光照方向之間的余弦值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);
}// Legacy, not used anymore; kept around to not break existing user shaders
float4 UnityClipSpaceShadowCasterPos(float3 vertex, float3 normal)
{// 調用帶 float4 參數的重載函數return UnityClipSpaceShadowCasterPos(float4(vertex, 1), normal);
}
6.UnityApplyLinearShadowBias
float4 UnityApplyLinearShadowBias(float4 clipPos)
{// 對于支持深度立方體貼圖的點光源,偏置在采樣陰影貼圖的片段著色器中應用。// 這是因為傳統行為的點光源陰影貼圖無法通過在生成陰影貼圖的頂點著色器中偏移頂點位置來實現。
#if !(defined(SHADOWS_CUBE) && defined(SHADOWS_CUBE_IN_DEPTH_TEX))#if defined(UNITY_REVERSED_Z)// 使用 max/min 而不是 clamp 以確保正確處理極少數情況下分子和分母都為零的情況,// 此時分數會變成 NaN。clipPos.z += max(-1, min(unity_LightShadowBias.x / clipPos.w, 0));#elseclipPos.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);
#elsefloat clamped = max(clipPos.z, clipPos.w * UNITY_NEAR_CLIP_VALUE);
#endifclipPos.z = lerp(clipPos.z, clamped, unity_LightShadowBias.y);return clipPos;
}
三、fragShadowCaster
half4 fragShadowCaster (UNITY_POSITION(vpos)
#ifdef UNITY_STANDARD_USE_SHADOW_OUTPUT_STRUCT, VertexOutputShadowCaster i
#endif
) : SV_Target
{// 如果啟用了陰影UVs#if defined(UNITY_STANDARD_USE_SHADOW_UVS)// 如果啟用了視差貼圖且著色器目標版本大于等于3.0#if defined(_PARALLAXMAP) && (SHADER_TARGET >= 30)// 歸一化視差貼圖的視圖方向half3 viewDirForParallax = normalize(i.viewDirForParallax);// 獲取視差貼圖的高度值(綠色通道)fixed h = tex2D(_ParallaxMap, i.tex.xy).g;// 計算視差偏移量half2 offset = ParallaxOffset1Step(h, _Parallax, viewDirForParallax);// 應用視差偏移量到紋理坐標i.tex.xy += offset;#endif// 如果平滑度存儲在Albedo通道A中#if defined(_SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A)half alpha = _Color.a;#else// 否則,從主紋理中獲取Alpha值并乘以顏色的Alpha值half alpha = tex2D(_MainTex, i.tex.xy).a * _Color.a;#endif// 如果啟用了Alpha測試#if defined(_ALPHATEST_ON)clip(alpha - _Cutoff); // 裁剪掉透明度低于閾值的部分#endif// 如果啟用了Alpha混合或Alpha預乘#if defined(_ALPHABLEND_ON) || defined(_ALPHAPREMULTIPLY_ON)// 如果啟用了Alpha預乘#if defined(_ALPHAPREMULTIPLY_ON)half outModifiedAlpha;// 預乘Alpha值PreMultiplyAlpha(half3(0, 0, 0), alpha, SHADOW_ONEMINUSREFLECTIVITY(i.tex), outModifiedAlpha);alpha = outModifiedAlpha;#endif// 如果啟用了抖動掩碼進行Alpha混合陰影#if defined(UNITY_STANDARD_USE_DITHER_MASK)// 基于像素位置xy和alpha級別使用抖動掩碼// 我們的抖動紋理是4x4x16。#ifdef LOD_FADE_CROSSFADE#define _LOD_FADE_ON_ALPHAalpha *= unity_LODFade.y; // 應用LOD淡入因子#endif// 計算alpha參考值half alphaRef = tex3D(_DitherMaskLOD, float3(vpos.xy * 0.25, alpha * 0.9375)).a;clip(alphaRef - 0.01); // 裁剪掉不滿足條件的部分#elseclip(alpha - _Cutoff); // 裁剪掉透明度低于閾值的部分#endif#endif#endif // #if defined(UNITY_STANDARD_USE_SHADOW_UVS)// 如果啟用了LOD交叉漸變#ifdef LOD_FADE_CROSSFADE#ifdef _LOD_FADE_ON_ALPHA#undef _LOD_FADE_ON_ALPHA#else// 應用LOD交叉漸變UnityApplyDitherCrossFade(vpos.xy);#endif#endif// 輸出陰影片段SHADOW_CASTER_FRAGMENT(i)
}
1.ParallaxOffset1Step
// Same as ParallaxOffset in Unity CG, except:
// *) precision - half instead of float
half2 ParallaxOffset1Step (half h, half height, half3 viewDir)
{h = h * height - height/2.0;half3 v = normalize(viewDir);v.z += 0.42;return h * (v.xy / v.z);
}
2.SHADOW_ONEMINUSREFLECTIVITY
#define SHADOW_ONEMINUSREFLECTIVITY SHADOW_JOIN(UNITY_SETUP_BRDF_INPUT, _ShadowGetOneMinusReflectivity)
#define SHADOW_JOIN(a, b) SHADOW_JOIN2(a,b)
#define SHADOW_JOIN2(a, b) a##b
3.UnityApplyDitherCrossFade
// 定義一個采樣器,用于訪問4x4的抖動遮罩紋理
sampler2D unity_DitherMask;/*** 應用基于抖動遮罩的交叉淡入淡出效果* @param vpos 輸入頂點位置,通常是屏幕空間坐標或裁剪空間坐標*/
void UnityApplyDitherCrossFade(float2 vpos)
{// 將輸入的位置除以4,因為抖動遮罩紋理是4x4的分辨率// 這一步是為了將輸入坐標映射到遮罩紋理的UV坐標空間vpos /= 4.0;// 使用tex2D函數從抖動遮罩紋理中采樣,并獲取alpha通道的值// mask的值范圍在[0, 1]之間float mask = tex2D(unity_DitherMask, vpos).a;// 根據unity_LODFade.x的值確定sgn的符號// 如果unity_LODFade.x大于0,則sgn為1.0f,否則為-1.0f// unity_LODFade.x通常用于表示LOD級別的變化方向float sgn = unity_LODFade.x > 0 ? 1.0f : -1.0f;// 使用clip函數進行像素裁剪// 如果unity_LODFade.x - mask * sgn小于0,則該像素會被裁剪掉(即不會被渲染)// 這一步實現了基于抖動遮罩的交叉淡入淡出效果clip(unity_LODFade.x - mask * sgn);
}
4.SHADOW_CASTER_FRAGMENT
//_LightPositionRange 是 float4 類型的內置變量:
//xyz分量?:表示光源在世界空間中的坐標位置。
//w分量?:表示光源的影響范圍(如點光源的半徑)。
//unity_LightShadowBias 是 float4 類型的內置變量,?
?//x 分量?:shadowBias,表示常量偏移值,用于調整頂點沿光源方向的偏移距離。
//y 分量?:shadowNormalBias,表示基于法線的偏移值,用于沿頂點法線方向內推頂點位置。
//z和w 分量?:通常與光源類型或特定計算需求相關(如點光源的歸一化參數)?。
#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
5.UnityEncodeCubeShadowDepth
float4 UnityEncodeCubeShadowDepth (float z)
{#ifdef UNITY_USE_RGBA_FOR_POINT_SHADOWSreturn EncodeFloatRGBA (min(z, 0.999));#elsereturn z;#endif
}
6.EncodeFloatRGBA
// 函數:將 [0, 1) 范圍內的浮點數編碼為 8 位每通道的 RGBA 顏色值
// 注意:1.0 值不會被正確編碼
inline float4 EncodeFloatRGBA(float v)
{// 定義乘法因子,用于將浮點數分解到四個通道中// kEncodeMul 的值分別是:// 1.0 -> 第一個通道(R)// 255.0 -> 第二個通道(G)// 65025.0 -> 第三個通道(B),即 255^2// 16581375.0-> 第四個通道(A),即 255^3float4 kEncodeMul = float4(1.0, 255.0, 65025.0, 16581375.0);// 定義用于減去溢出部分的常量,即每個通道的最大值(255)的倒數float kEncodeBit = 1.0 / 255.0;// 將輸入浮點數 v 分解到四個通道中float4 enc = kEncodeMul * v;// 取每個分量的小數部分,確保每個分量都在 [0, 1) 范圍內enc = frac(enc);// 減去高位通道對低位通道的溢出影響// enc.yzww 表示取 G、B 和 A 通道的值,并分別乘以 kEncodeBit// 這樣可以避免高位通道的值影響低位通道enc -= enc.yzww * kEncodeBit;// 返回編碼后的 RGBA 顏色值return enc;
}