Unity Standard Shader 解析(五)之ShadowCaster
一、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;
}