using System; using UnityEngine.Experimental.Rendering; using UnityEngine.Rendering.RenderGraphModule; using System.Collections.Generic; namespace UnityEngine.Rendering.Universal.Internal { /// /// Renders a shadow map for the main Light. /// public class MainLightShadowCasterPass : ScriptableRenderPass { private static class MainLightShadowConstantBuffer { public static int _WorldToShadow; public static int _ShadowParams; public static int _CascadeShadowSplitSpheres0; public static int _CascadeShadowSplitSpheres1; public static int _CascadeShadowSplitSpheres2; public static int _CascadeShadowSplitSpheres3; public static int _CascadeShadowSplitSphereRadii; public static int _ShadowOffset0; public static int _ShadowOffset1; public static int _ShadowmapSize; } const int k_MaxCascades = 4; const int k_ShadowmapBufferBits = 16; float m_CascadeBorder; float m_MaxShadowDistanceSq; int m_ShadowCasterCascadesCount; int m_MainLightShadowmapID; internal RTHandle m_MainLightShadowmapTexture; private RTHandle m_EmptyMainLightShadowmapTexture; private const int k_EmptyShadowMapDimensions = 1; private const string k_MainLightShadowMapTextureName = "_MainLightShadowmapTexture"; private const string k_EmptyMainLightShadowMapTextureName = "_EmptyMainLightShadowmapTexture"; private static readonly Vector4 s_EmptyShadowParams = new Vector4(1, 0, 1, 0); private static readonly Vector4 s_EmptyShadowmapSize = s_EmptyShadowmapSize = new Vector4(k_EmptyShadowMapDimensions, 1f / k_EmptyShadowMapDimensions, k_EmptyShadowMapDimensions, k_EmptyShadowMapDimensions); Matrix4x4[] m_MainLightShadowMatrices; ShadowSliceData[] m_CascadeSlices; Vector4[] m_CascadeSplitDistances; private RenderTextureDescriptor m_MainLightShadowDescriptor; bool m_CreateEmptyShadowmap; bool m_EmptyShadowmapNeedsClear = false; int renderTargetWidth; int renderTargetHeight; ProfilingSampler m_ProfilingSetupSampler = new ProfilingSampler("Setup Main Shadowmap"); private PassData m_PassData; /// /// Creates a new MainLightShadowCasterPass instance. /// /// The RenderPassEvent to use. /// public MainLightShadowCasterPass(RenderPassEvent evt) { profilingSampler = new ProfilingSampler("Draw Main Light Shadowmap"); renderPassEvent = evt; m_PassData = new PassData(); m_MainLightShadowMatrices = new Matrix4x4[k_MaxCascades + 1]; m_CascadeSlices = new ShadowSliceData[k_MaxCascades]; m_CascadeSplitDistances = new Vector4[k_MaxCascades]; MainLightShadowConstantBuffer._WorldToShadow = Shader.PropertyToID("_MainLightWorldToShadow"); MainLightShadowConstantBuffer._ShadowParams = Shader.PropertyToID("_MainLightShadowParams"); MainLightShadowConstantBuffer._CascadeShadowSplitSpheres0 = Shader.PropertyToID("_CascadeShadowSplitSpheres0"); MainLightShadowConstantBuffer._CascadeShadowSplitSpheres1 = Shader.PropertyToID("_CascadeShadowSplitSpheres1"); MainLightShadowConstantBuffer._CascadeShadowSplitSpheres2 = Shader.PropertyToID("_CascadeShadowSplitSpheres2"); MainLightShadowConstantBuffer._CascadeShadowSplitSpheres3 = Shader.PropertyToID("_CascadeShadowSplitSpheres3"); MainLightShadowConstantBuffer._CascadeShadowSplitSphereRadii = Shader.PropertyToID("_CascadeShadowSplitSphereRadii"); MainLightShadowConstantBuffer._ShadowOffset0 = Shader.PropertyToID("_MainLightShadowOffset0"); MainLightShadowConstantBuffer._ShadowOffset1 = Shader.PropertyToID("_MainLightShadowOffset1"); MainLightShadowConstantBuffer._ShadowmapSize = Shader.PropertyToID("_MainLightShadowmapSize"); m_MainLightShadowmapID = Shader.PropertyToID(k_MainLightShadowMapTextureName); m_EmptyShadowmapNeedsClear = true; } /// /// Cleans up resources used by the pass. /// public void Dispose() { m_MainLightShadowmapTexture?.Release(); m_EmptyMainLightShadowmapTexture?.Release(); } /// /// Sets up the pass. /// /// /// True if the pass should be enqueued, otherwise false. /// public bool Setup(ref RenderingData renderingData) { ContextContainer frameData = renderingData.frameData; UniversalRenderingData universalRenderingData = frameData.Get(); UniversalCameraData cameraData = frameData.Get(); UniversalLightData lightData = frameData.Get(); UniversalShadowData shadowData = frameData.Get(); return Setup(universalRenderingData, cameraData, lightData, shadowData); } /// /// Sets up the pass. /// /// Data containing rendering settings. /// Data containing camera settings. /// Data containing light settings. /// Data containing shadow settings. /// True if the pass should be enqueued, otherwise false. /// public bool Setup(UniversalRenderingData renderingData, UniversalCameraData cameraData, UniversalLightData lightData, UniversalShadowData shadowData) { if (!shadowData.mainLightShadowsEnabled) return false; #if UNITY_EDITOR if (CoreUtils.IsSceneLightingDisabled(cameraData.camera)) return false; #endif using var profScope = new ProfilingScope(m_ProfilingSetupSampler); if (!shadowData.supportsMainLightShadows) return SetupForEmptyRendering(cameraData.renderer.stripShadowsOffVariants); Clear(); int shadowLightIndex = lightData.mainLightIndex; if (shadowLightIndex == -1) return SetupForEmptyRendering(cameraData.renderer.stripShadowsOffVariants); VisibleLight shadowLight = lightData.visibleLights[shadowLightIndex]; Light light = shadowLight.light; if (light.shadows == LightShadows.None) return SetupForEmptyRendering(cameraData.renderer.stripShadowsOffVariants); if (shadowLight.lightType != LightType.Directional) { Debug.LogWarning("Only directional lights are supported as main light."); } if (!renderingData.cullResults.GetShadowCasterBounds(shadowLightIndex, out Bounds _)) return SetupForEmptyRendering(cameraData.renderer.stripShadowsOffVariants); m_ShadowCasterCascadesCount = shadowData.mainLightShadowCascadesCount; renderTargetWidth = shadowData.mainLightRenderTargetWidth; renderTargetHeight = shadowData.mainLightRenderTargetHeight; ref readonly URPLightShadowCullingInfos shadowCullingInfos = ref shadowData.visibleLightsShadowCullingInfos.UnsafeElementAt(shadowLightIndex); for (int cascadeIndex = 0; cascadeIndex < m_ShadowCasterCascadesCount; ++cascadeIndex) { ref readonly ShadowSliceData sliceData = ref shadowCullingInfos.slices.UnsafeElementAt(cascadeIndex); m_CascadeSplitDistances[cascadeIndex] = sliceData.splitData.cullingSphere; m_CascadeSlices[cascadeIndex] = sliceData; if (!shadowCullingInfos.IsSliceValid(cascadeIndex)) return SetupForEmptyRendering(cameraData.renderer.stripShadowsOffVariants); } UpdateTextureDescriptorIfNeeded(); m_MaxShadowDistanceSq = cameraData.maxShadowDistance * cameraData.maxShadowDistance; m_CascadeBorder = shadowData.mainLightShadowCascadeBorder; m_CreateEmptyShadowmap = false; useNativeRenderPass = true; return true; } private void UpdateTextureDescriptorIfNeeded() { if ( m_MainLightShadowDescriptor.width != renderTargetWidth || m_MainLightShadowDescriptor.height != renderTargetHeight || m_MainLightShadowDescriptor.depthBufferBits != k_ShadowmapBufferBits || m_MainLightShadowDescriptor.colorFormat != RenderTextureFormat.Shadowmap) { m_MainLightShadowDescriptor = new RenderTextureDescriptor(renderTargetWidth, renderTargetHeight, RenderTextureFormat.Shadowmap, k_ShadowmapBufferBits); } } bool SetupForEmptyRendering(bool stripShadowsOffVariants) { if (!stripShadowsOffVariants) return false; m_CreateEmptyShadowmap = true; useNativeRenderPass = false; return true; } /// [Obsolete(DeprecationMessage.CompatibilityScriptingAPIObsolete, false)] public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor) { // Disable obsolete warning for internal usage #pragma warning disable CS0618 if (m_CreateEmptyShadowmap) { // Required for scene view camera(URP renderer not initialized) if (ShadowUtils.ShadowRTReAllocateIfNeeded(ref m_EmptyMainLightShadowmapTexture, k_EmptyShadowMapDimensions, k_EmptyShadowMapDimensions, k_ShadowmapBufferBits, name: k_EmptyMainLightShadowMapTextureName)) m_EmptyShadowmapNeedsClear = true; if (!m_EmptyShadowmapNeedsClear) { // UUM-63146 - glClientWaitSync: Expected application to have kicked everything until job: 96089 (possibly by calling glFlush)" are thrown in the Android Player on some devices with PowerVR Rogue GE8320 // Resetting of target would clean up the color attachment buffers and depth attachment buffers, which inturn is preventing the leak in the said platform. This is likely a symptomatic fix, but is solving the problem for now. if (Application.platform == RuntimePlatform.Android && PlatformAutoDetect.isRunningOnPowerVRGPU) ResetTarget(); return; } ConfigureTarget(m_EmptyMainLightShadowmapTexture); m_EmptyShadowmapNeedsClear = false; } else { ShadowUtils.ShadowRTReAllocateIfNeeded(ref m_MainLightShadowmapTexture, renderTargetWidth, renderTargetHeight, k_ShadowmapBufferBits, name: k_MainLightShadowMapTextureName); ConfigureTarget(m_MainLightShadowmapTexture); } ConfigureClear(ClearFlag.All, Color.black); #pragma warning restore CS0618 } /// [Obsolete(DeprecationMessage.CompatibilityScriptingAPIObsolete, false)] public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { ContextContainer frameData = renderingData.frameData; UniversalRenderingData universalRenderingData = frameData.Get(); UniversalCameraData cameraData = frameData.Get(); UniversalLightData lightData = frameData.Get(); UniversalShadowData shadowData = frameData.Get(); if (m_CreateEmptyShadowmap) { SetEmptyMainLightCascadeShadowmap(CommandBufferHelpers.GetRasterCommandBuffer(universalRenderingData.commandBuffer)); universalRenderingData.commandBuffer.SetGlobalTexture(m_MainLightShadowmapID, m_EmptyMainLightShadowmapTexture.nameID); return; } InitPassData(ref m_PassData, universalRenderingData, cameraData, lightData, shadowData); InitRendererLists(ref m_PassData, context, default(RenderGraph), false); RenderMainLightCascadeShadowmap(CommandBufferHelpers.GetRasterCommandBuffer(universalRenderingData.commandBuffer), ref m_PassData, false); universalRenderingData.commandBuffer.SetGlobalTexture(m_MainLightShadowmapID, m_MainLightShadowmapTexture.nameID); } void Clear() { for (int i = 0; i < m_MainLightShadowMatrices.Length; ++i) m_MainLightShadowMatrices[i] = Matrix4x4.identity; for (int i = 0; i < m_CascadeSplitDistances.Length; ++i) m_CascadeSplitDistances[i] = new Vector4(0.0f, 0.0f, 0.0f, 0.0f); for (int i = 0; i < m_CascadeSlices.Length; ++i) m_CascadeSlices[i].Clear(); } void SetEmptyMainLightCascadeShadowmap(RasterCommandBuffer cmd) { cmd.EnableKeyword(ShaderGlobalKeywords.MainLightShadows); SetEmptyMainLightShadowParams(cmd); } internal static void SetEmptyMainLightShadowParams(RasterCommandBuffer cmd) { cmd.SetGlobalVector(MainLightShadowConstantBuffer._ShadowParams, s_EmptyShadowParams); cmd.SetGlobalVector(MainLightShadowConstantBuffer._ShadowmapSize, s_EmptyShadowmapSize); } void RenderMainLightCascadeShadowmap(RasterCommandBuffer cmd, ref PassData data, bool isRenderGraph) { var lightData = data.lightData; int shadowLightIndex = lightData.mainLightIndex; if (shadowLightIndex == -1) return; VisibleLight shadowLight = lightData.visibleLights[shadowLightIndex]; using (new ProfilingScope(cmd, ProfilingSampler.Get(URPProfileId.MainLightShadow))) { // Need to start by setting the Camera position and worldToCamera Matrix as that is not set for passes executed before normal rendering ShadowUtils.SetCameraPosition(cmd, data.cameraData.worldSpaceCameraPos); // For non-RG, need set the worldToCamera Matrix as that is not set for passes executed before normal rendering, // otherwise shadows will behave incorrectly when Scene and Game windows are open at the same time (UUM-63267). if (!isRenderGraph) ShadowUtils.SetWorldToCameraMatrix(cmd, data.cameraData.GetViewMatrix()); for (int cascadeIndex = 0; cascadeIndex < m_ShadowCasterCascadesCount; ++cascadeIndex) { Vector4 shadowBias = ShadowUtils.GetShadowBias(ref shadowLight, shadowLightIndex, data.shadowData, m_CascadeSlices[cascadeIndex].projectionMatrix, m_CascadeSlices[cascadeIndex].resolution); ShadowUtils.SetupShadowCasterConstantBuffer(cmd, ref shadowLight, shadowBias); cmd.SetKeyword(ShaderGlobalKeywords.CastingPunctualLightShadow, false); RendererList shadowRendererList = isRenderGraph? data.shadowRendererListsHandle[cascadeIndex] : data.shadowRendererLists[cascadeIndex]; ShadowUtils.RenderShadowSlice(cmd, ref m_CascadeSlices[cascadeIndex], ref shadowRendererList, m_CascadeSlices[cascadeIndex].projectionMatrix, m_CascadeSlices[cascadeIndex].viewMatrix); } data.shadowData.isKeywordSoftShadowsEnabled = shadowLight.light.shadows == LightShadows.Soft && data.shadowData.supportsSoftShadows; cmd.SetKeyword(ShaderGlobalKeywords.MainLightShadows, data.shadowData.mainLightShadowCascadesCount == 1); cmd.SetKeyword(ShaderGlobalKeywords.MainLightShadowCascades, data.shadowData.mainLightShadowCascadesCount > 1); ShadowUtils.SetSoftShadowQualityShaderKeywords(cmd, data.shadowData); SetupMainLightShadowReceiverConstants(cmd, ref shadowLight, data.shadowData); } } void SetupMainLightShadowReceiverConstants(RasterCommandBuffer cmd, ref VisibleLight shadowLight, UniversalShadowData shadowData) { Light light = shadowLight.light; bool softShadows = shadowLight.light.shadows == LightShadows.Soft && shadowData.supportsSoftShadows; int cascadeCount = m_ShadowCasterCascadesCount; for (int i = 0; i < cascadeCount; ++i) m_MainLightShadowMatrices[i] = m_CascadeSlices[i].shadowTransform; // We setup and additional a no-op WorldToShadow matrix in the last index // because the ComputeCascadeIndex function in Shadows.hlsl can return an index // out of bounds. (position not inside any cascade) and we want to avoid branching Matrix4x4 noOpShadowMatrix = Matrix4x4.zero; noOpShadowMatrix.m22 = (SystemInfo.usesReversedZBuffer) ? 1.0f : 0.0f; for (int i = cascadeCount; i <= k_MaxCascades; ++i) m_MainLightShadowMatrices[i] = noOpShadowMatrix; float invShadowAtlasWidth = 1.0f / renderTargetWidth; float invShadowAtlasHeight = 1.0f / renderTargetHeight; float invHalfShadowAtlasWidth = 0.5f * invShadowAtlasWidth; float invHalfShadowAtlasHeight = 0.5f * invShadowAtlasHeight; float softShadowsProp = ShadowUtils.SoftShadowQualityToShaderProperty(light, softShadows); ShadowUtils.GetScaleAndBiasForLinearDistanceFade(m_MaxShadowDistanceSq, m_CascadeBorder, out float shadowFadeScale, out float shadowFadeBias); cmd.SetGlobalMatrixArray(MainLightShadowConstantBuffer._WorldToShadow, m_MainLightShadowMatrices); cmd.SetGlobalVector(MainLightShadowConstantBuffer._ShadowParams, new Vector4(light.shadowStrength, softShadowsProp, shadowFadeScale, shadowFadeBias)); if (m_ShadowCasterCascadesCount > 1) { cmd.SetGlobalVector(MainLightShadowConstantBuffer._CascadeShadowSplitSpheres0, m_CascadeSplitDistances[0]); cmd.SetGlobalVector(MainLightShadowConstantBuffer._CascadeShadowSplitSpheres1, m_CascadeSplitDistances[1]); cmd.SetGlobalVector(MainLightShadowConstantBuffer._CascadeShadowSplitSpheres2, m_CascadeSplitDistances[2]); cmd.SetGlobalVector(MainLightShadowConstantBuffer._CascadeShadowSplitSpheres3, m_CascadeSplitDistances[3]); cmd.SetGlobalVector(MainLightShadowConstantBuffer._CascadeShadowSplitSphereRadii, new Vector4( m_CascadeSplitDistances[0].w * m_CascadeSplitDistances[0].w, m_CascadeSplitDistances[1].w * m_CascadeSplitDistances[1].w, m_CascadeSplitDistances[2].w * m_CascadeSplitDistances[2].w, m_CascadeSplitDistances[3].w * m_CascadeSplitDistances[3].w)); } // Inside shader soft shadows are controlled through global keyword. // If any additional light has soft shadows it will force soft shadows on main light too. // As it is not trivial finding out which additional light has soft shadows, we will pass main light properties if soft shadows are supported. // This workaround will be removed once we will support soft shadows per light. if (shadowData.supportsSoftShadows) { cmd.SetGlobalVector(MainLightShadowConstantBuffer._ShadowOffset0, new Vector4(-invHalfShadowAtlasWidth, -invHalfShadowAtlasHeight, invHalfShadowAtlasWidth, -invHalfShadowAtlasHeight)); cmd.SetGlobalVector(MainLightShadowConstantBuffer._ShadowOffset1, new Vector4(-invHalfShadowAtlasWidth, invHalfShadowAtlasHeight, invHalfShadowAtlasWidth, invHalfShadowAtlasHeight)); cmd.SetGlobalVector(MainLightShadowConstantBuffer._ShadowmapSize, new Vector4(invShadowAtlasWidth, invShadowAtlasHeight, renderTargetWidth, renderTargetHeight)); } } private class PassData { internal UniversalRenderingData renderingData; internal UniversalCameraData cameraData; internal UniversalLightData lightData; internal UniversalShadowData shadowData; internal MainLightShadowCasterPass pass; internal TextureHandle shadowmapTexture; internal int shadowmapID; internal bool emptyShadowmap; internal RendererListHandle[] shadowRendererListsHandle = new RendererListHandle[k_MaxCascades]; internal RendererList[] shadowRendererLists = new RendererList[k_MaxCascades]; } private void InitPassData( ref PassData passData, UniversalRenderingData renderingData, UniversalCameraData cameraData, UniversalLightData lightData, UniversalShadowData shadowData) { passData.pass = this; passData.emptyShadowmap = m_CreateEmptyShadowmap; passData.shadowmapID = m_MainLightShadowmapID; passData.renderingData = renderingData; passData.cameraData = cameraData; passData.lightData = lightData; passData.shadowData = shadowData; } void InitEmptyPassData( ref PassData passData, UniversalRenderingData renderingData, UniversalCameraData cameraData, UniversalLightData lightData, UniversalShadowData shadowData) { passData.pass = this; passData.emptyShadowmap = m_CreateEmptyShadowmap; passData.shadowmapID = m_MainLightShadowmapID; passData.renderingData = renderingData; passData.cameraData = cameraData; passData.lightData = lightData; passData.shadowData = shadowData; } private void InitRendererLists(ref PassData passData, ScriptableRenderContext context, RenderGraph renderGraph, bool useRenderGraph) { int shadowLightIndex = passData.lightData.mainLightIndex; if (!m_CreateEmptyShadowmap && shadowLightIndex != -1) { var settings = new ShadowDrawingSettings(passData.renderingData.cullResults, shadowLightIndex); settings.useRenderingLayerMaskTest = UniversalRenderPipeline.asset.useRenderingLayers; for (int cascadeIndex = 0; cascadeIndex < m_ShadowCasterCascadesCount; ++cascadeIndex) { if (useRenderGraph) passData.shadowRendererListsHandle[cascadeIndex] = renderGraph.CreateShadowRendererList(ref settings); else passData.shadowRendererLists[cascadeIndex] = context.CreateShadowRendererList(ref settings); } } } internal TextureHandle Render(RenderGraph graph, ContextContainer frameData) { UniversalRenderingData renderingData = frameData.Get(); UniversalCameraData cameraData = frameData.Get(); UniversalLightData lightData = frameData.Get(); UniversalShadowData shadowData = frameData.Get(); TextureHandle shadowTexture; using (var builder = graph.AddRasterRenderPass(passName, out var passData, profilingSampler)) { InitPassData(ref passData, renderingData, cameraData, lightData, shadowData); InitRendererLists(ref passData, default(ScriptableRenderContext), graph, true); if (!m_CreateEmptyShadowmap) { for (int cascadeIndex = 0; cascadeIndex < m_ShadowCasterCascadesCount; ++cascadeIndex) { builder.UseRendererList(passData.shadowRendererListsHandle[cascadeIndex]); } shadowTexture = UniversalRenderer.CreateRenderGraphTexture(graph, m_MainLightShadowDescriptor, k_MainLightShadowMapTextureName, true, ShadowUtils.m_ForceShadowPointSampling ? FilterMode.Point : FilterMode.Bilinear); builder.SetRenderAttachmentDepth(shadowTexture, AccessFlags.Write); } else { shadowTexture = graph.defaultResources.defaultShadowTexture; } // Need this as shadowmap is only used as Global Texture and not a buffer, so would get culled by RG builder.AllowPassCulling(false); builder.AllowGlobalStateModification(true); if (shadowTexture.IsValid()) builder.SetGlobalTextureAfterPass(shadowTexture, m_MainLightShadowmapID); builder.SetRenderFunc((PassData data, RasterGraphContext context) => { if (!data.emptyShadowmap) { data.pass.RenderMainLightCascadeShadowmap(context.cmd, ref data, true); } else { data.pass.SetEmptyMainLightCascadeShadowmap(context.cmd); } }); } return shadowTexture; } }; }