Unity Shader中CastShadows 和 ReceiveShadows 在代码中的区分

开发板推荐:天空星STM32F407VET6开发板

超高性价比 STM32主控 | 超高主频 | 一板兼容百芯 | 比赛神器 | 沉金彩色丝印

这两个功能走的是完全不同的两条路径,在这份 Shader 里对应的代码也完全不同。

一、Cast Shadows(投射阴影)—— 由 ShadowCaster Pass 承担

被谁调用:URP 在渲染 Shadow Map 时(光源视角),会遍历场景里所有物体,找出 "有 ShadowCaster Pass 且 MeshRenderer.Cast Shadows = On" 的,单独跑一遍这个 Pass。

对应代码:

zmx_TFD_CustomToon_Code.shaderLines 82-151

Pass

{

Name "ShadowCaster"

Tags { "LightMode" = "ShadowCaster" }

ZWrite On

ZTest LEqual

ColorMask 0

Cull Back

HLSLPROGRAM

...

float4 GetShadowPositionHClip(ShadowAttributes IN)

{

float3 positionWS = TransformObjectToWorld(IN.positionOS.xyz);

float3 normalWS = TransformObjectToWorldNormal(IN.normalOS);

...

float4 positionCS = TransformWorldToHClip(ApplyShadowBias(positionWS, normalWS, lightDirWS));

...

}

...

half4 shadowFrag(ShadowVaryings IN) : SV_Target

{

return 0;

}

ENDHLSL

}

关键点:

  • 这个 Pass 写入的是光源视角的 Shadow Map 纹理
  • ColorMask 0 不输出颜色,return 0 的片段输出也不重要,只要 ZWrite On 把深度写对就行
  • ApplyShadowBias 是为了减少自阴影 acne
  • 如果删掉这个 Pass → 物体不会出现在 Shadow Map 里 → 它投不出阴影,但依然会在 Forward Pass 里正常渲染

二、Receive Shadows(接收阴影)—— 由 Forward Pass 里的这段代码承担

被谁调用:常规渲染流程(相机视角),Forward Pass 采样别人写好的 Shadow Map,判断当前像素是否被挡住。

对应代码:

zmx_TFD_CustomToon_Code.shaderLines 37-76

#pragma multi_compile _ _MAIN_LIGHT_SHADOWS _MAIN_LIGHT_SHADOWS_CASCADE _MAIN_LIGHT_SHADOWS_SCREEN

#pragma multi_compile_fragment _ _SHADOWS_SOFT

...

half4 frag(Varyings IN) : SV_Target

{

#if defined(MAIN_LIGHT_CALCULATE_SHADOWS)

float4 shadowCoord = TransformWorldToShadowCoord(IN.positionWS);

#else

float4 shadowCoord = float4(0, 0, 0, 0);

#endif

Light mainLight = GetMainLight(shadowCoord);

float lightAtten = mainLight.distanceAttenuation * mainLight.shadowAttenuation;

return half4((half3)lightAtten, 1.0);

}

关键点:

  1. #pragma multi_compile _ _MAIN_LIGHT_SHADOWS ...:开启阴影关键字,让 URP 知道这个 Shader 想接收阴影
  2. TransformWorldToShadowCoord(positionWS):把当前像素的世界坐标变换到 Shadow Map 空间
  3. GetMainLight(shadowCoord):内部会调用 MainLightRealtimeShadow(shadowCoord) 采样 Shadow Map,把采样结果放到 mainLight.shadowAttenuation(0 = 完全在阴影中,1 = 完全被光照)
  4. 最后 lightAtten = distanceAttenuation * shadowAttenuation 就是"接收阴影"的结果

如果把 _MAIN_LIGHT_SHADOWS 改成默认关闭 → MAIN_LIGHT_CALCULATE_SHADOWS 宏不会定义 → shadowCoord = 0 → GetMainLight 里的 shadowAttenuation 永远返回 1 → 物体不接收阴影(但别人投到它身上的阴影不会显示)

三、两者的职责对照表

行为相关 Pass / 代码关键 API写入 / 读取

Cast(投出阴影)

ShadowCaster Pass

ApplyShadowBias + TransformWorldToHClip

写入 光源的 Shadow Map

Receive(接收阴影)

Forward Pass 的 frag

TransformWorldToShadowCoord + GetMainLight(shadowCoord)

读取 已经写好的 Shadow Map

四、组合效果

CastReceive结果

能投阴影到别人身上,自己也显示别人/自己投过来的阴影(默认、最常见)

像"完美白纸" —— 挡光能让地面有影子,但自己身上没有影子

自己能显示影子(例如地板),但自己不挡光(角色看不到它的投影)

和阴影系统完全无关(比如自发光特效片)

Unity Inspector 上 Mesh Renderer 的两个勾:

  • Cast Shadows = On/Off → 控制 URP 要不要调用你的 ShadowCaster Pass
  • Receive Shadows = On/Off → 控制 URP 要不要给你定义 _MAIN_LIGHT_SHADOWS 关键字

五、在你当前这份 Shader 里的体验

因为这个 Shader 的输出就是 lightAttenuation.xxx 灰度:

  • Receive Shadows 的结果你能直接肉眼看见:被阴影遮住的地方 shadowAttenuation = 0,物体表面就是黑色;被照亮的地方是白色。整个物体像一张可视化的"阴影热力图"。
  • Cast Shadows 的结果你要在别的物体上才能看到:比如把这个物体放到地面上方,地面上会出现它的影子。

这也是为什么它叫"Toon"——典型卡通渲染里常需要把 shadowAttenuation 这个硬边阴影值单独提取出来再做处理。

// 从 ASE 版本 zmx_TFD_CustomToon.shader 翻写的手写 URP Unlit Shader
//
// ASE 节点图等价表达:
//     LightAttenuation --> MasterNode(Forward).Color
// 即:
//     Color.rgb = (mainLight.distanceAttenuation * mainLight.shadowAttenuation).xxx
Shader "Toon/zmx_TFD_CustomToon_Code"
{
    Properties
    {
    }

    SubShader
    {
        Tags
        {
            "RenderPipeline"        = "UniversalPipeline"
            "RenderType"            = "Opaque"
            "Queue"                 = "Geometry"
            "UniversalMaterialType" = "Unlit"
        }
        LOD 100

        Pass
        {
            Name "Forward"
            Tags { "LightMode" = "UniversalForwardOnly" }

            ZWrite On
            ZTest LEqual
            Cull Back

            HLSLPROGRAM
            #pragma vertex   vert
            #pragma fragment frag

            #pragma multi_compile _ _MAIN_LIGHT_SHADOWS _MAIN_LIGHT_SHADOWS_CASCADE _MAIN_LIGHT_SHADOWS_SCREEN
            #pragma multi_compile_fragment _ _SHADOWS_SOFT

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Shadows.hlsl"

            struct Attributes
            {
                float4 positionOS : POSITION;
            };

            struct Varyings
            {
                float4 positionCS : SV_POSITION;
                float3 positionWS : TEXCOORD0;
            };

            CBUFFER_START(UnityPerMaterial)
            CBUFFER_END

            Varyings vert(Attributes IN)
            {
                Varyings OUT;
                VertexPositionInputs posInputs = GetVertexPositionInputs(IN.positionOS.xyz);
                OUT.positionCS = posInputs.positionCS;
                OUT.positionWS = posInputs.positionWS;
                return OUT;
            }

            half4 frag(Varyings IN) : SV_Target
            {
                #if defined(MAIN_LIGHT_CALCULATE_SHADOWS)
                    float4 shadowCoord = TransformWorldToShadowCoord(IN.positionWS);
                #else
                    float4 shadowCoord = float4(0, 0, 0, 0);
                #endif

                Light mainLight  = GetMainLight(shadowCoord);
                float lightAtten = mainLight.distanceAttenuation * mainLight.shadowAttenuation;

                return half4((half3)lightAtten, 1.0);
            }
            ENDHLSL
        }

        // ShadowCaster: 让物体自身能投射阴影到其它物体的 Shadow Map 上
        Pass
        {
            Name "ShadowCaster"
            Tags { "LightMode" = "ShadowCaster" }

            ZWrite On
            ZTest LEqual
            ColorMask 0
            Cull Back

            HLSLPROGRAM
            #pragma vertex   shadowVert
            #pragma fragment shadowFrag

            #pragma multi_compile_vertex _ _CASTING_PUNCTUAL_LIGHT_SHADOW

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Shadows.hlsl"

            // URP ShadowCaster 需要这两个由引擎设置的变量:
            // _LightDirection:主方向光 / Spot 的方向(世界空间)
            // _LightPosition :点光源的位置(仅 Punctual 阴影使用)
            float3 _LightDirection;
            float3 _LightPosition;

            struct ShadowAttributes
            {
                float4 positionOS : POSITION;
                float3 normalOS   : NORMAL;
            };

            struct ShadowVaryings
            {
                float4 positionCS : SV_POSITION;
            };

            float4 GetShadowPositionHClip(ShadowAttributes IN)
            {
                float3 positionWS = TransformObjectToWorld(IN.positionOS.xyz);
                float3 normalWS   = TransformObjectToWorldNormal(IN.normalOS);

                #if _CASTING_PUNCTUAL_LIGHT_SHADOW
                    float3 lightDirWS = normalize(_LightPosition - positionWS);
                #else
                    float3 lightDirWS = _LightDirection;
                #endif

                float4 positionCS = TransformWorldToHClip(ApplyShadowBias(positionWS, normalWS, lightDirWS));

                // 防止阴影偏移后越过近裁剪面
                #if UNITY_REVERSED_Z
                    positionCS.z = min(positionCS.z, UNITY_NEAR_CLIP_VALUE);
                #else
                    positionCS.z = max(positionCS.z, UNITY_NEAR_CLIP_VALUE);
                #endif
                return positionCS;
            }

            ShadowVaryings shadowVert(ShadowAttributes IN)
            {
                ShadowVaryings OUT;
                OUT.positionCS = GetShadowPositionHClip(IN);
                return OUT;
            }

            half4 shadowFrag(ShadowVaryings IN) : SV_Target
            {
                return 0;
            }
            ENDHLSL
        }

        // DepthOnly: 为需要 _CameraDepthTexture 的效果(SSAO / 软粒子 / 后处理等)提供深度
        Pass
        {
            Name "DepthOnly"
            Tags { "LightMode" = "DepthOnly" }

            ZWrite On
            ColorMask 0
            Cull Back

            HLSLPROGRAM
            #pragma vertex   depthVert
            #pragma fragment depthFrag

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

            struct DepthAttributes
            {
                float4 positionOS : POSITION;
            };

            struct DepthVaryings
            {
                float4 positionCS : SV_POSITION;
            };

            DepthVaryings depthVert(DepthAttributes IN)
            {
                DepthVaryings OUT;
                OUT.positionCS = TransformObjectToHClip(IN.positionOS.xyz);
                return OUT;
            }

            half4 depthFrag(DepthVaryings IN) : SV_Target
            {
                return 0;
            }
            ENDHLSL
        }
    }

    FallBack "Hidden/Universal Render Pipeline/FallbackError"
}

开发板推荐:天空星STM32F407VET6开发板

超高性价比 STM32主控 | 超高主频 | 一板兼容百芯 | 比赛神器 | 沉金彩色丝印

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值