Unity Standard Shader 解析(五)之ShadowCaster

一、ShadowCaster

csharp 复制代码
// ------------------------------------------------------------------
        //  Shadow rendering pass
        Pass {
            Name "ShadowCaster"
            Tags { "LightMode" = "ShadowCaster" }

            ZWrite On ZTest LEqual

            CGPROGRAM
            #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中的vertShadowCasterfragShadowCaster

以下是UnityStandardCoreForward.cginc的源码,

二、vertShadowCaster

csharp 复制代码
// 我们必须分别在顶点着色器中输出 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_STRUCT
        UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(os);
    #endif

    // 将顶点数据转换为阴影投射所需的格式,并输出 SV_POSITION
    TRANSFER_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

csharp 复制代码
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_WORLD
    half4 tangent   : TANGENT;
#endif
    UNITY_VERTEX_INPUT_INSTANCE_ID
};

2.VertexOutputShadowCaster

csharp 复制代码
#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

csharp 复制代码
#ifdef UNITY_STANDARD_USE_STEREO_SHADOW_OUTPUT_STRUCT
struct VertexOutputStereoShadowCaster
{
    UNITY_VERTEX_OUTPUT_STEREO
};
#endif

4.TRANSFER_SHADOW_CASTER_NOPOS

csharp 复制代码
#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

csharp 复制代码
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

csharp 复制代码
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));
    #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;
}

三、fragShadowCaster

csharp 复制代码
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_ALPHA
                    alpha *= unity_LODFade.y; // 应用LOD淡入因子
                #endif
                // 计算alpha参考值
                half alphaRef = tex3D(_DitherMaskLOD, float3(vpos.xy * 0.25, alpha * 0.9375)).a;
                clip(alphaRef - 0.01); // 裁剪掉不满足条件的部分
            #else
                clip(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

csharp 复制代码
// 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

csharp 复制代码
#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

csharp 复制代码
// 定义一个采样器,用于访问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

csharp 复制代码
//_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

csharp 复制代码
float4 UnityEncodeCubeShadowDepth (float z)
{
    #ifdef UNITY_USE_RGBA_FOR_POINT_SHADOWS
    return EncodeFloatRGBA (min(z, 0.999));
    #else
    return z;
    #endif
}

6.EncodeFloatRGBA

csharp 复制代码
// 函数:将 [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^3
    float4 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;
}
相关推荐
我想_iwant8 小时前
android集成unity后动态导入 assetsBundle
android·unity·游戏引擎
EQ-雪梨蛋花汤13 小时前
【踩坑记录】Unity 项目中 PlasticSCM 掩蔽列表引发的 文件缺失问题排查与解决
unity·游戏引擎
Thinbug17 小时前
Unity 枪械红点瞄准器计算
unity·游戏引擎
我想_iwant18 小时前
unity中的交互控制脚本
数码相机·unity·交互
龚子亦19 小时前
【Unity开发】热更新学习——AssetBundle
学习·unity·游戏引擎
萘柰奈20 小时前
Unity学习----【数据持久化】二进制数据(五)--由Excel自动生成数据结构类与二进制文件
数据结构·学习·unity
心前阳光21 小时前
Unity通过Object学习原型模式
学习·unity·原型模式
SmalBox1 天前
【URP】[投影Projector]解析与应用
unity·渲染