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"
}
相关推荐
阿松爱学习12 分钟前
【Unity开发】Rigidbody中Body Type属性
unity·游戏引擎·unity开发
winlife_33 分钟前
AI 怎么验证 Unity PlayMode 行为:截图 + 输入模拟的完整闭环
人工智能·unity·游戏引擎·ai编程·claude·playmode
CandyU23 小时前
Cursor AI Unity
unity
LF男男3 小时前
Bullect.cs(bullet)——子弹基类
unity
mxwin20 小时前
unity shader中 ddx ddy是什么
unity·游戏引擎·shader
郝学胜-神的一滴1 天前
[简化版 GAMES 101] 计算机图形学 08:三角形光栅化上
c++·unity·游戏引擎·godot·图形渲染·opengl·unreal
nnsix1 天前
Unity ILRuntime 笔记
unity·游戏引擎
nnsix1 天前
Unity API 兼容的 .NET Standard 2.1 和 .NET Framework 区别
unity·游戏引擎·.net
mxwin1 天前
Unity Shader 制作半透明物体 使用多Pass提前写入深度的方式 避免穿模
unity·游戏引擎
nnsix1 天前
Unity HybridCLR 笔记
笔记·unity·游戏引擎