diff --git a/Source/Engine/Level/Actors/DirectionalLight.cpp b/Source/Engine/Level/Actors/DirectionalLight.cpp
index e6644c1f7..8ab2b2dad 100644
--- a/Source/Engine/Level/Actors/DirectionalLight.cpp
+++ b/Source/Engine/Level/Actors/DirectionalLight.cpp
@@ -48,6 +48,7 @@ void DirectionalLight::Draw(RenderContext& renderContext)
data.Cascade2Spacing = Cascade2Spacing;
data.Cascade3Spacing = Cascade3Spacing;
data.Cascade4Spacing = Cascade4Spacing;
+ data.CascadeBlendSize = CascadeBlendSize;
data.PartitionMode = PartitionMode;
data.ContactShadowsLength = ContactShadowsLength;
data.StaticFlags = GetStaticFlags();
@@ -69,6 +70,7 @@ void DirectionalLight::Serialize(SerializeStream& stream, const void* otherObj)
SERIALIZE(Cascade2Spacing);
SERIALIZE(Cascade3Spacing);
SERIALIZE(Cascade4Spacing);
+ SERIALIZE(CascadeBlendSize);
SERIALIZE(PartitionMode);
}
@@ -82,6 +84,7 @@ void DirectionalLight::Deserialize(DeserializeStream& stream, ISerializeModifier
DESERIALIZE(Cascade2Spacing);
DESERIALIZE(Cascade3Spacing);
DESERIALIZE(Cascade4Spacing);
+ DESERIALIZE(CascadeBlendSize);
DESERIALIZE(PartitionMode);
}
diff --git a/Source/Engine/Level/Actors/DirectionalLight.h b/Source/Engine/Level/Actors/DirectionalLight.h
index 076eb1139..538243468 100644
--- a/Source/Engine/Level/Actors/DirectionalLight.h
+++ b/Source/Engine/Level/Actors/DirectionalLight.h
@@ -48,6 +48,12 @@ public:
API_FIELD(Attributes="EditorOrder(69), DefaultValue(1.0f), VisibleIf(nameof(ShowCascade4)), Limit(0, 1, 0.001f), EditorDisplay(\"Shadow\")")
float Cascade4Spacing = 1.0f;
+ ///
+ /// Percentage of the cascade distance over which cascades will blend together. This helps to hide the transition between cascades.
+ ///
+ API_FIELD(Attributes = "EditorOrder(70), EditorDisplay(\"Shadow\", \"Cascade Blend Distance\"), Limit(0.0f, 1.0f)")
+ float CascadeBlendSize = 0.1f;
+
public:
// [LightWithShadow]
void Draw(RenderContext& renderContext) override;
diff --git a/Source/Engine/Renderer/RenderList.h b/Source/Engine/Renderer/RenderList.h
index 8041141b1..6133924a3 100644
--- a/Source/Engine/Renderer/RenderList.h
+++ b/Source/Engine/Renderer/RenderList.h
@@ -79,6 +79,7 @@ struct RenderDirectionalLightData : RenderLightData
PartitionMode PartitionMode;
int32 CascadeCount;
+ float CascadeBlendSize;
RenderDirectionalLightData()
{
diff --git a/Source/Engine/Renderer/ShadowsPass.cpp b/Source/Engine/Renderer/ShadowsPass.cpp
index da9f3909a..1e2436604 100644
--- a/Source/Engine/Renderer/ShadowsPass.cpp
+++ b/Source/Engine/Renderer/ShadowsPass.cpp
@@ -195,7 +195,7 @@ struct ShadowAtlasLight
bool BlendCSM;
mutable StaticStates StaticState;
BoundingSphere Bounds;
- float Sharpness, Fade, NormalOffsetScale, Bias, FadeDistance, Distance, TileBorder;
+ float Sharpness, Fade, NormalOffsetScale, Bias, FadeDistance, Distance, TileBorder, CascadeBlendSize;
Float4 CascadeSplits;
ShadowAtlasLightTile Tiles[SHADOWS_MAX_TILES];
ShadowAtlasLightCache Cache;
@@ -846,6 +846,7 @@ void ShadowsPass::SetupLight(ShadowsCustomBuffer& shadows, RenderContext& render
cascadeSplits[i] = (cascadeSplits[i] - renderContext.View.Near) / viewRange;
}
atlasLight.CascadeSplits = renderContext.View.Near + Float4(cascadeSplits) * viewRange;
+ atlasLight.CascadeBlendSize = Math::Saturate(light.CascadeBlendSize);
// Update cached state (invalidate it if the light changed)
atlasLight.ValidateCache(renderContext.View, light);
@@ -917,10 +918,10 @@ void ShadowsPass::SetupLight(ShadowsCustomBuffer& shadows, RenderContext& render
// Calculate cascade split frustum corners in view space
Float3 cascadeCornersVs[8];
- float csmOverlap = atlasLight.BlendCSM ? 0.2f : 0.1f;
+ float blendDistance = atlasLight.CascadeBlendSize;
for (int32 j = 0; j < 4; j++)
{
- float overlapWithPrevSplit = csmOverlap * (splitMinRatio - oldSplitMinRatio);
+ float overlapWithPrevSplit = blendDistance * (splitMinRatio - oldSplitMinRatio);
const Float3 frustumRangeVS = frustumCornersVs[j + 4] - frustumCornersVs[j];
cascadeCornersVs[j] = frustumCornersVs[j] + frustumRangeVS * (splitMinRatio - overlapWithPrevSplit);
cascadeCornersVs[j + 4] = frustumCornersVs[j] + frustumRangeVS * splitMaxRatio;
@@ -1430,7 +1431,7 @@ RETRY_ATLAS_SETUP:
{
// Shadow info
auto* packed = shadows.ShadowsBuffer.WriteReserve(2);
- Color32 packed0x((byte)(atlasLight.Sharpness * (255.0f / 10.0f)), (byte)(atlasLight.Fade * 255.0f), (byte)atlasLight.TilesCount, 0);
+ Color32 packed0x((byte)(atlasLight.Sharpness * (255.0f / 10.0f)), (byte)(atlasLight.Fade * 255.0f), (byte)atlasLight.TilesCount, (byte)(atlasLight.CascadeBlendSize * 255.0f));
packed[0] = Float4(*(const float*)&packed0x, atlasLight.FadeDistance, atlasLight.NormalOffsetScale, atlasLight.Bias);
packed[1] = atlasLight.CascadeSplits;
}
diff --git a/Source/Shaders/ShadowsCommon.hlsl b/Source/Shaders/ShadowsCommon.hlsl
index 580dc2607..f3ff9fd5b 100644
--- a/Source/Shaders/ShadowsCommon.hlsl
+++ b/Source/Shaders/ShadowsCommon.hlsl
@@ -21,6 +21,7 @@ struct ShadowData
float FadeDistance;
float NormalOffsetScale;
float Bias;
+ float CascadeBlendSize;
uint TilesCount;
float4 CascadeSplits;
};
@@ -43,6 +44,7 @@ ShadowData LoadShadowsBuffer(Buffer shadowsBuffer, uint shadowsBufferAdd
shadow.Sharpness = (packed0x & 0x000000ff) * (10.0f / 255.0f);
shadow.Fade = ((packed0x & 0x0000ff00) >> 8) * (1.0f / 255.0f);
shadow.TilesCount = ((packed0x & 0x00ff0000) >> 16);
+ shadow.CascadeBlendSize = ((packed0x & 0xff000000) >> 24) * (1.0f / 255.0f);
shadow.FadeDistance = vector0.y;
shadow.NormalOffsetScale = vector0.z;
shadow.Bias = vector0.w;
diff --git a/Source/Shaders/ShadowsSampling.hlsl b/Source/Shaders/ShadowsSampling.hlsl
index f30952f87..07968f8de 100644
--- a/Source/Shaders/ShadowsSampling.hlsl
+++ b/Source/Shaders/ShadowsSampling.hlsl
@@ -293,11 +293,10 @@ ShadowSample SampleDirectionalLightShadow(LightData light, Buffer shadow
float splitDist = (nextSplit - viewDepth) / splitSize;
#endif
#if SHADOWS_CSM_DITHERING && !SHADOWS_CSM_BLENDING
- const float BlendThreshold = 0.05f;
- if (splitDist <= BlendThreshold && cascadeIndex != shadow.TilesCount - 1)
+ if (splitDist <= shadow.CascadeBlendSize && cascadeIndex != shadow.TilesCount - 1)
{
// Dither with the next cascade but with screen-space dithering (gets cleaned out by TAA)
- float lerpAmount = 1 - splitDist / BlendThreshold;
+ float lerpAmount = 1 - splitDist / shadow.CascadeBlendSize;
if (step(RandN2(gBuffer.ViewPos.xy + dither).x, lerpAmount))
cascadeIndex++;
}
@@ -312,17 +311,19 @@ ShadowSample SampleDirectionalLightShadow(LightData light, Buffer shadow
result = SampleDirectionalLightShadowCascade(light, shadowsBuffer, shadowMap, gBuffer, shadow, samplePosition, cascadeIndex);
#if SHADOWS_CSM_BLENDING
- const float BlendThreshold = 0.1f;
- if (splitDist <= BlendThreshold && cascadeIndex != shadow.TilesCount - 1)
+ if (splitDist <= shadow.CascadeBlendSize && cascadeIndex != shadow.TilesCount - 1)
{
// Sample the next cascade, and blend between the two results to smooth the transition
ShadowSample nextResult = SampleDirectionalLightShadowCascade(light, shadowsBuffer, shadowMap, gBuffer, shadow, samplePosition, cascadeIndex + 1);
- float blendAmount = splitDist / BlendThreshold;
+ float blendAmount = splitDist / shadow.CascadeBlendSize;
result.SurfaceShadow = lerp(nextResult.SurfaceShadow, result.SurfaceShadow, blendAmount);
result.TransmissionShadow = lerp(nextResult.TransmissionShadow, result.TransmissionShadow, blendAmount);
}
#endif
+ result.SurfaceShadow = lerp(result.SurfaceShadow, 1, fade);
+ result.TransmissionShadow = lerp(result.TransmissionShadow, 1, fade);
+
return result;
}