From ca0a238c5b83b90ff299b02380cdd419c2b85106 Mon Sep 17 00:00:00 2001 From: FrancescoC-Unity Date: Thu, 4 Feb 2021 14:26:23 +0100 Subject: [PATCH 1/6] Fix WillFitAtlas + add API entry with light data itself. --- .../Lighting/Shadow/HDCachedShadowAtlas.cs | 59 +++++++++++++++---- .../Lighting/Shadow/HDCachedShadowManager.cs | 44 +++++++++++++- 2 files changed, 89 insertions(+), 14 deletions(-) diff --git a/com.unity.render-pipelines.high-definition/Runtime/Lighting/Shadow/HDCachedShadowAtlas.cs b/com.unity.render-pipelines.high-definition/Runtime/Lighting/Shadow/HDCachedShadowAtlas.cs index 6ad6d110b52..09264aca8f9 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/Lighting/Shadow/HDCachedShadowAtlas.cs +++ b/com.unity.render-pipelines.high-definition/Runtime/Lighting/Shadow/HDCachedShadowAtlas.cs @@ -30,11 +30,18 @@ struct CachedTransform internal Vector3 angles; // Only for area and spot } + enum SlotValue : byte + { + Free, + Occupied, + TempOccupied // Used when checking if it will fit. + } + private int m_AtlasResolutionInSlots; // Atlas Resolution / m_MinSlotSize private bool m_NeedOptimalPacking = true; // Whenever this is set to true, the pending lights are sorted before insertion. - private List m_AtlasSlots; // One entry per slot (of size m_MinSlotSize) true if occupied, false if free. + private List m_AtlasSlots; // One entry per slot (of size m_MinSlotSize) true if occupied, false if free. // Note: Some of these could be simple lists, but since we might need to search by index some of them and we want to avoid GC alloc, a dictionary is easier. // This also mean slightly worse performance, however hopefully the number of cached shadow lights is not huge at any tie. @@ -73,10 +80,10 @@ public override void InitAtlas(RenderPipelineResources renderPipelineResources, m_IsACacheForShadows = true; m_AtlasResolutionInSlots = HDUtils.DivRoundUp(width, m_MinSlotSize); - m_AtlasSlots = new List(m_AtlasResolutionInSlots * m_AtlasResolutionInSlots); + m_AtlasSlots = new List(m_AtlasResolutionInSlots * m_AtlasResolutionInSlots); for (int i = 0; i < m_AtlasResolutionInSlots * m_AtlasResolutionInSlots; ++i) { - m_AtlasSlots.Add(false); + m_AtlasSlots.Add(SlotValue.Free); } // Note: If changing the characteristics of the atlas via HDRP asset, the lights OnEnable will not be called again so we are missing them, however we can explicitly @@ -108,21 +115,26 @@ public void AddBlitRequestsForUpdatedShadows(HDDynamicShadowAtlas dynamicAtlas) // ------------------------------------------------------------------------------------------ private bool IsEntryEmpty(int x, int y) { - return (m_AtlasSlots[y * m_AtlasResolutionInSlots + x] == false); + return (m_AtlasSlots[y * m_AtlasResolutionInSlots + x] == SlotValue.Free); } private bool IsEntryFull(int x, int y) { - return (m_AtlasSlots[y * m_AtlasResolutionInSlots + x]); + return (m_AtlasSlots[y * m_AtlasResolutionInSlots + x]) != SlotValue.Free; + } + + private bool IsEntryTempOccupied(int x, int y) + { + return (m_AtlasSlots[y * m_AtlasResolutionInSlots + x]) == SlotValue.TempOccupied; } // Always fill slots in a square shape, for example : if x = 1 and y = 2, if numEntries = 2 it will fill {(1,2),(2,2),(1,3),(2,3)} private void FillEntries(int x, int y, int numEntries) { - MarkEntries(x, y, numEntries, true); + MarkEntries(x, y, numEntries, SlotValue.Occupied); } - private void MarkEntries(int x, int y, int numEntries, bool value) + private void MarkEntries(int x, int y, int numEntries, SlotValue value) { for (int j = y; j < y + numEntries; ++j) { @@ -149,10 +161,9 @@ private bool CheckSlotAvailability(int x, int y, int numEntries) return true; } - internal bool FindSlotInAtlas(int resolution, out int x, out int y) + internal bool FindSlotInAtlas(int resolution, bool tempFill, out int x, out int y) { int numEntries = HDUtils.DivRoundUp(resolution, m_MinSlotSize); - for (int j = 0; j < m_AtlasResolutionInSlots; ++j) { for (int i = 0; i < m_AtlasResolutionInSlots; ++i) @@ -161,6 +172,10 @@ internal bool FindSlotInAtlas(int resolution, out int x, out int y) { x = i; y = j; + + if (tempFill) + MarkEntries(x, y, numEntries, SlotValue.TempOccupied); + return true; } } @@ -171,6 +186,26 @@ internal bool FindSlotInAtlas(int resolution, out int x, out int y) return false; } + internal void FreeTempFilled(int x, int y, int resolution) + { + int numEntries = HDUtils.DivRoundUp(resolution, m_MinSlotSize); + for (int j = y; j < y + numEntries; ++j) + { + for (int i = x; i < x + numEntries; ++i) + { + if (m_AtlasSlots[j * m_AtlasResolutionInSlots + i] == SlotValue.TempOccupied) + { + m_AtlasSlots[j * m_AtlasResolutionInSlots + i] = SlotValue.Free; + } + } + } + } + + internal bool FindSlotInAtlas(int resolution, out int x, out int y) + { + return FindSlotInAtlas(resolution, false, out x, out y); + } + internal bool GetSlotInAtlas(int resolution, out int x, out int y) { if (FindSlotInAtlas(resolution, out x, out y)) @@ -242,7 +277,7 @@ internal void EvictLight(HDAdditionalLightData lightData) m_PlacedShadows.Remove(shadowIdx); m_ShadowsPendingRendering.Remove(shadowIdx); - MarkEntries((int)recordToRemove.offsetInAtlas.z, (int)recordToRemove.offsetInAtlas.w, HDUtils.DivRoundUp(recordToRemove.viewportSize, m_MinSlotSize), false); + MarkEntries((int)recordToRemove.offsetInAtlas.z, (int)recordToRemove.offsetInAtlas.w, HDUtils.DivRoundUp(recordToRemove.viewportSize, m_MinSlotSize), SlotValue.Free); m_CanTryPlacement = true; } } @@ -362,7 +397,7 @@ private bool PlaceMultipleShadows(int startIdx, int numberOfShadows) int numEntries = HDUtils.DivRoundUp(m_TempListForPlacement[startIdx].viewportSize, m_MinSlotSize); for (int j = 0; j < successfullyPlaced; ++j) { - MarkEntries(placements[j].x, placements[j].y, numEntries, false); + MarkEntries(placements[j].x, placements[j].y, numEntries, SlotValue.Free); } } @@ -446,7 +481,7 @@ internal void DefragmentAtlasAndReRender(HDShadowInitParameters initParams) for (int i = 0; i < m_AtlasResolutionInSlots * m_AtlasResolutionInSlots; ++i) { - m_AtlasSlots[i] = false; + m_AtlasSlots[i] = SlotValue.Free; } // Clear the other state lists. diff --git a/com.unity.render-pipelines.high-definition/Runtime/Lighting/Shadow/HDCachedShadowManager.cs b/com.unity.render-pipelines.high-definition/Runtime/Lighting/Shadow/HDCachedShadowManager.cs index d2736bd13ee..f1b61cef400 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/Lighting/Shadow/HDCachedShadowManager.cs +++ b/com.unity.render-pipelines.high-definition/Runtime/Lighting/Shadow/HDCachedShadowManager.cs @@ -22,6 +22,9 @@ public class HDCachedShadowManager private Vector3 m_CachedDirectionalForward; private Vector3 m_CachedDirectionalAngles; + // Helper array used to check what has been tmp filled. + private (int, int)[] m_TempFilled = new(int, int)[6]; + // Cached atlas internal HDCachedShadowAtlas punctualShadowAtlas; internal HDCachedShadowAtlas areaShadowAtlas; @@ -76,13 +79,34 @@ internal void PrintLightStatusInCachedAtlas() public bool WouldFitInAtlas(int shadowResolution, HDLightType lightType) { bool fits = true; - int x, y; + int x = 0; + int y = 0; if (lightType == HDLightType.Point) { + int fitted = 0; for (int i = 0; i < 6; ++i) { - fits = fits && HDShadowManager.cachedShadowManager.punctualShadowAtlas.FindSlotInAtlas(shadowResolution, out x, out y); + fits = fits && HDShadowManager.cachedShadowManager.punctualShadowAtlas.FindSlotInAtlas(shadowResolution, true, out x, out y); + if (fits) + { + m_TempFilled[fitted++] = (x, y); + } + else + { + // Free the temp filled ones. + for (int filled = 0; filled < fitted; ++filled) + { + HDShadowManager.cachedShadowManager.punctualShadowAtlas.FreeTempFilled(m_TempFilled[filled].Item1, m_TempFilled[filled].Item2, shadowResolution); + } + return false; + } + } + + // Free the temp filled ones. + for (int filled = 0; filled < fitted; ++filled) + { + HDShadowManager.cachedShadowManager.punctualShadowAtlas.FreeTempFilled(m_TempFilled[filled].Item1, m_TempFilled[filled].Item2, shadowResolution); } } @@ -95,6 +119,22 @@ public bool WouldFitInAtlas(int shadowResolution, HDLightType lightType) return fits; } + /// + /// This function verifies if the shadow map for the passed light would fit in the atlas when inserted. + /// + /// The light that we try to fit in the atlas. + /// True if the shadow map would fit in the atlas, false otherwise. If lightData does not cast shadows, false is returned. + public bool WouldFitInAtlas(HDAdditionalLightData lightData) + { + if (lightData.legacyLight.shadows != LightShadows.None) + { + var lightType = lightData.type; + var resolution = lightData.GetResolutionFromSettings(lightData.GetShadowMapType(lightType), m_InitParams); + return WouldFitInAtlas(resolution, lightType); + } + return false; + } + /// /// If a light is added after a scene is loaded, its placement in the atlas might be not optimal and the suboptimal placement might prevent a light to find a place in the atlas. /// This function will force a defragmentation of the atlas containing lights of type lightType and redistributes the shadows inside so that the placement is optimal. Note however that this will also mark the shadow maps From b9756415cc3268c0f0cdd4d429e4ae1b36ed84d8 Mon Sep 17 00:00:00 2001 From: FrancescoC-Unity Date: Thu, 4 Feb 2021 14:38:29 +0100 Subject: [PATCH 2/6] Light is placed API --- .../Lighting/Shadow/HDCachedShadowAtlas.cs | 6 ++++++ .../Lighting/Shadow/HDCachedShadowManager.cs | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/com.unity.render-pipelines.high-definition/Runtime/Lighting/Shadow/HDCachedShadowAtlas.cs b/com.unity.render-pipelines.high-definition/Runtime/Lighting/Shadow/HDCachedShadowAtlas.cs index 09264aca8f9..16d2b21f748 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/Lighting/Shadow/HDCachedShadowAtlas.cs +++ b/com.unity.render-pipelines.high-definition/Runtime/Lighting/Shadow/HDCachedShadowAtlas.cs @@ -530,6 +530,12 @@ internal bool ShadowIsPendingRendering(int shadowIdx) return m_ShadowsPendingRendering.ContainsKey(shadowIdx); } + internal bool LightIsPlaced(HDAdditionalLightData lightData) + { + int cachedShadowIdx = lightData.lightIdxForCachedShadows; + return cachedShadowIdx >= 0 && m_PlacedShadows.ContainsKey(cachedShadowIdx); + } + internal void ScheduleShadowUpdate(HDAdditionalLightData lightData) { if (!lightData.isActiveAndEnabled) return; diff --git a/com.unity.render-pipelines.high-definition/Runtime/Lighting/Shadow/HDCachedShadowManager.cs b/com.unity.render-pipelines.high-definition/Runtime/Lighting/Shadow/HDCachedShadowManager.cs index f1b61cef400..a6efd1e2073 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/Lighting/Shadow/HDCachedShadowManager.cs +++ b/com.unity.render-pipelines.high-definition/Runtime/Lighting/Shadow/HDCachedShadowManager.cs @@ -173,6 +173,24 @@ public void ForceRegisterLight(HDAdditionalLightData lightData) RegisterLight(lightData); } + /// + /// This function verifies if the light has its shadow maps placed in the cached shadow atlas. + /// + /// The light that we want to check the placement of. + /// True if the shadow map is already placed in the atlas, false otherwise. + public bool LightHasBeenPlacedInAtlas(HDAdditionalLightData lightData) + { + var lightType = lightData.type; + if (lightType == HDLightType.Area) + return instance.areaShadowAtlas.LightIsPlaced(lightData); + if (lightType == HDLightType.Point || lightType == HDLightType.Spot) + return instance.punctualShadowAtlas.LightIsPlaced(lightData); + if (lightType == HDLightType.Directional) + return !lightData.ShadowIsUpdatedEveryFrame(); + + return false; + } + // ------------------------------------------------------------------------------------------------------------------ private void MarkAllDirectionalShadowsForUpdate() From 08cd37180918278f2f21f0b751d63f7bb51dd97c Mon Sep 17 00:00:00 2001 From: FrancescoC-Unity Date: Thu, 4 Feb 2021 14:49:35 +0100 Subject: [PATCH 3/6] Changelog --- com.unity.render-pipelines.high-definition/CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/com.unity.render-pipelines.high-definition/CHANGELOG.md b/com.unity.render-pipelines.high-definition/CHANGELOG.md index 8894bdd2505..1f285642344 100644 --- a/com.unity.render-pipelines.high-definition/CHANGELOG.md +++ b/com.unity.render-pipelines.high-definition/CHANGELOG.md @@ -11,9 +11,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Added UV manipulation for Decals (edit mode). - Added color and intensity customization for Decals. - Added a history rejection criterion based on if the pixel was moving in world space (case 1302392). +- Added new API in CachedShadowManager ### Fixed +- Fixed WouldFitInAtlas that would previously return wrong results if any one face of a point light would fit (it used to return true even though the light in entirety wouldn't fit). + ### Changed - Removed the material pass probe volumes evaluation mode. - Unifying the history validation pass so that it is only done once for the whole frame and not per effect. From 8dfda16e100ee0f5320f5c76c4016bf1191cdf81 Mon Sep 17 00:00:00 2001 From: FrancescoC-Unity Date: Thu, 4 Feb 2021 18:34:50 +0100 Subject: [PATCH 4/6] Placed and rendered API --- .../Lighting/Shadow/HDCachedShadowAtlas.cs | 26 +++++++ .../Lighting/Shadow/HDCachedShadowManager.cs | 69 +++++++++++++++++++ 2 files changed, 95 insertions(+) diff --git a/com.unity.render-pipelines.high-definition/Runtime/Lighting/Shadow/HDCachedShadowAtlas.cs b/com.unity.render-pipelines.high-definition/Runtime/Lighting/Shadow/HDCachedShadowAtlas.cs index 16d2b21f748..f34fec4d4fc 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/Lighting/Shadow/HDCachedShadowAtlas.cs +++ b/com.unity.render-pipelines.high-definition/Runtime/Lighting/Shadow/HDCachedShadowAtlas.cs @@ -47,6 +47,7 @@ enum SlotValue : byte // This also mean slightly worse performance, however hopefully the number of cached shadow lights is not huge at any tie. private Dictionary m_PlacedShadows; private Dictionary m_ShadowsPendingRendering; + private Dictionary m_ShadowsWithValidData; // Shadows that have been placed and rendered at least once (OnDemand shadows are not rendered unless requested explicitly). It is a dictionary for fast access by shadow index. private Dictionary m_RegisteredLightDataPendingPlacement; private Dictionary m_RecordsPendingPlacement; // Note: this is different from m_RegisteredLightDataPendingPlacement because it contains records that were allocated in the system // but they lost their spot (e.g. post defrag). They don't have a light associated anymore if not by index, so we keep a separate collection. @@ -64,6 +65,7 @@ public HDCachedShadowAtlas(ShadowMapType type) { m_PlacedShadows = new Dictionary(s_InitialCapacity); m_ShadowsPendingRendering = new Dictionary(s_InitialCapacity); + m_ShadowsWithValidData = new Dictionary(s_InitialCapacity); m_TempListForPlacement = new List(s_InitialCapacity); m_RegisteredLightDataPendingPlacement = new Dictionary(s_InitialCapacity); @@ -276,6 +278,7 @@ internal void EvictLight(HDAdditionalLightData lightData) #endif m_PlacedShadows.Remove(shadowIdx); m_ShadowsPendingRendering.Remove(shadowIdx); + m_ShadowsWithValidData.Remove(shadowIdx); MarkEntries((int)recordToRemove.offsetInAtlas.z, (int)recordToRemove.offsetInAtlas.w, HDUtils.DivRoundUp(recordToRemove.viewportSize, m_MinSlotSize), SlotValue.Free); m_CanTryPlacement = true; @@ -487,6 +490,7 @@ internal void DefragmentAtlasAndReRender(HDShadowInitParameters initParams) // Clear the other state lists. m_PlacedShadows.Clear(); m_ShadowsPendingRendering.Clear(); + m_ShadowsWithValidData.Clear(); m_RecordsPendingPlacement.Clear(); // We'll reset what records are pending. // Sort in order to obtain a more optimal packing. @@ -530,6 +534,27 @@ internal bool ShadowIsPendingRendering(int shadowIdx) return m_ShadowsPendingRendering.ContainsKey(shadowIdx); } + internal bool ShadowHasRenderedAtLeastOnce(int shadowIdx) + { + return m_ShadowsWithValidData.ContainsKey(shadowIdx); + } + + internal bool FullLightShadowHasRenderedAtLeastOnce(HDAdditionalLightData lightData) + { + int cachedShadowIdx = lightData.lightIdxForCachedShadows; + if (lightData.type == HDLightType.Point) + { + bool allRendered = true; + for (int i = 0; i < 6; ++i) + { + allRendered = allRendered && m_ShadowsWithValidData.ContainsKey(cachedShadowIdx + i); + } + + return allRendered; + } + return m_ShadowsWithValidData.ContainsKey(cachedShadowIdx); + } + internal bool LightIsPlaced(HDAdditionalLightData lightData) { int cachedShadowIdx = lightData.lightIdxForCachedShadows; @@ -583,6 +608,7 @@ internal void MarkAsRendered(int shadowIdx) if (m_ShadowsPendingRendering.ContainsKey(shadowIdx)) { m_ShadowsPendingRendering.Remove(shadowIdx); + m_ShadowsWithValidData.Add(shadowIdx, shadowIdx); } } diff --git a/com.unity.render-pipelines.high-definition/Runtime/Lighting/Shadow/HDCachedShadowManager.cs b/com.unity.render-pipelines.high-definition/Runtime/Lighting/Shadow/HDCachedShadowManager.cs index a6efd1e2073..b8068d6bcf0 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/Lighting/Shadow/HDCachedShadowManager.cs +++ b/com.unity.render-pipelines.high-definition/Runtime/Lighting/Shadow/HDCachedShadowManager.cs @@ -19,6 +19,7 @@ public class HDCachedShadowManager // Data for cached directional light shadows. private const int m_MaxShadowCascades = 4; private bool[] m_DirectionalShadowPendingUpdate = new bool[m_MaxShadowCascades]; + private bool[] m_DirectionalShadowHasRendered = new bool[m_MaxShadowCascades]; private Vector3 m_CachedDirectionalForward; private Vector3 m_CachedDirectionalAngles; @@ -191,6 +192,70 @@ public bool LightHasBeenPlacedInAtlas(HDAdditionalLightData lightData) return false; } + /// + /// This function verifies if the light has its shadow maps placed in the cached shadow atlas and if it was rendered at least once. + /// + /// The light that we want to check. + /// Optional parameter required only when querying data about a directional light. It needs to match the number of cascades used by the directional light. + /// True if the shadow map is already placed in the atlas and rendered at least once, false otherwise. + public bool LightHasBeenPlaceAndRenderedAtLeastOnce(HDAdditionalLightData lightData, int numberOfCascades = 0) + { + var lightType = lightData.type; + if (lightType == HDLightType.Area) + { + return instance.areaShadowAtlas.LightIsPlaced(lightData) && instance.areaShadowAtlas.FullLightShadowHasRenderedAtLeastOnce(lightData); + } + if (lightType == HDLightType.Point || lightType == HDLightType.Spot) + { + return instance.punctualShadowAtlas.LightIsPlaced(lightData) && instance.punctualShadowAtlas.FullLightShadowHasRenderedAtLeastOnce(lightData); + } + if (lightType == HDLightType.Directional) + { + Debug.Assert(numberOfCascades < m_MaxShadowCascades, "numberOfCascades is bigger than the maximum cascades allowed"); + bool hasRendered = true; + for (int i = 0; i < numberOfCascades; ++i) + { + hasRendered = hasRendered && m_DirectionalShadowHasRendered[i]; + } + return !lightData.ShadowIsUpdatedEveryFrame() && hasRendered; + } + + return false; + } + + /// + /// This function verifies if the light if a specific sub-shadow maps is placed in the cached shadow atlas and if it was rendered at least once. + /// + /// The light that we want to check. + /// The sub-shadow index (e.g. cascade index or point light face). It is ignored when irrelevant to the light type. + /// True if the shadow map is already placed in the atlas and rendered at least once, false otherwise. + public bool ShadowHasBeenPlaceAndRenderedAtLeastOnce(HDAdditionalLightData lightData, int shadowIndex) + { + var lightType = lightData.type; + if (lightType == HDLightType.Area) + { + return instance.areaShadowAtlas.LightIsPlaced(lightData) && instance.areaShadowAtlas.ShadowHasRenderedAtLeastOnce(lightData.lightIdxForCachedShadows); + } + if (lightType == HDLightType.Spot) + { + return instance.punctualShadowAtlas.LightIsPlaced(lightData) && instance.punctualShadowAtlas.ShadowHasRenderedAtLeastOnce(lightData.lightIdxForCachedShadows); + } + if (lightType == HDLightType.Point || lightType == HDLightType.Spot) + { + if (lightType == HDLightType.Point) + Debug.Assert(shadowIndex < 6, "Shadow Index is bigger than the available sub-shadows"); + + return instance.punctualShadowAtlas.LightIsPlaced(lightData) && instance.punctualShadowAtlas.ShadowHasRenderedAtLeastOnce(lightData.lightIdxForCachedShadows + shadowIndex); + } + if (lightType == HDLightType.Directional) + { + Debug.Assert(shadowIndex < m_MaxShadowCascades, "Shadow Index is bigger than the maximum cascades allowed"); + return !lightData.ShadowIsUpdatedEveryFrame() && m_DirectionalShadowHasRendered[shadowIndex]; + } + + return false; + } + // ------------------------------------------------------------------------------------------------------------------ private void MarkAllDirectionalShadowsForUpdate() @@ -198,6 +263,7 @@ private void MarkAllDirectionalShadowsForUpdate() for (int i = 0; i < m_MaxShadowCascades; ++i) { m_DirectionalShadowPendingUpdate[i] = true; + m_DirectionalShadowHasRendered[i] = false; } } @@ -335,7 +401,10 @@ internal void MarkShadowAsRendered(int shadowIdx, ShadowMapType shadowMapType) if (shadowMapType == ShadowMapType.AreaLightAtlas) areaShadowAtlas.MarkAsRendered(shadowIdx); if (shadowMapType == ShadowMapType.CascadedDirectional) + { m_DirectionalShadowPendingUpdate[shadowIdx] = false; + m_DirectionalShadowHasRendered[shadowIdx] = true; + } } internal void UpdateResolutionRequest(ref HDShadowResolutionRequest request, int shadowIdx, ShadowMapType shadowMapType) From e3088c8b458ef876b5f5e7f749985e951efab45e Mon Sep 17 00:00:00 2001 From: FrancescoC-Unity Date: Mon, 22 Feb 2021 15:20:38 +0100 Subject: [PATCH 5/6] Fix assert --- .../Runtime/Lighting/Shadow/HDCachedShadowManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.render-pipelines.high-definition/Runtime/Lighting/Shadow/HDCachedShadowManager.cs b/com.unity.render-pipelines.high-definition/Runtime/Lighting/Shadow/HDCachedShadowManager.cs index b8068d6bcf0..29fea17419d 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/Lighting/Shadow/HDCachedShadowManager.cs +++ b/com.unity.render-pipelines.high-definition/Runtime/Lighting/Shadow/HDCachedShadowManager.cs @@ -211,7 +211,7 @@ public bool LightHasBeenPlaceAndRenderedAtLeastOnce(HDAdditionalLightData lightD } if (lightType == HDLightType.Directional) { - Debug.Assert(numberOfCascades < m_MaxShadowCascades, "numberOfCascades is bigger than the maximum cascades allowed"); + Debug.Assert(numberOfCascades <= m_MaxShadowCascades, "numberOfCascades is bigger than the maximum cascades allowed"); bool hasRendered = true; for (int i = 0; i < numberOfCascades; ++i) { From 04e4583d1db3e596a02866422423de0e30556805 Mon Sep 17 00:00:00 2001 From: FrancescoC-Unity Date: Mon, 22 Feb 2021 18:28:06 +0100 Subject: [PATCH 6/6] Doc update --- .../Documentation~/Shadows-in-HDRP.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/com.unity.render-pipelines.high-definition/Documentation~/Shadows-in-HDRP.md b/com.unity.render-pipelines.high-definition/Documentation~/Shadows-in-HDRP.md index b3276df5e2e..420dfca1a11 100644 --- a/com.unity.render-pipelines.high-definition/Documentation~/Shadows-in-HDRP.md +++ b/com.unity.render-pipelines.high-definition/Documentation~/Shadows-in-HDRP.md @@ -132,6 +132,8 @@ If the shadow atlas is full when a Light requests a spot, the cached shadow mana After a Scene loads with all the already placed Lights, if you add a new Light with cached shadows to the Scene, HDRP tries to place it in order to fill the holes in the atlas. However, depending on the order of insertion, the atlas may be fragmented and the holes available are not enough to place the Light's shadow map in. In this case, you can defragment the atlas to allow for additional Lights. To do this, pass the target atlas into the following function: `HDCachedShadowManager.instance.DefragAtlas` Note that this causes HDRP to mark all the shadow maps in the atlas as dirty which means HDRP renders them the moment their parent Light becomes visible. +It is possible to check if a light has its shadow maps has a placement in the cached shadow atlas `HDCachedShadowManager.instance.LightHasBeenPlacedInAtlas` and if it has been placed and rendered at least once with `HDCachedShadowManager.instance.LightHasBeenPlaceAndRenderedAtLeastOnce`. + ### Preserving shadow atlas placement If you disable the Light or change its **Update Mode** to **Every Frame**, the cached shadow manager unreserves the Light's shadow map's space in the cached shadow atlas and HDRP begins to render the Light's shadow map to the normal shadow atlases every frame. If the cached shadow manager needs to allocate space on the atlas for another Light, it can overwrite the space currently taken up by the original Light's shadow map.