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

这两个功能走的是完全不同的两条路径,在这份 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 = 0GetMainLight 里的 shadowAttenuation 永远返回 1 → 物体不接收阴影(但别人投到它身上的阴影不会显示)

三、两者的职责对照表

行为 相关 Pass / 代码 关键 API 写入 / 读取
Cast(投出阴影) ShadowCaster Pass ApplyShadowBias + TransformWorldToHClip 写入 光源的 Shadow Map
Receive(接收阴影) Forward Pass 的 frag TransformWorldToShadowCoord + GetMainLight(shadowCoord) 读取 已经写好的 Shadow Map

四、组合效果

Cast Receive 结果
能投阴影到别人身上,自己也显示别人/自己投过来的阴影(默认、最常见)
像"完美白纸" ------ 挡光能让地面有影子,但自己身上没有影子
自己能显示影子(例如地板),但自己不挡光(角色看不到它的投影)
和阴影系统完全无关(比如自发光特效片)

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 这个硬边阴影值单独提取出来再做处理。

cs 复制代码
// 从 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"
}
相关推荐
ellis19702 小时前
Unity中使用Cursor辅助开发
unity
avi91114 小时前
Unity商业插件之(三) Editor扩展,二次开发
unity·单例·editor扩展·editor开发
winlife_5 小时前
让 AI 自动跑 PlayMode 回归测试:从 BUG 注入到自动判 FAIL 的完整闭环
人工智能·unity·bug·ai编程·mcp·回归测试·游戏测试
WarPigs21 小时前
游戏签到系统
unity
小拉达不是臭老鼠1 天前
Unity中的UI系统之UGUI
学习·ui·unity
万兴丶1 天前
Coplay适用于 Unity 的“Al 代理”使用指南
unity·游戏引擎·ai编程
魔士于安1 天前
Unity材质球大合集
unity·游戏引擎·材质
mxwin1 天前
Unity Shader 冰面 Shader 制作原理与流程
unity·游戏引擎·shader
玖玥拾1 天前
Cocos学习笔记:关卡系统、音频管理与物理控制
游戏引擎·cocos2d
小拉达不是臭老鼠1 天前
Unity中的UI系统之UGUI_登陆面板实现
ui·unity