源码 / Ch23/PlanarShadow.shader
Ch23/PlanarShadow.shader
// 案例 23.3:平面投影阴影(Planar Shadow)。 // 第一个 Pass 正常着色;第二个 Pass(SRPDefaultUnlit)把同一个网格沿主光方向“拍扁”到地面上, // 输出半透明的阴影色,并用模板测试保证同一个像素只被阴影覆盖一次,避免重叠部分变黑。 Shader "Cookbook/Ch23/PlanarShadow" { Properties { [MainColor] _BaseColor ("Base Color", Color) = (0.9, 0.6, 0.3, 1) _ShadowColor ("Shadow Color", Color) = (0.05, 0.06, 0.1, 0.65) _PlaneHeight ("Plane Height (世界空间 y)", Float) = 0 _ShadowFalloff ("Height Falloff", Range(0, 2)) = 0.35 [Enum(UnityEngine.Rendering.CompareFunction)] _StencilComp ("Stencil Comp", Float) = 6 // 6 = NotEqual } SubShader { // 排在普通不透明物体之后,保证地面已经画好,阴影才能混合到地面颜色上 Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" "Queue"="Geometry+10" } HLSLINCLUDE #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" CBUFFER_START(UnityPerMaterial) half4 _BaseColor; half4 _ShadowColor; float _PlaneHeight; float _ShadowFalloff; CBUFFER_END ENDHLSL Pass { Name "ForwardLit" Tags { "LightMode"="UniversalForward" } HLSLPROGRAM #pragma vertex vert #pragma fragment frag #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl" struct Attributes { float4 positionOS : POSITION; float3 normalOS : NORMAL; }; struct Varyings { float4 positionCS : SV_POSITION; half3 normalWS : TEXCOORD0; }; Varyings vert(Attributes v) { Varyings o; o.positionCS = TransformObjectToHClip(v.positionOS.xyz); o.normalWS = TransformObjectToWorldNormal(v.normalOS); return o; } half4 frag(Varyings i) : SV_Target { half3 n = normalize(i.normalWS); Light l = GetMainLight(); half3 c = _BaseColor.rgb * (l.color * saturate(dot(n, l.direction)) + SampleSH(n)); return half4(c, 1); } ENDHLSL } Pass { Name "PlanarShadow" Tags { "LightMode"="SRPDefaultUnlit" } // URP 会把它和 UniversalForward 一起画 Stencil { Ref 1 Comp [_StencilComp] // NotEqual:这个像素还没画过阴影才画 Pass Replace // 画过就把模板写成 1,后面的阴影三角形再来就被拒绝 } Blend SrcAlpha OneMinusSrcAlpha ZWrite Off Cull Back HLSLPROGRAM #pragma vertex vert #pragma fragment frag struct Attributes { float4 positionOS : POSITION; }; struct Varyings { float4 positionCS : SV_POSITION; half fade : TEXCOORD0; }; Varyings vert(Attributes v) { Varyings o; float3 positionWS = TransformObjectToWorld(v.positionOS.xyz); // _MainLightPosition.xyz 对方向光来说是“指向光源”的单位向量 L float3 L = normalize(_MainLightPosition.xyz); // 求沿 -L 方向走多远能碰到平面 y = h: P' = P - L * (P.y - h) / L.y float height = positionWS.y - _PlaneHeight; float3 projected = positionWS - L * (height / max(L.y, 1e-3)); projected.y = _PlaneHeight + 0.002; // 抬高 2 毫米,避免与地面 Z-Fighting o.positionCS = TransformWorldToHClip(projected); // 离地越高的部位投下的阴影越淡,模拟真实阴影随距离变软变淡 o.fade = saturate(1.0 - height * _ShadowFalloff); return o; } half4 frag(Varyings i) : SV_Target { return half4(_ShadowColor.rgb, _ShadowColor.a * i.fade); } ENDHLSL } Pass { Name "DepthOnly" Tags { "LightMode"="DepthOnly" } ZWrite On ColorMask R HLSLPROGRAM #pragma vertex Ch23DepthOnlyVert #pragma fragment Ch23NullFrag #include "Assets/Cookbook/Ch23/Ch23Common.hlsl" ENDHLSL } } }