using System; using UnityEngine.Experimental.Rendering; using UnityEngine.Rendering.RenderGraphModule; namespace UnityEngine.Rendering.Universal { [Serializable] internal class ScreenSpaceShadowsSettings { } [SupportedOnRenderer(typeof(UniversalRendererData))] [DisallowMultipleRendererFeature("Screen Space Shadows")] [Tooltip("Screen Space Shadows")] [URPHelpURL("urp/renderer-feature-screen-space-shadows")] internal class ScreenSpaceShadows : ScriptableRendererFeature { #if UNITY_EDITOR [UnityEditor.ShaderKeywordFilter.SelectIf(true, keywordNames: ShaderKeywordStrings.MainLightShadowScreen)] private const bool k_RequiresScreenSpaceShadowsKeyword = true; #endif // Serialized Fields [SerializeField, HideInInspector] private Shader m_Shader = null; [SerializeField] private ScreenSpaceShadowsSettings m_Settings = new ScreenSpaceShadowsSettings(); // Private Fields private Material m_Material; private ScreenSpaceShadowsPass m_SSShadowsPass = null; private ScreenSpaceShadowsPostPass m_SSShadowsPostPass = null; // Constants private const string k_ShaderName = "Hidden/Universal Render Pipeline/ScreenSpaceShadows"; /// public override void Create() { if (m_SSShadowsPass == null) m_SSShadowsPass = new ScreenSpaceShadowsPass(); if (m_SSShadowsPostPass == null) m_SSShadowsPostPass = new ScreenSpaceShadowsPostPass(); LoadMaterial(); m_SSShadowsPass.renderPassEvent = RenderPassEvent.BeforeRenderingGbuffer; m_SSShadowsPostPass.renderPassEvent = RenderPassEvent.BeforeRenderingTransparents; } /// public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) { if (UniversalRenderer.IsOffscreenDepthTexture(ref renderingData.cameraData)) return; if (!LoadMaterial()) { Debug.LogErrorFormat( "{0}.AddRenderPasses(): Missing material. {1} render pass will not be added. Check for missing reference in the renderer resources.", GetType().Name, name); return; } bool allowMainLightShadows = renderingData.shadowData.supportsMainLightShadows && renderingData.lightData.mainLightIndex != -1; bool shouldEnqueue = allowMainLightShadows && m_SSShadowsPass.Setup(m_Settings, m_Material); if (shouldEnqueue) { bool usesDeferredLighting = renderer is UniversalRenderer { usesDeferredLighting: true }; m_SSShadowsPass.renderPassEvent = usesDeferredLighting ? RenderPassEvent.BeforeRenderingGbuffer : RenderPassEvent.AfterRenderingPrePasses + 1; // We add 1 to ensure this happens after depth priming depth copy pass that might be scheduled renderer.EnqueuePass(m_SSShadowsPass); renderer.EnqueuePass(m_SSShadowsPostPass); } } /// protected override void Dispose(bool disposing) { m_SSShadowsPass = null; CoreUtils.Destroy(m_Material); } private bool LoadMaterial() { if (m_Material != null) { return true; } if (m_Shader == null) { m_Shader = Shader.Find(k_ShaderName); if (m_Shader == null) { return false; } } m_Material = CoreUtils.CreateEngineMaterial(m_Shader); return m_Material != null; } /// /// Pass that renders screen-space shadows by sampling the main light shadow map. /// /// This pass reconstructs world positions from the depth prepass (cameraDepthTexture), /// transforms them to shadow space, and samples the shadow map to produce a screen-space /// shadow texture. This is more efficient than traditional forward shadow sampling per-pixel. /// /// IMPORTANT: World position reconstruction from depth requires the inverse view-projection matrix /// (unity_MatrixInvVP) to match the UV origin of the depth texture. In Tile-Only Mode and /// direct-to-backbuffer scenarios, the depth texture orientation may differ from the active /// render target, requiring explicit matrix setup. See ExecutePass for details. /// private class ScreenSpaceShadowsPass : ScriptableRenderPass { // Private Variables private Material m_Material; private ScreenSpaceShadowsSettings m_CurrentSettings; private int m_ScreenSpaceShadowmapTextureID; internal ScreenSpaceShadowsPass() { profilingSampler = new ProfilingSampler("Blit Screen Space Shadows"); m_CurrentSettings = new ScreenSpaceShadowsSettings(); m_ScreenSpaceShadowmapTextureID = Shader.PropertyToID("_ScreenSpaceShadowmapTexture"); } internal bool Setup(ScreenSpaceShadowsSettings featureSettings, Material material) { m_CurrentSettings = featureSettings; m_Material = material; ConfigureInput(ScriptableRenderPassInput.Depth); return m_Material != null; } private class PassData { internal TextureHandle target; internal TextureHandle cameraDepthTexture; internal TextureHandle activeTarget; internal Material material; internal UniversalCameraData cameraData; } /// /// Initialize the shared pass data. /// /// private void InitPassData(ref PassData passData, in TextureHandle cameraDepthTexture, in TextureHandle activeTarget, UniversalCameraData cameraData) { passData.material = m_Material; passData.cameraDepthTexture = cameraDepthTexture; passData.activeTarget = activeTarget; passData.cameraData = cameraData; } public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData) { if (m_Material == null) { Debug.LogErrorFormat("{0}.Execute(): Missing material. ScreenSpaceShadows pass will not execute. Check for missing reference in the renderer resources.", GetType().Name); return; } var cameraData = frameData.Get(); var resourceData = frameData.Get(); var desc = cameraData.cameraTargetDescriptor; desc.depthStencilFormat = GraphicsFormat.None; desc.msaaSamples = 1; // UUM-41070: We require `Linear | Render` but with the deprecated FormatUsage this was checking `Blend` // For now, we keep checking for `Blend` until the performance hit of doing the correct checks is evaluated desc.graphicsFormat = SystemInfo.IsFormatSupported(GraphicsFormat.R8_UNorm, GraphicsFormatUsage.Blend) ? GraphicsFormat.R8_UNorm : GraphicsFormat.B8G8R8A8_UNorm; TextureHandle color = UniversalRenderer.CreateRenderGraphTexture(renderGraph, desc, "_ScreenSpaceShadowmapTexture", true); // UUM-85291: Using UnsafePass to not allow this pass to merge with other passes as it can cause issues // when using Deferred Lighting by breaking up the Draw GBuffer and Deferred Lighting passes because // of 1) the Deferred Lighting pass reads this resource so it breaks the pass 2) a maximum input attachment // limit is met when this is moved before Draw GBuffer. // For now, using an UnsafePass ensures that this pass won't be merged as a fix is found for the other // underlying issues. using (var builder = renderGraph.AddUnsafePass(passName, out var passData, profilingSampler)) { passData.target = color; builder.UseTexture(color, AccessFlags.WriteAll); builder.UseTexture(resourceData.cameraDepthTexture); // activeColorTexture is always valid here since AddRenderPasses returns early for offscreen depth cameras InitPassData(ref passData, resourceData.cameraDepthTexture, resourceData.activeColorTexture, cameraData); builder.AllowGlobalStateModification(true); if (color.IsValid()) builder.SetGlobalTextureAfterPass(color, m_ScreenSpaceShadowmapTextureID); builder.SetRenderFunc(static (PassData data, UnsafeGraphContext rgContext) => { ExecutePass(rgContext, data); }); } } private static void ExecutePass(UnsafeGraphContext rgContext, PassData data) { var cmd = rgContext.cmd; // CRITICAL FIX for Tile-Only Mode and direct-to-backbuffer rendering: // // The shader reconstructs world positions from depth using: // float3 wpos = ComputeWorldSpacePosition(uv, depth, unity_MatrixInvVP); // // The global unity_MatrixInvVP might be set for the active render target's orientation (e.g., TopLeft in Tile-Only Mode), // but cameraDepthTexture was rendered with a potentially different orientation (always BottomLeft - it's an intermediate texture). // // When orientations don't match, the Y-coordinate in world-space reconstruction is inverted, causing shadow lookups // to sample from the wrong world positions → shadows appear upside down. // // FIX: Temporarily set unity_MatrixInvVP to match cameraDepthTexture's orientation for correct position reconstruction. TextureUVOrigin depthOrigin = rgContext.GetTextureUVOrigin(data.cameraDepthTexture); Matrix4x4 depthInvVP = RenderingUtils.ComputeInverseViewProjectionMatrix(depthOrigin, data.cameraData); cmd.SetGlobalMatrix(ShaderPropertyId.inverseViewAndProjectionMatrix, depthInvVP); // Perform the shadow sampling blit Blitter.BlitTexture(cmd, data.target, Vector2.one, data.material, 0); // Set shadow keywords cmd.SetKeyword(ShaderGlobalKeywords.MainLightShadows, false); cmd.SetKeyword(ShaderGlobalKeywords.MainLightShadowCascades, false); cmd.SetKeyword(ShaderGlobalKeywords.MainLightShadowScreen, true); // Restore unity_MatrixInvVP to match the active target's orientation for subsequent passes. // Without this, later passes that rely on the global matrix might get incorrect results. TextureUVOrigin activeOrigin = rgContext.GetTextureUVOrigin(data.activeTarget); Matrix4x4 activeInvVP = RenderingUtils.ComputeInverseViewProjectionMatrix(activeOrigin, data.cameraData); cmd.SetGlobalMatrix(ShaderPropertyId.inverseViewAndProjectionMatrix, activeInvVP); } } private class ScreenSpaceShadowsPostPass : ScriptableRenderPass { internal ScreenSpaceShadowsPostPass() { profilingSampler = new ProfilingSampler("Set Screen Space Shadow Keywords"); } private static void ExecutePass(RasterCommandBuffer cmd, UniversalShadowData shadowData) { int cascadesCount = shadowData.mainLightShadowCascadesCount; bool mainLightShadows = shadowData.supportsMainLightShadows; bool receiveShadowsNoCascade = mainLightShadows && cascadesCount == 1; bool receiveShadowsCascades = mainLightShadows && cascadesCount > 1; // Before transparent object pass, force to disable screen space shadow of main light cmd.SetKeyword(ShaderGlobalKeywords.MainLightShadowScreen, false); // then enable main light shadows with or without cascades cmd.SetKeyword(ShaderGlobalKeywords.MainLightShadows, receiveShadowsNoCascade); cmd.SetKeyword(ShaderGlobalKeywords.MainLightShadowCascades, receiveShadowsCascades); } internal class PassData { internal UniversalShadowData shadowData; } public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData) { using (var builder = renderGraph.AddRasterRenderPass(passName, out var passData, profilingSampler)) { UniversalResourceData resourceData = frameData.Get(); TextureHandle color = resourceData.activeColorTexture; builder.SetRenderAttachment(color, 0, AccessFlags.Write); passData.shadowData = frameData.Get(); builder.AllowGlobalStateModification(true); builder.SetRenderFunc(static (PassData data, RasterGraphContext rgContext) => { ExecutePass(rgContext.cmd, data.shadowData); }); } } } } }