diff --git a/Source/Engine/Level/Actors/DirectionalLight.cpp b/Source/Engine/Level/Actors/DirectionalLight.cpp
index 3a2a44e45..3d198e042 100644
--- a/Source/Engine/Level/Actors/DirectionalLight.cpp
+++ b/Source/Engine/Level/Actors/DirectionalLight.cpp
@@ -38,6 +38,8 @@ void DirectionalLight::Draw(RenderContext& renderContext)
data.VolumetricScatteringIntensity = VolumetricScatteringIntensity;
data.IndirectLightingIntensity = IndirectLightingIntensity;
data.CastVolumetricShadow = CastVolumetricShadow;
+ data.ShadowsUpdateRate = ShadowsUpdateRate;
+ data.ShadowsUpdateRateAtDistance = ShadowsUpdateRateAtDistance;
data.ShadowsMode = ShadowsMode;
data.CascadeCount = CascadeCount;
data.Cascade1Spacing = Cascade1Spacing;
diff --git a/Source/Engine/Level/Actors/Light.cpp b/Source/Engine/Level/Actors/Light.cpp
index 44e54f9c0..6a0dfa726 100644
--- a/Source/Engine/Level/Actors/Light.cpp
+++ b/Source/Engine/Level/Actors/Light.cpp
@@ -100,6 +100,8 @@ void LightWithShadow::Serialize(SerializeStream& stream, const void* otherObj)
SERIALIZE(ShadowsDepthBias);
SERIALIZE(ShadowsNormalOffsetScale);
SERIALIZE(ContactShadowsLength);
+ SERIALIZE(ShadowsUpdateRate);
+ SERIALIZE(ShadowsUpdateRateAtDistance);
}
void LightWithShadow::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
@@ -116,4 +118,6 @@ void LightWithShadow::Deserialize(DeserializeStream& stream, ISerializeModifier*
DESERIALIZE(ShadowsDepthBias);
DESERIALIZE(ShadowsNormalOffsetScale);
DESERIALIZE(ContactShadowsLength);
+ DESERIALIZE(ShadowsUpdateRate);
+ DESERIALIZE(ShadowsUpdateRateAtDistance);
}
diff --git a/Source/Engine/Level/Actors/Light.h b/Source/Engine/Level/Actors/Light.h
index ed12f6fbd..afe644c27 100644
--- a/Source/Engine/Level/Actors/Light.h
+++ b/Source/Engine/Level/Actors/Light.h
@@ -128,6 +128,18 @@ public:
API_FIELD(Attributes="EditorOrder(99), EditorDisplay(\"Shadow\"), Limit(0.0f, 0.1f, 0.001f)")
float ContactShadowsLength = 0.0f;
+ ///
+ /// Frequency of shadow updates. 1 - every frame, 0.5 - every second frame, 0 - on start or change. It's the inverse value of how many frames should happen in-between shadow map updates (eg. inverse of 0.5 is 2 thus shadow will update every 2nd frame).
+ ///
+ API_FIELD(Attributes="EditorOrder(100), EditorDisplay(\"Shadow\", \"Update Rate\"), Limit(0.0f, 1.0f)")
+ float ShadowsUpdateRate = 1.0f;
+
+ ///
+ /// Frequency of shadow updates at the maximum distance from the view at which shadows are still rendered. This value is multiplied by ShadowsUpdateRate and allows scaling the update rate in-between the shadow range. For example, if light is near view, it will get normal shadow updates but will reduce this rate when far from view. See ShadowsUpdateRate to learn more.
+ ///
+ API_FIELD(Attributes="EditorOrder(105), EditorDisplay(\"Shadow\", \"Update Rate At Distance\"), Limit(0.0f, 1.0f)")
+ float ShadowsUpdateRateAtDistance = 0.5f;
+
///
/// Describes how a visual element casts shadows.
///
diff --git a/Source/Engine/Level/Actors/PointLight.cpp b/Source/Engine/Level/Actors/PointLight.cpp
index ff12f8dfd..626008a54 100644
--- a/Source/Engine/Level/Actors/PointLight.cpp
+++ b/Source/Engine/Level/Actors/PointLight.cpp
@@ -103,6 +103,8 @@ void PointLight::Draw(RenderContext& renderContext)
data.ShadowsSharpness = ShadowsSharpness;
data.VolumetricScatteringIntensity = VolumetricScatteringIntensity;
data.CastVolumetricShadow = CastVolumetricShadow;
+ data.ShadowsUpdateRate = ShadowsUpdateRate;
+ data.ShadowsUpdateRateAtDistance = ShadowsUpdateRateAtDistance;
data.ShadowsMode = ShadowsMode;
data.Radius = radius;
data.FallOffExponent = FallOffExponent;
diff --git a/Source/Engine/Level/Actors/SpotLight.cpp b/Source/Engine/Level/Actors/SpotLight.cpp
index ab9cde364..f2b6046ee 100644
--- a/Source/Engine/Level/Actors/SpotLight.cpp
+++ b/Source/Engine/Level/Actors/SpotLight.cpp
@@ -153,6 +153,8 @@ void SpotLight::Draw(RenderContext& renderContext)
data.ShadowsSharpness = ShadowsSharpness;
data.VolumetricScatteringIntensity = VolumetricScatteringIntensity;
data.CastVolumetricShadow = CastVolumetricShadow;
+ data.ShadowsUpdateRate = ShadowsUpdateRate;
+ data.ShadowsUpdateRateAtDistance = ShadowsUpdateRateAtDistance;
data.ShadowsMode = ShadowsMode;
data.Radius = radius;
data.FallOffExponent = FallOffExponent;
diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp
index d7441c67d..f0cf76ce4 100644
--- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp
+++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp
@@ -391,7 +391,9 @@ void ParticleEmitterGraphCPUExecutor::Draw(ParticleEmitter* emitter, ParticleEff
RenderPointLightData lightData;
lightData.MinRoughness = 0.04f;
lightData.ShadowsDistance = 2000.0f;
- lightData.ShadowsStrength = 1.0f;
+ lightData.ShadowsStrength = 0.0f;
+ lightData.ShadowsUpdateRate = 1.0f;
+ lightData.ShadowsUpdateRateAtDistance = 0.5f;
lightData.Direction = Float3::Forward;
lightData.ShadowsFadeDistance = 50.0f;
lightData.ShadowsNormalOffsetScale = 10.0f;
diff --git a/Source/Engine/Renderer/RenderList.cpp b/Source/Engine/Renderer/RenderList.cpp
index 8f07180f3..4039c5565 100644
--- a/Source/Engine/Renderer/RenderList.cpp
+++ b/Source/Engine/Renderer/RenderList.cpp
@@ -80,7 +80,7 @@ bool RenderLocalLightData::CanRenderShadow(const RenderView& view) const
const float fadeDistance = Math::Max(ShadowsFadeDistance, 0.1f);
const float dstLightToView = Float3::Distance(Position, view.Position);
const float fade = 1 - Math::Saturate((dstLightToView - Radius - ShadowsDistance + fadeDistance) / fadeDistance);
- return fade > ZeroTolerance && RenderLightData::CanRenderShadow(view);
+ return fade > ZeroTolerance && Radius > 10 && RenderLightData::CanRenderShadow(view);
}
void RenderSpotLightData::SetShaderData(ShaderLightData& data, bool useShadow) const
diff --git a/Source/Engine/Renderer/RenderList.h b/Source/Engine/Renderer/RenderList.h
index 3a7ab373e..e84660ced 100644
--- a/Source/Engine/Renderer/RenderList.h
+++ b/Source/Engine/Renderer/RenderList.h
@@ -56,6 +56,9 @@ struct RenderLightData
float ScreenSize;
uint32 ShadowsBufferAddress;
+ float ShadowsUpdateRate;
+ float ShadowsUpdateRateAtDistance;
+
RenderLightData()
{
Platform::MemoryClear(this, sizeof(RenderLightData));
diff --git a/Source/Engine/Renderer/ShadowsPass.cpp b/Source/Engine/Renderer/ShadowsPass.cpp
index 7da06041c..5f129d0be 100644
--- a/Source/Engine/Renderer/ShadowsPass.cpp
+++ b/Source/Engine/Renderer/ShadowsPass.cpp
@@ -18,7 +18,8 @@
#include "Engine/Renderer/Lightmaps.h"
#endif
-#define MaxTiles 6
+#define SHADOWS_MAX_TILES 6
+#define SHADOWS_MIN_RESOLUTION 16
#define NormalOffsetScaleTweak 100.0f
#define LocalLightNearPlane 10.0f
@@ -33,10 +34,10 @@ PACK_STRUCT(struct Data{
float ContactShadowsLength;
});
-struct ShadowsAtlasTile : RectPack
+struct ShadowsAtlasRectTile : RectPack
{
- ShadowsAtlasTile(uint16 x, uint16 y, uint16 width, uint16 height)
- : RectPack(x, y, width, height)
+ ShadowsAtlasRectTile(uint16 x, uint16 y, uint16 width, uint16 height)
+ : RectPack(x, y, width, height)
{
}
@@ -56,26 +57,25 @@ uint16 QuantizeResolution(float input)
return output;
}
-struct ShadowAtlasLight
+// State for shadow projection
+struct ShadowAtlasLightTile
{
- uint64 LastFrameUsed;
- int32 ContextIndex;
- int32 ContextCount;
- uint16 Resolution;
- uint16 TilesNeeded;
- float Sharpness, Fade, NormalOffsetScale, Bias, FadeDistance;
- Float4 CascadeSplits;
- ShadowsAtlasTile* Tiles[MaxTiles];
- Matrix WorldToShadow[MaxTiles];
+ ShadowsAtlasRectTile* RectTile;
+ Matrix WorldToShadow;
+ float FramesToUpdate; // Amount of frames (with fraction) until the next shadow update can happen
+ bool SkipUpdate;
+ Viewport CachedViewport; // The viewport used the last time to render shadow to the atlas
- ShadowAtlasLight()
+ void Free(ShadowsCustomBuffer* buffer)
{
- Platform::MemoryClear(this, sizeof(ShadowAtlasLight));
+ if (RectTile)
+ {
+ RectTile->Free(buffer);
+ RectTile = nullptr;
+ }
}
- POD_COPYABLE(ShadowAtlasLight);
-
- void SetWorldToShadow(int32 index, const Matrix& shadowViewProjection)
+ void SetWorldToShadow(const Matrix& shadowViewProjection)
{
// Transform Clip Space [-1,+1]^2 to UV Space [0,1]^2 (saves MAD instruction in shader)
const Matrix ClipToUV(
@@ -85,7 +85,120 @@ struct ShadowAtlasLight
0.5f, 0.5f, 0.0f, 1.0f);
Matrix m;
Matrix::Multiply(shadowViewProjection, ClipToUV, m);
- Matrix::Transpose(m, WorldToShadow[index]);
+ Matrix::Transpose(m, WorldToShadow);
+ }
+};
+
+// State for shadow cache sed to invalidate any prerendered shadow depths
+struct ShadowAtlasLightCache
+{
+ bool Valid;
+ float ShadowsUpdateRate;
+ float ShadowsUpdateRateAtDistance;
+ Float3 Position;
+ float Radius;
+ Float3 Direction;
+ float Distance;
+ Float4 CascadeSplits;
+
+ void Set(const RenderView& view, const RenderLightData& light, const Float4& cascadeSplits = Float4::Zero)
+ {
+ Valid = true;
+ Distance = light.ShadowsDistance;
+ ShadowsUpdateRate = light.ShadowsUpdateRate;
+ ShadowsUpdateRateAtDistance = light.ShadowsUpdateRateAtDistance;
+ if (light.IsDirectionalLight)
+ {
+ // Sun
+ Position = view.Position;
+ Direction = light.Direction;
+ CascadeSplits = cascadeSplits;
+ }
+ else
+ {
+ // Local light
+ const auto& localLight = (const RenderLocalLightData&)light;
+ Position = light.Position;
+ Radius = localLight.Radius;
+ }
+ }
+};
+
+// State for light's shadows rendering
+struct ShadowAtlasLight
+{
+ uint64 LastFrameUsed;
+ int32 ContextIndex;
+ int32 ContextCount;
+ uint16 Resolution;
+ uint8 TilesNeeded;
+ uint8 TilesCount;
+ float Sharpness, Fade, NormalOffsetScale, Bias, FadeDistance, Distance;
+ Float4 CascadeSplits;
+ ShadowAtlasLightTile Tiles[SHADOWS_MAX_TILES];
+ ShadowAtlasLightCache Cache;
+
+ ShadowAtlasLight()
+ {
+ Platform::MemoryClear(this, sizeof(ShadowAtlasLight));
+ }
+
+ POD_COPYABLE(ShadowAtlasLight);
+
+ float CalculateUpdateRateInv(const RenderLightData& light, float distanceFromView, bool& freezeUpdate) const
+ {
+ const float shadowsUpdateRate = light.ShadowsUpdateRate;
+ const float shadowsUpdateRateAtDistance = shadowsUpdateRate * light.ShadowsUpdateRateAtDistance;
+ float updateRate = Math::Lerp(shadowsUpdateRate, shadowsUpdateRateAtDistance, Math::Saturate(distanceFromView / Distance));
+ // TODO: add global shadows update rate scale to be adjusted per-platform
+ freezeUpdate = updateRate <= ZeroTolerance;
+ if (freezeUpdate)
+ return 0.0f;
+ return 1.0f / updateRate;
+ }
+
+ void ValidateCache(const RenderView& view, const RenderLightData& light)
+ {
+ if (!Cache.Valid)
+ return;
+ if (!Math::NearEqual(Cache.Distance, light.ShadowsDistance) ||
+ !Math::NearEqual(Cache.ShadowsUpdateRate, light.ShadowsUpdateRate) ||
+ !Math::NearEqual(Cache.ShadowsUpdateRateAtDistance, light.ShadowsUpdateRateAtDistance))
+ {
+ // Invalidate
+ Cache.Valid = false;
+ }
+ if (light.IsDirectionalLight)
+ {
+ // Sun
+ if (Float3::Dot(Cache.Direction, light.Direction) < 0.999999f ||
+ !Float3::NearEqual(Cache.Position, view.Position, 1.0f) ||
+ !Float4::NearEqual(Cache.CascadeSplits, CascadeSplits))
+ {
+ // Invalidate
+ Cache.Valid = false;
+ }
+ }
+ else
+ {
+ // Local light
+ const auto& localLight = (const RenderLocalLightData&)light;
+ if (!Float3::NearEqual(Cache.Position, light.Position, 1.0f) ||
+ !Math::NearEqual(Cache.Radius, localLight.Radius))
+ {
+ // Invalidate
+ Cache.Valid = false;
+ }
+ }
+ for (int32 i = 0; i < TilesCount && Cache.Valid; i++)
+ {
+ auto& tile = Tiles[i];
+ if (tile.CachedViewport != Viewport(tile.RectTile->X, tile.RectTile->Y, tile.RectTile->Width, tile.RectTile->Height))
+ {
+ // Invalidate
+ Cache.Valid = false;
+ }
+ }
}
};
@@ -99,7 +212,7 @@ public:
GPUTexture* ShadowMapAtlas = nullptr;
DynamicTypedBuffer ShadowsBuffer;
GPUBufferView* ShadowsBufferView = nullptr;
- ShadowsAtlasTile* AtlasTiles = nullptr; // TODO: optimize with a single allocation for atlas tiles
+ ShadowsAtlasRectTile* AtlasTiles = nullptr; // TODO: optimize with a single allocation for atlas tiles
Dictionary Lights;
ShadowsCustomBuffer()
@@ -117,6 +230,7 @@ public:
{
auto& atlasLight = it->Value;
Platform::MemoryClear(atlasLight.Tiles, sizeof(atlasLight.Tiles));
+ Platform::MemoryClear(&atlasLight.Cache, sizeof(atlasLight.Cache));
}
}
@@ -134,12 +248,12 @@ public:
}
};
-void ShadowsAtlasTile::OnInsert(ShadowsCustomBuffer* buffer)
+void ShadowsAtlasRectTile::OnInsert(ShadowsCustomBuffer* buffer)
{
buffer->AtlasPixelsUsed += (int32)Width * (int32)Height;
}
-void ShadowsAtlasTile::OnFree(ShadowsCustomBuffer* buffer)
+void ShadowsAtlasRectTile::OnFree(ShadowsCustomBuffer* buffer)
{
buffer->AtlasPixelsUsed -= (int32)Width * (int32)Height;
}
@@ -262,6 +376,7 @@ void ShadowsPass::SetupRenderContext(RenderContext& renderContext, RenderContext
shadowContext.List = RenderList::GetFromPool();
shadowContext.Buffers = renderContext.Buffers;
shadowContext.Task = renderContext.Task;
+ shadowContext.List->Clear();
}
void ShadowsPass::SetupLight(RenderContext& renderContext, RenderContextBatch& renderContextBatch, RenderLightData& light, ShadowAtlasLight& atlasLight)
@@ -272,17 +387,47 @@ void ShadowsPass::SetupLight(RenderContext& renderContext, RenderContextBatch& r
atlasLight.NormalOffsetScale = light.ShadowsNormalOffsetScale * NormalOffsetScaleTweak * (1.0f / (float)atlasLight.Resolution);
atlasLight.Bias = light.ShadowsDepthBias;
atlasLight.FadeDistance = Math::Max(light.ShadowsFadeDistance, 0.1f);
+ atlasLight.Distance = Math::Min(renderContext.View.Far, light.ShadowsDistance);
}
-void ShadowsPass::SetupLight(RenderContext& renderContext, RenderContextBatch& renderContextBatch, RenderLocalLightData& light, ShadowAtlasLight& atlasLight)
+bool ShadowsPass::SetupLight(RenderContext& renderContext, RenderContextBatch& renderContextBatch, RenderLocalLightData& light, ShadowAtlasLight& atlasLight)
{
SetupLight(renderContext, renderContextBatch, (RenderLightData&)light, atlasLight);
// Fade shadow on distance
const float fadeDistance = Math::Max(light.ShadowsFadeDistance, 0.1f);
- const float dstLightToView = Float3::Distance(light.Position, renderContext.View.Position);
- const float fade = 1 - Math::Saturate((dstLightToView - light.Radius - light.ShadowsDistance + fadeDistance) / fadeDistance);
+ const float dstLightToView = Float3::Distance(light.Position, renderContext.View.Position) - light.Radius;
+ const float fade = 1 - Math::Saturate((dstLightToView - atlasLight.Distance + fadeDistance) / fadeDistance);
atlasLight.Fade *= fade;
+
+ // Update cached state (invalidate it if the light changed)
+ atlasLight.ValidateCache(renderContext.View, light);
+
+ // Calculate update rate based on the distance to the view
+ bool freezeUpdate;
+ const float updateRateInv = atlasLight.CalculateUpdateRateInv(light, dstLightToView, freezeUpdate);
+ float& framesToUpdate = atlasLight.Tiles[0].FramesToUpdate; // Use the first tile for all local light projections to be in sync
+ if ((framesToUpdate > 0.0f || freezeUpdate) && atlasLight.Cache.Valid)
+ {
+ // Light state matches the cached state and the update rate allows us to reuse the cached shadow map so skip update
+ if (!freezeUpdate)
+ framesToUpdate -= 1.0f;
+ for (auto& tile : atlasLight.Tiles)
+ tile.SkipUpdate = true;
+ return true;
+ }
+ framesToUpdate += updateRateInv - 1.0f;
+
+ // Cache the current state
+ atlasLight.Cache.Set(renderContext.View, light);
+ for (int32 i = 0; i < atlasLight.TilesCount; i++)
+ {
+ auto& tile = atlasLight.Tiles[i];
+ tile.SkipUpdate = false;
+ tile.CachedViewport = Viewport(tile.RectTile->X, tile.RectTile->Y, tile.RectTile->Width, tile.RectTile->Height);
+ }
+
+ return false;
}
void ShadowsPass::SetupLight(RenderContext& renderContext, RenderContextBatch& renderContextBatch, RenderDirectionalLightData& light, ShadowAtlasLight& atlasLight)
@@ -290,29 +435,16 @@ void ShadowsPass::SetupLight(RenderContext& renderContext, RenderContextBatch& r
SetupLight(renderContext, renderContextBatch, (RenderLightData&)light, atlasLight);
const RenderView& view = renderContext.View;
- auto mainCache = renderContext.List;
- Float3 lightDirection = light.Direction;
- float shadowsDistance = Math::Min(view.Far, light.ShadowsDistance);
- int32 csmCount = Math::Clamp(light.CascadeCount, 0, MAX_CSM_CASCADES);
+ const int32 csmCount = atlasLight.TilesCount;
const auto shadowMapsSize = (float)atlasLight.Resolution;
- // Views with orthographic cameras cannot use cascades, we force it to 1 shadow map here
- if (view.Projection.M44 == 1.0f)
- csmCount = 1;
-
// Calculate cascade splits
- auto cameraNear = view.Near;
- auto cameraFar = view.Far;
- auto cameraRange = cameraFar - cameraNear;
- float minDistance;
- float maxDistance;
+ const float minDistance = view.Near;
+ const float maxDistance = view.Near + atlasLight.Distance;
+ const float viewRange = view.Far - view.Near;
float cascadeSplits[MAX_CSM_CASCADES];
{
- minDistance = cameraNear;
- maxDistance = cameraNear + shadowsDistance;
-
PartitionMode partitionMode = light.PartitionMode;
- float pssmFactor = 0.5f;
float splitDistance0 = light.Cascade1Spacing;
float splitDistance1 = Math::Max(splitDistance0, light.Cascade2Spacing);
float splitDistance2 = Math::Max(splitDistance1, light.Cascade3Spacing);
@@ -346,8 +478,8 @@ void ShadowsPass::SetupLight(RenderContext& renderContext, RenderContextBatch& r
}
else if (partitionMode == PartitionMode::Logarithmic || partitionMode == PartitionMode::PSSM)
{
+ const float pssmFactor = 0.5f;
const float lambda = partitionMode == PartitionMode::PSSM ? pssmFactor : 1.0f;
-
const auto range = maxDistance - minDistance;
const auto ratio = maxDistance / minDistance;
const auto logRatio = Math::Clamp(1.0f - lambda, 0.0f, 1.0f);
@@ -355,7 +487,7 @@ void ShadowsPass::SetupLight(RenderContext& renderContext, RenderContextBatch& r
{
// Compute cascade split (between znear and zfar)
const float distribute = static_cast(cascadeLevel + 1) / csmCount;
- float logZ = static_cast(minDistance * powf(ratio, distribute));
+ float logZ = minDistance * Math::Pow(ratio, distribute);
float uniformZ = minDistance + range * distribute;
cascadeSplits[cascadeLevel] = Math::Lerp(uniformZ, logZ, logRatio);
}
@@ -363,9 +495,44 @@ void ShadowsPass::SetupLight(RenderContext& renderContext, RenderContextBatch& r
// Convert distance splits to ratios cascade in the range [0, 1]
for (int32 i = 0; i < MAX_CSM_CASCADES; i++)
- cascadeSplits[i] = (cascadeSplits[i] - cameraNear) / cameraRange;
+ cascadeSplits[i] = (cascadeSplits[i] - view.Near) / viewRange;
}
- atlasLight.CascadeSplits = view.Near + Float4(cascadeSplits) * cameraRange;
+ atlasLight.CascadeSplits = view.Near + Float4(cascadeSplits) * viewRange;
+
+ // Update cached state (invalidate it if the light changed)
+ atlasLight.ValidateCache(renderContext.View, light);
+
+ // Update cascades to check which should be updated this frame
+ atlasLight.ContextIndex = renderContextBatch.Contexts.Count();
+ atlasLight.ContextCount = 0;
+ for (int32 cascadeIndex = 0; cascadeIndex < csmCount; cascadeIndex++)
+ {
+ const float dstToCascade = atlasLight.CascadeSplits.Raw[cascadeIndex];
+ bool freezeUpdate;
+ const float updateRateInv = atlasLight.CalculateUpdateRateInv(light, dstToCascade, freezeUpdate);
+ auto& tile = atlasLight.Tiles[cascadeIndex];
+ if ((tile.FramesToUpdate > 0.0f || freezeUpdate) && atlasLight.Cache.Valid)
+ {
+ // Light state matches the cached state and the update rate allows us to reuse the cached shadow map so skip update
+ if (!freezeUpdate)
+ tile.FramesToUpdate -= 1.0f;
+ tile.SkipUpdate = true;
+ continue;
+ }
+ tile.FramesToUpdate += updateRateInv - 1.0f;
+
+ // Cache the current state
+ tile.SkipUpdate = false;
+ tile.CachedViewport = Viewport(tile.RectTile->X, tile.RectTile->Y, tile.RectTile->Width, tile.RectTile->Height);
+ atlasLight.ContextCount++;
+ }
+
+ // Init shadow data
+ atlasLight.ContextIndex = renderContextBatch.Contexts.Count();
+ if (atlasLight.ContextCount == 0)
+ return;
+ renderContextBatch.Contexts.AddDefault(atlasLight.ContextCount);
+ atlasLight.Cache.Set(renderContext.View, light, atlasLight.CascadeSplits);
// Select best Up vector
Float3 side = Float3::UnitX;
@@ -374,37 +541,34 @@ void ShadowsPass::SetupLight(RenderContext& renderContext, RenderContextBatch& r
for (int32 i = 0; i < ARRAY_COUNT(vectorUps); i++)
{
const Float3 vectorUp = vectorUps[i];
- if (Math::Abs(Float3::Dot(lightDirection, vectorUp)) < (1.0f - 0.0001f))
+ if (Math::Abs(Float3::Dot(light.Direction, vectorUp)) < (1.0f - 0.0001f))
{
- side = Float3::Normalize(Float3::Cross(vectorUp, lightDirection));
- upDirection = Float3::Normalize(Float3::Cross(lightDirection, side));
+ side = Float3::Normalize(Float3::Cross(vectorUp, light.Direction));
+ upDirection = Float3::Normalize(Float3::Cross(light.Direction, side));
break;
}
}
- // Temporary data
- Float3 frustumCorners[8];
- Matrix shadowView, shadowProjection, shadowVP;
-
- // Init shadow data
- atlasLight.ContextIndex = renderContextBatch.Contexts.Count();
- atlasLight.ContextCount = csmCount;
- renderContextBatch.Contexts.AddDefault(atlasLight.ContextCount);
-
// Create the different view and projection matrices for each split
float splitMinRatio = 0;
- float splitMaxRatio = (minDistance - cameraNear) / cameraRange;
+ float splitMaxRatio = (minDistance - view.Near) / viewRange;
+ int32 contextIndex = 0;
for (int32 cascadeIndex = 0; cascadeIndex < csmCount; cascadeIndex++)
{
- // Cascade splits
const auto oldSplitMinRatio = splitMinRatio;
splitMinRatio = splitMaxRatio;
splitMaxRatio = cascadeSplits[cascadeIndex];
+ auto& tile = atlasLight.Tiles[cascadeIndex];
+ if (tile.SkipUpdate)
+ continue;
+
// Calculate cascade split frustum corners in view space
+ Float3 frustumCorners[8];
for (int32 j = 0; j < 4; j++)
{
float overlap = 0.1f * (splitMinRatio - oldSplitMinRatio); // CSM blending overlap
+ const RenderList* mainCache = renderContext.List;
const auto frustumRangeVS = mainCache->FrustumCornersVs[j + 4] - mainCache->FrustumCornersVs[j];
frustumCorners[j] = mainCache->FrustumCornersVs[j] + frustumRangeVS * (splitMinRatio - overlap);
frustumCorners[j + 4] = mainCache->FrustumCornersVs[j] + frustumRangeVS * splitMaxRatio;
@@ -438,8 +602,8 @@ void ShadowsPass::SetupLight(RenderContext& renderContext, RenderContextBatch& r
float shadowMapHalfSize = shadowMapsSize * 0.5f;
float x = Math::Ceil(Float3::Dot(target, upDirection) * shadowMapHalfSize / boundingVSRadius) * boundingVSRadius / shadowMapHalfSize;
float y = Math::Ceil(Float3::Dot(target, side) * shadowMapHalfSize / boundingVSRadius) * boundingVSRadius / shadowMapHalfSize;
- float z = Float3::Dot(target, lightDirection);
- target = upDirection * x + side * y + lightDirection * z;
+ float z = Float3::Dot(target, light.Direction);
+ target = upDirection * x + side * y + light.Direction * z;
}
}
@@ -447,7 +611,8 @@ void ShadowsPass::SetupLight(RenderContext& renderContext, RenderContextBatch& r
const auto farClip = cascadeMaxBoundLS.Z - cascadeMinBoundLS.Z;
// Create shadow view matrix
- Matrix::LookAt(target - lightDirection * cascadeMaxBoundLS.Z, target, upDirection, shadowView);
+ Matrix shadowView, shadowProjection, shadowVP;
+ Matrix::LookAt(target - light.Direction * cascadeMaxBoundLS.Z, target, upDirection, shadowView);
// Create viewport for culling with extended near/far planes due to culling issues
Matrix cullingVP;
@@ -474,14 +639,13 @@ void ShadowsPass::SetupLight(RenderContext& renderContext, RenderContextBatch& r
Matrix::Multiply(shadowView, shadowProjection, shadowVP);
}
- atlasLight.SetWorldToShadow(cascadeIndex, shadowVP);
+ tile.SetWorldToShadow(shadowVP);
// Setup context for cascade
- auto& shadowContext = renderContextBatch.Contexts[atlasLight.ContextIndex + cascadeIndex];
+ auto& shadowContext = renderContextBatch.Contexts[atlasLight.ContextIndex + contextIndex++];
SetupRenderContext(renderContext, shadowContext);
- shadowContext.List->Clear();
- shadowContext.View.Position = -lightDirection * shadowsDistance + view.Position;
- shadowContext.View.Direction = lightDirection;
+ shadowContext.View.Position = light.Direction * -atlasLight.Distance + view.Position;
+ shadowContext.View.Direction = light.Direction;
shadowContext.View.SetUp(shadowView, shadowProjection);
shadowContext.View.CullingFrustum.SetMatrix(cullingVP);
shadowContext.View.PrepareCache(shadowContext, shadowMapsSize, shadowMapsSize, Float2::Zero, &view);
@@ -490,54 +654,40 @@ void ShadowsPass::SetupLight(RenderContext& renderContext, RenderContextBatch& r
void ShadowsPass::SetupLight(RenderContext& renderContext, RenderContextBatch& renderContextBatch, RenderPointLightData& light, ShadowAtlasLight& atlasLight)
{
- SetupLight(renderContext, renderContextBatch, (RenderLocalLightData&)light, atlasLight);
+ if (SetupLight(renderContext, renderContextBatch, (RenderLocalLightData&)light, atlasLight))
+ return;
- // Init shadow data
+ // Render depth to all 6 faces of the cube map
atlasLight.ContextIndex = renderContextBatch.Contexts.Count();
atlasLight.ContextCount = 6;
renderContextBatch.Contexts.AddDefault(atlasLight.ContextCount);
-
- const auto& view = renderContext.View;
- const auto shadowMapsSize = (float)atlasLight.Resolution;
-
- // Fade shadow on distance
- const float fadeDistance = Math::Max(light.ShadowsFadeDistance, 0.1f);
- const float dstLightToView = Float3::Distance(light.Position, view.Position);
- const float fade = 1 - Math::Saturate((dstLightToView - light.Radius - light.ShadowsDistance + fadeDistance) / fadeDistance);
- atlasLight.Fade *= fade;
-
- // Render depth to all 6 faces of the cube map
for (int32 faceIndex = 0; faceIndex < 6; faceIndex++)
{
auto& shadowContext = renderContextBatch.Contexts[atlasLight.ContextIndex + faceIndex];
SetupRenderContext(renderContext, shadowContext);
- shadowContext.List->Clear();
shadowContext.View.SetUpCube(LocalLightNearPlane, light.Radius, light.Position);
shadowContext.View.SetFace(faceIndex);
- shadowContext.View.PrepareCache(shadowContext, shadowMapsSize, shadowMapsSize, Float2::Zero, &view);
- atlasLight.SetWorldToShadow(faceIndex, shadowContext.View.ViewProjection());
+ const auto shadowMapsSize = (float)atlasLight.Resolution;
+ shadowContext.View.PrepareCache(shadowContext, shadowMapsSize, shadowMapsSize, Float2::Zero, &renderContext.View);
+ atlasLight.Tiles[faceIndex].SetWorldToShadow(shadowContext.View.ViewProjection());
}
}
void ShadowsPass::SetupLight(RenderContext& renderContext, RenderContextBatch& renderContextBatch, RenderSpotLightData& light, ShadowAtlasLight& atlasLight)
{
- SetupLight(renderContext, renderContextBatch, (RenderLocalLightData&)light, atlasLight);
+ if (SetupLight(renderContext, renderContextBatch, (RenderLocalLightData&)light, atlasLight))
+ return;
- // Init shadow data
+ // Render depth to a single projection
atlasLight.ContextIndex = renderContextBatch.Contexts.Count();
atlasLight.ContextCount = 1;
renderContextBatch.Contexts.AddDefault(atlasLight.ContextCount);
-
- const auto& view = renderContext.View;
- const auto shadowMapsSize = (float)atlasLight.Resolution;
-
- // Render depth to a single projection
auto& shadowContext = renderContextBatch.Contexts[atlasLight.ContextIndex];
SetupRenderContext(renderContext, shadowContext);
- shadowContext.List->Clear();
shadowContext.View.SetProjector(LocalLightNearPlane, light.Radius, light.Position, light.Direction, light.UpVector, light.OuterConeAngle * 2.0f);
- shadowContext.View.PrepareCache(shadowContext, shadowMapsSize, shadowMapsSize, Float2::Zero, &view);
- atlasLight.SetWorldToShadow(0, shadowContext.View.ViewProjection());
+ const auto shadowMapsSize = (float)atlasLight.Resolution;
+ shadowContext.View.PrepareCache(shadowContext, shadowMapsSize, shadowMapsSize, Float2::Zero, &renderContext.View);
+ atlasLight.Tiles[0].SetWorldToShadow(shadowContext.View.ViewProjection());
}
void ShadowsPass::Dispose()
@@ -624,7 +774,7 @@ void ShadowsPass::SetupShadows(RenderContext& renderContext, RenderContextBatch&
shadows.ViewOrigin = renderContext.View.Origin;
}
if (!shadows.AtlasTiles)
- shadows.AtlasTiles = New(0, 0, atlasResolution, atlasResolution);
+ shadows.AtlasTiles = New(0, 0, atlasResolution, atlasResolution);
// Update/add lights
for (const RenderLightData* light : shadowedLights)
@@ -637,12 +787,17 @@ void ShadowsPass::SetupShadows(RenderContext& renderContext, RenderContextBatch&
atlasLight.Resolution = QuantizeResolution(lightResolutionFloat);
// Cull too small lights
- constexpr uint16 MinResolution = 16;
- if (atlasLight.Resolution < MinResolution)
+ if (atlasLight.Resolution < SHADOWS_MIN_RESOLUTION)
continue;
if (light->IsDirectionalLight)
- atlasLight.TilesNeeded = Math::Clamp(((const RenderDirectionalLightData*)light)->CascadeCount, 0, MAX_CSM_CASCADES);
+ {
+ atlasLight.TilesNeeded = Math::Clamp(((const RenderDirectionalLightData*)light)->CascadeCount, 1, MAX_CSM_CASCADES);
+
+ // Views with orthographic cameras cannot use cascades, we force it to 1 shadow map here
+ if (renderContext.View.IsOrthographicProjection())
+ atlasLight.TilesNeeded = 1;
+ }
else if (light->IsPointLight)
atlasLight.TilesNeeded = 6;
else
@@ -655,11 +810,8 @@ void ShadowsPass::SetupShadows(RenderContext& renderContext, RenderContextBatch&
{
if (it->Value.LastFrameUsed != currentFrame)
{
- for (auto& tile : it->Value.Tiles)
- {
- if (tile)
- tile->Free(&shadows);
- }
+ for (ShadowAtlasLightTile& tile : it->Value.Tiles)
+ tile.Free(&shadows);
shadows.Lights.Remove(it);
}
}
@@ -696,50 +848,41 @@ RETRY_ATLAS_SETUP:
}
// Macro checks if light has proper amount of tiles already assigned and the resolution is matching
-#define IS_LIGHT_TILE_REUSABLE (atlasLight.ContextCount == atlasLight.TilesNeeded && atlasLight.Tiles[0] && atlasLight.Tiles[0]->Width == atlasLight.Resolution)
+#define IS_LIGHT_TILE_REUSABLE (atlasLight.TilesCount == atlasLight.TilesNeeded && atlasLight.Tiles[0].RectTile && atlasLight.Tiles[0].RectTile->Width == atlasLight.Resolution)
// Remove incorrect tiles before allocating new ones
for (RenderLightData* light : shadowedLights)
{
- auto& atlasLight = shadows.Lights[light->ID];
+ ShadowAtlasLight& atlasLight = shadows.Lights[light->ID];
if (IS_LIGHT_TILE_REUSABLE)
continue;
// Remove existing tiles
- for (auto& tile : atlasLight.Tiles)
- {
- if (tile)
- {
- tile->Free(&shadows);
- tile = nullptr;
- }
- }
+ for (ShadowAtlasLightTile& tile : atlasLight.Tiles)
+ tile.Free(&shadows);
}
// Insert tiles into the atlas (already sorted to favor the first ones)
for (RenderLightData* light : shadowedLights)
{
auto& atlasLight = shadows.Lights[light->ID];
- if (IS_LIGHT_TILE_REUSABLE || atlasLight.Resolution < 16)
+ if (IS_LIGHT_TILE_REUSABLE || atlasLight.Resolution < SHADOWS_MIN_RESOLUTION)
continue;
// Try to insert tiles
bool failedToInsert = false;
for (int32 tileIndex = 0; tileIndex < atlasLight.TilesNeeded; tileIndex++)
{
- auto tile = shadows.AtlasTiles->Insert(atlasLight.Resolution, atlasLight.Resolution, 0, &shadows);
- if (!tile)
+ auto rectTile = shadows.AtlasTiles->Insert(atlasLight.Resolution, atlasLight.Resolution, 0, &shadows);
+ if (!rectTile)
{
// Free any previous tiles that were added
for (int32 i = 0; i < tileIndex; i++)
- {
- atlasLight.Tiles[i]->Free(&shadows);
- atlasLight.Tiles[i] = nullptr;
- }
+ atlasLight.Tiles[i].Free(&shadows);
failedToInsert = true;
break;
}
- atlasLight.Tiles[tileIndex] = tile;
+ atlasLight.Tiles[tileIndex].RectTile = rectTile;
}
if (failedToInsert)
{
@@ -757,7 +900,7 @@ RETRY_ATLAS_SETUP:
// Rebuild atlas
shadows.ClearTiles();
- shadows.AtlasTiles = New(0, 0, atlasResolution, atlasResolution);
+ shadows.AtlasTiles = New(0, 0, atlasResolution, atlasResolution);
goto RETRY_ATLAS_SETUP;
}
}
@@ -766,9 +909,14 @@ RETRY_ATLAS_SETUP:
for (RenderLightData* light : shadowedLights)
{
auto& atlasLight = shadows.Lights[light->ID];
- if (atlasLight.Tiles[0] && atlasLight.Tiles[0]->Width == atlasLight.Resolution)
+ if (atlasLight.Tiles[0].RectTile && atlasLight.Tiles[0].RectTile->Width == atlasLight.Resolution)
{
+ // Invalidate cache when whole atlas will be cleared
+ if (shadows.ClearShadowMapAtlas)
+ atlasLight.Cache.Valid = false;
+
light->HasShadow = true;
+ atlasLight.TilesCount = atlasLight.TilesNeeded;
if (light->IsPointLight)
SetupLight(renderContext, renderContextBatch, *(RenderPointLightData*)light, atlasLight);
else if (light->IsSpotLight)
@@ -787,7 +935,7 @@ RETRY_ATLAS_SETUP:
for (RenderLightData* light : shadowedLights)
{
auto& atlasLight = shadows.Lights[light->ID];
- if (atlasLight.Tiles[0] == nullptr)
+ if (atlasLight.Tiles[0].RectTile == nullptr)
{
light->ShadowsBufferAddress = 0; // Clear to indicate no shadow
continue;
@@ -797,26 +945,24 @@ RETRY_ATLAS_SETUP:
light->ShadowsBufferAddress = shadows.ShadowsBuffer.Data.Count() / sizeof(Float4);
// Write shadow data (this must match HLSL)
- const int32 tilesCount = atlasLight.ContextCount;
{
// Shadow info
auto* packed = shadows.ShadowsBuffer.WriteReserve(2);
- Color32 packed0x((byte)(atlasLight.Sharpness * (255.0f / 10.0f)), (byte)(atlasLight.Fade * 255.0f), tilesCount, 0);
+ Color32 packed0x((byte)(atlasLight.Sharpness * (255.0f / 10.0f)), (byte)(atlasLight.Fade * 255.0f), (byte)atlasLight.TilesCount, 0);
packed[0] = Float4(*(const float*)&packed0x, atlasLight.FadeDistance, atlasLight.NormalOffsetScale, atlasLight.Bias);
packed[1] = atlasLight.CascadeSplits;
}
- for (int32 tileIndex = 0; tileIndex < tilesCount; tileIndex++)
+ for (int32 tileIndex = 0; tileIndex < atlasLight.TilesCount; tileIndex++)
{
// Shadow projection info
- const ShadowsAtlasTile* tile = atlasLight.Tiles[tileIndex];
- ASSERT(tile);
- const Matrix& worldToShadow = atlasLight.WorldToShadow[tileIndex];
+ const ShadowAtlasLightTile& tile = atlasLight.Tiles[tileIndex];
+ ASSERT(tile.RectTile);
auto* packed = shadows.ShadowsBuffer.WriteReserve(5);
- packed[0] = Float4(tile->Width, tile->Height, tile->X, tile->Y) * atlasResolutionInv; // UV to AtlasUV via a single MAD instruction
- packed[1] = worldToShadow.GetColumn1();
- packed[2] = worldToShadow.GetColumn2();
- packed[3] = worldToShadow.GetColumn3();
- packed[4] = worldToShadow.GetColumn4();
+ packed[0] = Float4(tile.RectTile->Width, tile.RectTile->Height, tile.RectTile->X, tile.RectTile->Y) * atlasResolutionInv; // UV to AtlasUV via a single MAD instruction
+ packed[1] = tile.WorldToShadow.GetColumn1();
+ packed[2] = tile.WorldToShadow.GetColumn2();
+ packed[3] = tile.WorldToShadow.GetColumn3();
+ packed[4] = tile.WorldToShadow.GetColumn4();
}
}
GPUContext* context = GPUDevice::Instance->GetMainContext();
@@ -852,14 +998,18 @@ void ShadowsPass::RenderShadowMaps(RenderContextBatch& renderContextBatch)
for (auto& e : shadows.Lights)
{
const ShadowAtlasLight& atlasLight = e.Value;
- for (int32 tileIndex = 0; tileIndex < atlasLight.ContextCount; tileIndex++)
+ int32 contextIndex = 0;
+ for (int32 tileIndex = 0; tileIndex < atlasLight.TilesCount; tileIndex++)
{
- const ShadowsAtlasTile* tile = atlasLight.Tiles[tileIndex];
- if (!tile)
+ const ShadowAtlasLightTile& tile = atlasLight.Tiles[tileIndex];
+ if (!tile.RectTile)
break;
+ if (tile.SkipUpdate)
+ continue;
// Set viewport for tile
- context->SetViewportAndScissors(Viewport(tile->X, tile->Y, tile->Width, tile->Height));
+ ASSERT_LOW_LAYER(tile.CachedViewport == Viewport(tile.RectTile->X, tile.RectTile->Y, tile.RectTile->Width, tile.RectTile->Height));
+ context->SetViewportAndScissors(tile.CachedViewport);
if (!shadows.ClearShadowMapAtlas)
{
@@ -870,7 +1020,7 @@ void ShadowsPass::RenderShadowMaps(RenderContextBatch& renderContextBatch)
}
// Draw objects depth
- auto& shadowContext = renderContextBatch.Contexts[atlasLight.ContextIndex + tileIndex];
+ auto& shadowContext = renderContextBatch.Contexts[atlasLight.ContextIndex + contextIndex++];
shadowContext.List->ExecuteDrawCalls(shadowContext, DrawCallsListType::Depth);
shadowContext.List->ExecuteDrawCalls(shadowContext, shadowContext.List->ShadowDepthDrawCallsList, renderContext.List->DrawCalls, nullptr);
}
diff --git a/Source/Engine/Renderer/ShadowsPass.h b/Source/Engine/Renderer/ShadowsPass.h
index 5f8558abd..c22a17881 100644
--- a/Source/Engine/Renderer/ShadowsPass.h
+++ b/Source/Engine/Renderer/ShadowsPass.h
@@ -52,12 +52,12 @@ public:
static void GetShadowAtlas(const RenderBuffers* renderBuffers, GPUTexture*& shadowMapAtlas, GPUBufferView*& shadowsBuffer);
private:
- void SetupRenderContext(RenderContext& renderContext, RenderContext& shadowContext);
- void SetupLight(RenderContext& renderContext, RenderContextBatch& renderContextBatch, RenderLightData& light, struct ShadowAtlasLight& atlasLight);
- void SetupLight(RenderContext& renderContext, RenderContextBatch& renderContextBatch, RenderLocalLightData& light, ShadowAtlasLight& atlasLight);
- void SetupLight(RenderContext& renderContext, RenderContextBatch& renderContextBatch, RenderDirectionalLightData& light, ShadowAtlasLight& atlasLight);
- void SetupLight(RenderContext& renderContext, RenderContextBatch& renderContextBatch, RenderPointLightData& light, ShadowAtlasLight& atlasLight);
- void SetupLight(RenderContext& renderContext, RenderContextBatch& renderContextBatch, RenderSpotLightData& light, ShadowAtlasLight& atlasLight);
+ static void SetupRenderContext(RenderContext& renderContext, RenderContext& shadowContext);
+ static void SetupLight(RenderContext& renderContext, RenderContextBatch& renderContextBatch, RenderLightData& light, struct ShadowAtlasLight& atlasLight);
+ static bool SetupLight(RenderContext& renderContext, RenderContextBatch& renderContextBatch, RenderLocalLightData& light, ShadowAtlasLight& atlasLight);
+ static void SetupLight(RenderContext& renderContext, RenderContextBatch& renderContextBatch, RenderDirectionalLightData& light, ShadowAtlasLight& atlasLight);
+ static void SetupLight(RenderContext& renderContext, RenderContextBatch& renderContextBatch, RenderPointLightData& light, ShadowAtlasLight& atlasLight);
+ static void SetupLight(RenderContext& renderContext, RenderContextBatch& renderContextBatch, RenderSpotLightData& light, ShadowAtlasLight& atlasLight);
#if COMPILE_WITH_DEV_ENV
void OnShaderReloading(Asset* obj)
diff --git a/Source/Shaders/ShadowsSampling.hlsl b/Source/Shaders/ShadowsSampling.hlsl
index 20806daf6..6d9390570 100644
--- a/Source/Shaders/ShadowsSampling.hlsl
+++ b/Source/Shaders/ShadowsSampling.hlsl
@@ -146,8 +146,8 @@ ShadowSample SampleDirectionalLightShadow(LightData light, Buffer shadow
result.SurfaceShadow = SampleShadowMap(shadowMap, shadowMapUV, shadowPosition.z);
// Increase the sharpness for higher cascades to match the filter radius
- const float SharpnessScale[MaxNumCascades] = { 1.0f, 1.5f, 3.0f, 3.5f };
- shadow.Sharpness *= SharpnessScale[cascadeIndex];
+ //const float SharpnessScale[MaxNumCascades] = { 1.0f, 1.5f, 3.0f, 3.5f };
+ //shadow.Sharpness *= SharpnessScale[cascadeIndex];
#if defined(USE_GBUFFER_CUSTOM_DATA)
// Subsurface shadowing