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; }