Add CascadeBlendSize to Directional Light and fix sun shadow fade at distance

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