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 = 0 → GetMainLight 里的 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"
}
相关推荐
辻弋2012 天前
五年前的旅行视频糊成马赛克?Video2X用Real-ESRGAN逐帧重建细节,但只支持Windows、集显用户建议直接放弃
服务器·数据库·windows·游戏引擎·电脑
Martina_03212 天前
AI生成3D场景导入 Unity/Unreal 后,NPC 总串层?用6步检查导航高度与跨层连接
人工智能·游戏·数学建模·3d·unity·自然语言处理·aigc
小贺儿开发2 天前
Unity 局域网遥控幻灯片展示工具 3.0
科技·http·unity·工具·ppt·通信·网页
WarPigs4 天前
Unity对象不应该使用null传播的问题
unity
每日出拳老爷子5 天前
【VR】【Unity】抓走转放系列21|一天搭完门和抽屉工具槽:小场景按这个次序拼装
unity·vr·驱动·排错·snap zone
淡海水6 天前
02-02-原理篇-分代GC
算法·unity·c#·游戏引擎·.net·gc
小贺儿开发6 天前
Unity 文物新生 会讲故事的文物展墙
人工智能·科技·unity·ai·视频·互动·演示
淡海水6 天前
02-03-原理篇-大对象堆
unity·c#·游戏引擎·.net·gc
WarPigs7 天前
Unity在Create菜单添加创建自定义脚本模板选项
unity
郝学胜-神的一滴7 天前
C++ Templates 01:拨开迷雾,读懂C++模板经典著作
开发语言·c++·程序人生·游戏·游戏引擎·软件构建