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

张开发
2026/5/10 19:25:37 15 分钟阅读
Unity Shader中CastShadows 和 ReceiveShadows 在代码中的区分
这两个功能走的是完全不同的两条路径在这份 Shader 里对应的代码也完全不同。一、Cast Shadows投射阴影—— 由ShadowCasterPass 承担被谁调用URP 在渲染 Shadow Map 时光源视角会遍历场景里所有物体找出 有ShadowCasterPass 且MeshRenderer.Cast Shadows On 的单独跑一遍这个 Pass。对应代码zmx_TFD_CustomToon_Code.shaderLines 82-151Pass{Name ShadowCasterTags { LightMode ShadowCaster }ZWrite OnZTest LEqualColorMask 0Cull BackHLSLPROGRAM...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接收阴影—— 由ForwardPass 里的这段代码承担被谁调用常规渲染流程相机视角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);#elsefloat4 shadowCoord float4(0, 0, 0, 0);#endifLight mainLight GetMainLight(shadowCoord);float lightAtten mainLight.distanceAttenuation * mainLight.shadowAttenuation;return half4((half3)lightAtten, 1.0);}关键点#pragma multi_compile _ _MAIN_LIGHT_SHADOWS ...开启阴影关键字让 URP 知道这个 Shader 想接收阴影TransformWorldToShadowCoord(positionWS)把当前像素的世界坐标变换到 Shadow Map 空间GetMainLight(shadowCoord)内部会调用MainLightRealtimeShadow(shadowCoord)采样 Shadow Map把采样结果放到mainLight.shadowAttenuation0 完全在阴影中1 完全被光照最后lightAtten distanceAttenuation * shadowAttenuation就是接收阴影的结果如果把_MAIN_LIGHT_SHADOWS改成默认关闭 →MAIN_LIGHT_CALCULATE_SHADOWS宏不会定义 →shadowCoord 0→GetMainLight里的shadowAttenuation永远返回 1 → 物体不接收阴影但别人投到它身上的阴影不会显示三、两者的职责对照表行为相关 Pass / 代码关键 API写入 / 读取Cast投出阴影ShadowCasterPassApplyShadowBiasTransformWorldToHClip写入 光源的 Shadow MapReceive接收阴影ForwardPass 的fragTransformWorldToShadowCoordGetMainLight(shadowCoord)读取 已经写好的 Shadow Map四、组合效果CastReceive结果✓✓能投阴影到别人身上自己也显示别人/自己投过来的阴影默认、最常见✓✗像完美白纸 —— 挡光能让地面有影子但自己身上没有影子✗✓自己能显示影子例如地板但自己不挡光角色看不到它的投影✗✗和阴影系统完全无关比如自发光特效片Unity Inspector 上Mesh Renderer的两个勾Cast Shadows On/Off→ 控制 URP 要不要调用你的ShadowCasterPassReceive 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 }

更多文章