源码 / Ch23/ContactShadowFeature.cs
Ch23/ContactShadowFeature.cs
using UnityEngine; using UnityEngine.Experimental.Rendering; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; namespace Cookbook.Ch23 { /// 案例 23.9:接触阴影(Contact Shadows)。 /// 在深度预渲染之后,用一个全屏 Pass 从每个像素出发、沿主光方向在屏幕空间里步进几步, /// 检查有没有被深度缓冲里更靠前的表面挡住,结果写进一张 R8 的全屏纹理 _ContactShadowTexture。 /// 接收物体的 Shader 再用屏幕 UV 读它,与阴影贴图的结果取最小值。 /// 结构完全仿照 URP 14 自带的 ScreenSpaceShadows(Runtime/RendererFeatures/ScreenSpaceShadows.cs)。 public class ContactShadowFeature : ScriptableRendererFeature { [System.Serializable] public class Settings { [Tooltip("沿光线方向追踪的世界空间长度(米)。接触阴影只负责“贴脚”的那一小段")] public float length = 0.35f; [Range(4, 64)] public int steps = 16; [Tooltip("认为是“挡住”的最大深度差(米),超过它视为在物体背后穿过,不算遮挡")] public float thickness = 0.15f; [Tooltip("起点沿光方向的偏移,防止表面自己挡自己")] public float bias = 0.01f; [Range(0, 1)] public float intensity = 1f; } public Settings settings = new Settings(); const string k_ShaderName = "Cookbook/Ch23/ContactShadowPass"; Material m_Material; ContactShadowPass m_Pass; public override void Create() { m_Pass ??= new ContactShadowPass(); // 与 URP 的屏幕空间阴影相同的时机:深度预渲染之后、不透明物体之前 m_Pass.renderPassEvent = RenderPassEvent.AfterRenderingPrePasses + 1; } public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) { int mainLight = renderingData.lightData.mainLightIndex; if (mainLight < 0) return; if (m_Material == null) { var shader = Shader.Find(k_ShaderName); if (shader == null) return; m_Material = CoreUtils.CreateEngineMaterial(shader); } // 方向光的 forward 是光线前进方向,取反得到“指向光源”的 L Vector3 L = -renderingData.lightData.visibleLights[mainLight].localToWorldMatrix.GetColumn(2); m_Pass.Setup(settings, m_Material, L.normalized); renderer.EnqueuePass(m_Pass); } protected override void Dispose(bool disposing) { m_Pass?.Dispose(); CoreUtils.Destroy(m_Material); } class ContactShadowPass : ScriptableRenderPass { static readonly int s_Params = Shader.PropertyToID("_ContactParams"); static readonly int s_LightDir = Shader.PropertyToID("_ContactLightDir"); readonly ProfilingSampler m_Sampler = new ProfilingSampler("ContactShadows"); RTHandle m_Target; Material m_Material; Settings m_Settings; Vector3 m_LightDir; public void Setup(Settings s, Material material, Vector3 lightDir) { m_Settings = s; m_Material = material; m_LightDir = lightDir; // 声明需要深度纹理:URP 会因此安排 DepthPrepass,保证此时 _CameraDepthTexture 已就绪 ConfigureInput(ScriptableRenderPassInput.Depth); } public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData) { var desc = renderingData.cameraData.cameraTargetDescriptor; desc.depthBufferBits = 0; desc.msaaSamples = 1; desc.graphicsFormat = RenderingUtils.SupportsGraphicsFormat(GraphicsFormat.R8_UNorm, FormatUsage.Linear | FormatUsage.Render) ? GraphicsFormat.R8_UNorm : GraphicsFormat.B8G8R8A8_UNorm; RenderingUtils.ReAllocateIfNeeded(ref m_Target, desc, FilterMode.Bilinear, TextureWrapMode.Clamp, name: "_ContactShadowTexture"); cmd.SetGlobalTexture(m_Target.name, m_Target.nameID); ConfigureTarget(m_Target); ConfigureClear(ClearFlag.None, Color.white); } public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { // URP 14 里 renderingData.commandBuffer 是 internal,自己的 Feature 从池里借一个 var cmd = CommandBufferPool.Get(); using (new ProfilingScope(cmd, m_Sampler)) { m_Material.SetVector(s_Params, new Vector4(m_Settings.length, m_Settings.steps, m_Settings.thickness, m_Settings.bias)); m_Material.SetVector(s_LightDir, new Vector4(m_LightDir.x, m_LightDir.y, m_LightDir.z, m_Settings.intensity)); // 源和目标是同一张图:Shader 根本不读 _BlitTexture,只借 Blitter 画一个全屏三角形 Blitter.BlitCameraTexture(cmd, m_Target, m_Target, m_Material, 0); } context.ExecuteCommandBuffer(cmd); CommandBufferPool.Release(cmd); } public void Dispose() => m_Target?.Release(); } } }