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"
}
相关推荐
努力长头发的程序猿5 小时前
Unity2D当中的A*寻路算法
算法·unity·c#
RReality16 小时前
【Unity Shader URP】Matcap 材质捕捉实战教程
java·ui·unity·游戏引擎·图形渲染·材质
魔士于安16 小时前
unity urp材质球大全
游戏·unity·游戏引擎·材质·贴图·模型
南無忘码至尊19 小时前
Unity学习90天 - 第 6 天 -学习物理 Material + 重力与阻力并实现弹跳球和冰面滑动效果
学习·unity·游戏引擎
mxwin1 天前
Unity 单通道立体渲染(Single Pass Instanced)对 Shader 顶点布局的特殊要求
unity·游戏引擎·shader
魔士于安1 天前
unity 低多边形 无人小村 木质建筑 晾衣架 盆子手推车,桌子椅子,罐子,水井
游戏·unity·游戏引擎·贴图·模型
RReality1 天前
【Unity Shader URP】简易卡通着色(Simple Toon)实战教程
ui·unity·游戏引擎·图形渲染·材质
魔士于安1 天前
unity 骷髅人 连招 武器 刀光 扭曲空气
游戏·unity·游戏引擎·贴图·模型
洛阳吕工1 天前
从 micro-ROS 到 px4_ros2:ROS2 无人机集成开发实战指南
游戏引擎·无人机·cocos2d