using System; using Unity.Collections; using UnityEngine; using UnityEngine.Experimental.Rendering; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; #if UNITY_6000_0_OR_NEWER || UNITY_2023_2_OR_NEWER using UnityEngine.Rendering.RenderGraphModule; namespace BadDog.Rendering.AreaLight { public partial class AreaLightingPass { private RendererListHandle[] m_ShadowRendererListsHandles; private partial class PassData { internal RendererListHandle[] shadowRendererListsHdl; internal UniversalLightData lightData; internal int maxAreaLights; } partial void ConfigureShadowSettingsRenderGraph(int maxShadowCastingLights) { if (m_ShadowRendererListsHandles == null || m_ShadowRendererListsHandles.Length != maxShadowCastingLights) { m_ShadowRendererListsHandles = new RendererListHandle[maxShadowCastingLights]; } if (m_PassData != null) { m_PassData.shadowRendererListsHdl = m_ShadowRendererListsHandles; } } partial void EnsureMappingArraysRenderGraph() { if (m_ShadowRendererListsHandles == null || m_ShadowRendererListsHandles.Length != m_ShadowSettings.maxShadowCastingLights) { m_ShadowRendererListsHandles = new RendererListHandle[m_ShadowSettings.maxShadowCastingLights]; } } partial void ResetMappingArraysRenderGraph() { if (m_ShadowRendererListsHandles != null) { Array.Clear(m_ShadowRendererListsHandles, 0, m_ShadowRendererListsHandles.Length); } } public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData) { UniversalLightData lightData = frameData.Get(); UniversalCameraData cameraData = frameData.Get(); UniversalRenderingData renderingData = frameData.Get(); UniversalShadowData shadowData = frameData.Get(); using (var builder = renderGraph.AddUnsafePass("AreaLighting Setup", out var passData, m_ProfilingSampler)) { // Disable pass culling first - this pass must always execute to set up global textures and buffers builder.AllowPassCulling(false); builder.AllowGlobalStateModification(true); passData.lightData = lightData; passData.maxAreaLights = m_LightSettings.maxAreaLights; builder.SetRenderFunc((PassData data, UnsafeGraphContext context) => { // Convert UnsafeCommandBuffer to CommandBuffer for methods that require CommandBuffer CommandBuffer cmd = CommandBufferHelpers.GetNativeCommandBuffer(context.cmd); // Enable global shader keyword for BG Area Lighting CoreUtils.SetKeyword(cmd, AreaLightingShaderIDs.ENABLE_BG_AREA_LIGHTING_KEYWORD, true); // Build LTC data texture (uses reference counting, safe to call multiple times) LTCAreaLight.instance.Build(); LTCAreaLight.instance.Bind(cmd); // GGX + Disney Diffuse PreIntegratedFGD.instance.Build(PreIntegratedFGD.FGDIndex.FGD_GGXAndDisneyDiffuse); PreIntegratedFGD.instance.RenderInit(PreIntegratedFGD.FGDIndex.FGD_GGXAndDisneyDiffuse, cmd); PreIntegratedFGD.instance.Bind(cmd, PreIntegratedFGD.FGDIndex.FGD_GGXAndDisneyDiffuse); // Charlie + Fabric Lambert PreIntegratedFGD.instance.Build(PreIntegratedFGD.FGDIndex.FGD_CharlieAndFabricLambert); PreIntegratedFGD.instance.RenderInit(PreIntegratedFGD.FGDIndex.FGD_CharlieAndFabricLambert, cmd); PreIntegratedFGD.instance.Bind(cmd, PreIntegratedFGD.FGDIndex.FGD_CharlieAndFabricLambert); // Marschner PreIntegratedFGD.instance.Build(PreIntegratedFGD.FGDIndex.FGD_Marschner); PreIntegratedFGD.instance.RenderInit(PreIntegratedFGD.FGDIndex.FGD_Marschner, cmd); PreIntegratedFGD.instance.Bind(cmd, PreIntegratedFGD.FGDIndex.FGD_Marschner); var visibleLights = data.lightData.visibleLights; var areaLightDataArray = BGAreaLightManager.BuildAreaLightDataArray(visibleLights, data.lightData.mainLightIndex, data.maxAreaLights); var areaLightBuffer = BGAreaLightManager.UpdateGraphicsBuffer(areaLightDataArray); if (areaLightBuffer != null) { cmd.SetGlobalBuffer(AreaLightingShaderIDs._AreaLightDataBuffer, areaLightBuffer); cmd.SetGlobalInt(AreaLightingShaderIDs._AreaLightDataBufferCount, areaLightDataArray.IsCreated ? areaLightDataArray.Length : 0); } else { var emptyBuffer = BGAreaLightManager.GetEmptyBuffer(); cmd.SetGlobalBuffer(AreaLightingShaderIDs._AreaLightDataBuffer, emptyBuffer); cmd.SetGlobalInt(AreaLightingShaderIDs._AreaLightDataBufferCount, 0); } }); } // Shadow pass - render area light shadows if any if (m_ValidShadowCastingLightsCount > 0 || m_CreateEmptyShadowmap) { using (var builder = renderGraph.AddRasterRenderPass("AreaLight Shadows", out var passData, m_ShadowProfilingSampler)) { InitPassData(ref passData, cameraData, lightData, shadowData); InitRendererLists(ref renderingData.cullResults, ref passData, renderGraph); TextureHandle shadowTexture; if (!m_CreateEmptyShadowmap) { for (int globalShadowSliceIndex = 0; globalShadowSliceIndex < m_ShadowSliceToAreaLightIndex.Count; ++globalShadowSliceIndex) { builder.UseRendererList(passData.shadowRendererListsHdl[globalShadowSliceIndex]); } shadowTexture = UniversalRenderer.CreateRenderGraphTexture(renderGraph, m_AreaLightShadowDescriptor, "_AreaLightsShadowmapTexture", true, FilterMode.Bilinear); builder.SetRenderAttachmentDepth(shadowTexture, AccessFlags.Write); } else { shadowTexture = renderGraph.defaultResources.defaultShadowTexture; } TextureDesc descriptor = shadowTexture.GetDescriptor(renderGraph); passData.allocatedShadowAtlasSize = new Vector2Int(descriptor.width, descriptor.height); builder.AllowGlobalStateModification(true); if (shadowTexture.IsValid()) { builder.SetGlobalTextureAfterPass(shadowTexture, AreaLightingShaderIDs._AreaLightsShadowmapTexture); } builder.SetRenderFunc((PassData data, RasterGraphContext context) => { RasterCommandBuffer rasterCommandBuffer = context.cmd; if (!data.emptyShadowmap) { data.pass.RenderAreaLightShadowmapAtlas(rasterCommandBuffer, ref data); } else { SetShadowParamsForEmptyShadowmap(rasterCommandBuffer); } }); } } } private void InitRendererLists(ref CullingResults cullResults, ref PassData passData, RenderGraph renderGraph) { if (m_CreateEmptyShadowmap) { return; } for (int shadowSliceIndex = 0; shadowSliceIndex < m_ValidShadowCastingLightsCount; ++shadowSliceIndex) { int areaLightIndex = m_ShadowSliceToAreaLightIndex[shadowSliceIndex]; int visibleLightIndex = m_AreaLightIndexToVisibleLightIndex[areaLightIndex]; if (visibleLightIndex < 0 || visibleLightIndex >= passData.visibleLights.Length) { passData.shadowRendererListsHdl[shadowSliceIndex] = default; continue; } Light shadowLight = passData.visibleLights[visibleLightIndex].light; int layerMask = shadowLight != null ? shadowLight.cullingMask : ~0; bool useRenderingLayers = UniversalRenderPipeline.asset != null && UniversalRenderPipeline.asset.useRenderingLayers; uint renderingLayerMask = useRenderingLayers && shadowLight != null ? (uint)shadowLight.renderingLayerMask : uint.MaxValue; var sorting = new SortingSettings(passData.camera) { criteria = SortingCriteria.None }; var ds = new DrawingSettings(new ShaderTagId("ShadowCaster"), sorting); var fs = new FilteringSettings(RenderQueueRange.all, layerMask, renderingLayerMask); RendererListParams param = new RendererListParams(cullResults, ds, fs); passData.shadowRendererListsHdl[shadowSliceIndex] = renderGraph.CreateRendererList(param); } } private void InitPassData(ref PassData passData, UniversalCameraData cameraData, UniversalLightData lightData, UniversalShadowData shadowData) { passData.pass = this; passData.emptyShadowmap = m_CreateEmptyShadowmap; passData.visibleLights = lightData.visibleLights; passData.viewMatrix = cameraData.GetViewMatrix(); passData.projectionMatrix = cameraData.GetProjectionMatrix(); passData.camera = cameraData.camera; passData.shadowRendererLists = m_ShadowRendererLists; passData.shadowRendererListsHdl = m_ShadowRendererListsHandles; } private void SetupAreaLightsShadowReceiverConstants(RasterCommandBuffer cmd, Vector2Int atlasSize) { cmd.SetGlobalMatrixArray(AreaLightingShaderIDs._AreaLightsWorldToShadow, m_AreaLightShadowSliceIndexTo_WorldShadowMatrix); cmd.SetGlobalVectorArray(AreaLightingShaderIDs._AreaLightShadowParams, m_AreaLightIndexToShadowParams); cmd.SetGlobalVector(AreaLightingShaderIDs._AreaLightShadowmapSize, new Vector4(1f / atlasSize.x, 1f / atlasSize.y, atlasSize.x, atlasSize.y)); float pcssShadowSoftness = Mathf.Max(0f, m_ShadowSettings.pcssShadowSoftness); int pcssBlockerSampleCount = Mathf.Clamp(m_ShadowSettings.pcssBlockerSampleCount, 1, 64); int pcssFilterSampleCount = Mathf.Clamp(m_ShadowSettings.pcssFilterSampleCount, 1, 64); cmd.SetGlobalVector(AreaLightingShaderIDs._PCSSAdditionalLightParams, new Vector4(pcssShadowSoftness, pcssBlockerSampleCount, pcssFilterSampleCount, 1.0f)); } private static void SetShadowParamsForEmptyShadowmap(RasterCommandBuffer cmd) { cmd.SetGlobalMatrixArray(AreaLightingShaderIDs._AreaLightsWorldToShadow, s_EmptyShadowMatrices); cmd.SetGlobalVectorArray(AreaLightingShaderIDs._AreaLightShadowParams, s_EmptyShadowParams); cmd.SetGlobalVector(AreaLightingShaderIDs._AreaLightShadowmapSize, new Vector4(0f, 0f, 1f, 1f)); cmd.SetGlobalVector(AreaLightingShaderIDs._PCSSAdditionalLightParams, Vector4.zero); } private void SetBGAreaLightShadowQualityKeywords(RasterCommandBuffer cmd) { cmd.SetKeyword(ShaderGlobalKeywords.BGAreaLightShadowsPCF2x2, false); cmd.SetKeyword(ShaderGlobalKeywords.BGAreaLightShadowsTent5x5, false); cmd.SetKeyword(ShaderGlobalKeywords.BGAreaLightShadowsTent7x7, false); cmd.SetKeyword(ShaderGlobalKeywords.BGAreaLightShadowsPCSS, false); switch (m_ShadowSettings.shadowFilter) { case AreaLightShadowSettings.BGAreaLightShadowMode.PCF2x2: cmd.SetKeyword(ShaderGlobalKeywords.BGAreaLightShadowsPCF2x2, true); break; case AreaLightShadowSettings.BGAreaLightShadowMode.Tent5x5: cmd.SetKeyword(ShaderGlobalKeywords.BGAreaLightShadowsTent5x5, true); break; case AreaLightShadowSettings.BGAreaLightShadowMode.Tent7x7: cmd.SetKeyword(ShaderGlobalKeywords.BGAreaLightShadowsTent7x7, true); break; case AreaLightShadowSettings.BGAreaLightShadowMode.PCSS: cmd.SetKeyword(ShaderGlobalKeywords.BGAreaLightShadowsPCSS, true); break; case AreaLightShadowSettings.BGAreaLightShadowMode.Off: default: // All keywords disabled (handled above) break; } } private void RenderAreaLightShadowmapAtlas(RasterCommandBuffer cmd, ref PassData data) { using (new ProfilingScope(cmd, m_ShadowProfilingSampler)) { SetBGAreaLightShadowQualityKeywords(cmd); bool anyShadowSliceRendered = false; int shadowSlicesCount = m_ShadowSliceToAreaLightIndex.Count; Vector4 lastShadowBias = new Vector4(-10f, -10f, -10f, -10f); for (int globalShadowSliceIndex = 0; globalShadowSliceIndex < shadowSlicesCount; ++globalShadowSliceIndex) { int areaLightIndex = m_ShadowSliceToAreaLightIndex[globalShadowSliceIndex]; if (areaLightIndex < 0) { continue; } Vector4 shadowParams = m_AreaLightIndexToShadowParams[areaLightIndex]; // Check if shadow is valid: w != -1 and shadowStrength > 0 if (Mathf.Approximately(shadowParams.x, 0.0f) || Mathf.Approximately(shadowParams.w, -1.0f)) { continue; } int visibleLightIndex = m_AreaLightIndexToVisibleLightIndex[areaLightIndex]; if (visibleLightIndex < 0 || visibleLightIndex >= data.visibleLights.Length) { continue; } VisibleLight shadowLight = data.visibleLights[visibleLightIndex]; ShadowSliceData shadowSliceData = m_AreaLightsShadowSlices[globalShadowSliceIndex]; // Get nearPlane for this light (matching the logic in TryComputeAreaLightShadowSlice) Light unityLight = shadowLight.light; float nearPlane; if (unityLight != null && BGAreaLightManager.TryGetRegisteredAreaLight(unityLight, out var areaLightComp)) { nearPlane = areaLightComp.GetShadowNearPlane(); } else { nearPlane = unityLight != null ? unityLight.shadowNearPlane : 0.1f; } Vector4 shadowBias = AreaShadowUtils.GetRectangleLightShadowBias(ref shadowLight, ref m_ShadowDataCache, shadowSliceData.resolution, nearPlane, m_ShadowSettings.shadowFilter); if (globalShadowSliceIndex == 0 || !AreaShadowUtils.FastApproximately(shadowBias, lastShadowBias)) { SetShadowBias(cmd, shadowBias); lastShadowBias = shadowBias; } Vector3 lightPosition = shadowLight.localToWorldMatrix.GetColumn(3); SetLightPosition(cmd, lightPosition); RendererList shadowRendererList = data.shadowRendererListsHdl[globalShadowSliceIndex]; RenderShadowSlice(cmd, ref shadowSliceData, ref shadowRendererList, shadowSliceData.projectionMatrix, shadowSliceData.viewMatrix); anyShadowSliceRendered = true; } if (anyShadowSliceRendered) { SetupAreaLightsShadowReceiverConstants(cmd, data.allocatedShadowAtlasSize); cmd.SetViewProjectionMatrices(data.viewMatrix, data.projectionMatrix); } } } private void SetShadowBias(RasterCommandBuffer cmd, Vector4 shadowBias) { cmd.SetGlobalVector(AreaLightingShaderIDs._ShadowBias, shadowBias); } private void SetLightPosition(RasterCommandBuffer cmd, Vector3 lightPosition) { cmd.SetGlobalVector(AreaLightingShaderIDs._LightPosition, new Vector4(lightPosition.x, lightPosition.y, lightPosition.z, 1.0f)); } private void RenderShadowSlice(RasterCommandBuffer cmd, ref ShadowSliceData shadowSliceData, ref RendererList shadowRendererList, Matrix4x4 proj, Matrix4x4 view) { cmd.SetGlobalDepthBias(1.0f, 2.5f); cmd.SetViewport(new Rect(shadowSliceData.offsetX, shadowSliceData.offsetY, shadowSliceData.resolution, shadowSliceData.resolution)); cmd.SetViewProjectionMatrices(view, proj); if (shadowRendererList.isValid) { cmd.DrawRendererList(shadowRendererList); } cmd.DisableScissorRect(); cmd.SetGlobalDepthBias(0.0f, 0.0f); } } } #endif