diff --git a/Source/Editor/Windows/GraphicsQualityWindow.cs b/Source/Editor/Windows/GraphicsQualityWindow.cs index 59cd61f25..a1b6b3eab 100644 --- a/Source/Editor/Windows/GraphicsQualityWindow.cs +++ b/Source/Editor/Windows/GraphicsQualityWindow.cs @@ -1,6 +1,5 @@ // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. -using System.Collections.Generic; using System.ComponentModel; using FlaxEditor.CustomEditors; using FlaxEngine; @@ -96,14 +95,6 @@ namespace FlaxEditor.Windows set => Graphics.ShadowMapsQuality = value; } - [DefaultValue(false)] - [EditorOrder(1320), EditorDisplay("Quality", "Allow CSM Blending"), Tooltip("Enables cascades splits blending for directional light shadows.")] - public bool AllowCSMBlending - { - get => Graphics.AllowCSMBlending; - set => Graphics.AllowCSMBlending = value; - } - [NoSerialize, DefaultValue(1.0f), Limit(0.05f, 5, 0)] [EditorOrder(1400), EditorDisplay("Quality")] [Tooltip("The scale of the rendering resolution relative to the output dimensions. If lower than 1 the scene and postprocessing will be rendered at a lower resolution and upscaled to the output backbuffer.")] diff --git a/Source/Engine/Core/Config/GraphicsSettings.h b/Source/Engine/Core/Config/GraphicsSettings.h index 97c5f81cc..a3a5e7ded 100644 --- a/Source/Engine/Core/Config/GraphicsSettings.h +++ b/Source/Engine/Core/Config/GraphicsSettings.h @@ -61,9 +61,10 @@ public: /// /// Enables cascades splits blending for directional light shadows. + /// [Deprecated in v1.9] /// API_FIELD(Attributes="EditorOrder(1320), DefaultValue(false), EditorDisplay(\"Quality\", \"Allow CSM Blending\")") - bool AllowCSMBlending = false; + DEPRECATED bool AllowCSMBlending = false; /// /// Default probes cubemap resolution (use for Environment Probes, can be overriden per-actor). diff --git a/Source/Engine/Graphics/Graphics.cpp b/Source/Engine/Graphics/Graphics.cpp index 1e834ff55..954fc7c73 100644 --- a/Source/Engine/Graphics/Graphics.cpp +++ b/Source/Engine/Graphics/Graphics.cpp @@ -65,7 +65,6 @@ void GraphicsSettings::Apply() Graphics::VolumetricFogQuality = VolumetricFogQuality; Graphics::ShadowsQuality = ShadowsQuality; Graphics::ShadowMapsQuality = ShadowMapsQuality; - Graphics::AllowCSMBlending = AllowCSMBlending; Graphics::GlobalSDFQuality = GlobalSDFQuality; Graphics::GIQuality = GIQuality; Graphics::PostProcessSettings = ::PostProcessSettings(); diff --git a/Source/Engine/Graphics/Graphics.h b/Source/Engine/Graphics/Graphics.h index 7f49d450c..55ded56e5 100644 --- a/Source/Engine/Graphics/Graphics.h +++ b/Source/Engine/Graphics/Graphics.h @@ -50,8 +50,9 @@ public: /// /// Enables cascades splits blending for directional light shadows. + /// [Deprecated in v1.9] /// - API_FIELD() static bool AllowCSMBlending; + API_FIELD() DEPRECATED static bool AllowCSMBlending; /// /// The Global SDF quality. Controls the volume texture resolution and amount of cascades to use. diff --git a/Source/Engine/Graphics/RenderTools.cpp b/Source/Engine/Graphics/RenderTools.cpp index f43fc8c75..edd30af7a 100644 --- a/Source/Engine/Graphics/RenderTools.cpp +++ b/Source/Engine/Graphics/RenderTools.cpp @@ -552,6 +552,14 @@ void RenderTools::ComputeCascadeUpdateFrequency(int32 cascadeIndex, int32 cascad } } +float RenderTools::ComputeTemporalTime() +{ + const float time = Time::Draw.UnscaledTime.GetTotalSeconds(); + const float scale = 10; + const float integral = roundf(time / scale) * scale; + return time - integral; +} + void RenderTools::CalculateTangentFrame(FloatR10G10B10A2& resultNormal, FloatR10G10B10A2& resultTangent, const Float3& normal) { // Calculate tangent diff --git a/Source/Engine/Graphics/RenderTools.h b/Source/Engine/Graphics/RenderTools.h index 06b059975..07fcab89a 100644 --- a/Source/Engine/Graphics/RenderTools.h +++ b/Source/Engine/Graphics/RenderTools.h @@ -121,6 +121,10 @@ public: return (frameIndex % updateFrequency == updatePhrase) || updateForce; } + // Calculates temporal offset in the dithering factor that gets cleaned out by TAA. + // Returns 0-1 value based on unscaled draw time for temporal effects to reduce artifacts from screen-space dithering when using Temporal Anti-Aliasing. + static float ComputeTemporalTime(); + static void CalculateTangentFrame(FloatR10G10B10A2& resultNormal, FloatR10G10B10A2& resultTangent, const Float3& normal); static void CalculateTangentFrame(FloatR10G10B10A2& resultNormal, FloatR10G10B10A2& resultTangent, const Float3& normal, const Float3& tangent); }; diff --git a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp index 56d1194d5..e488bab81 100644 --- a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp +++ b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp @@ -13,13 +13,13 @@ #include "Engine/Engine/Engine.h" #include "Engine/Content/Content.h" #include "Engine/Debug/DebugDraw.h" -#include "Engine/Engine/Time.h" #include "Engine/Graphics/GPUContext.h" #include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/Graphics.h" #include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/RenderBuffers.h" #include "Engine/Graphics/RenderTargetPool.h" +#include "Engine/Graphics/RenderTools.h" #include "Engine/Graphics/Shaders/GPUShader.h" #include "Engine/Level/Actors/BrushMode.h" #include "Engine/Renderer/GBufferPass.h" @@ -480,18 +480,7 @@ bool DynamicDiffuseGlobalIlluminationPass::RenderInner(RenderContext& renderCont auto& cascade = ddgiData.Cascades[cascadeIndex]; data.ProbeScrollClears[cascadeIndex] = Int4(cascade.ProbeScrollClears, 0); } - if (renderContext.List->Setup.UseTemporalAAJitter) - { - // Use temporal offset in the dithering factor (gets cleaned out by TAA) - const float time = Time::Draw.UnscaledTime.GetTotalSeconds(); - const float scale = 10; - const float integral = roundf(time / scale) * scale; - data.TemporalTime = time - integral; - } - else - { - data.TemporalTime = 0.0f; - } + data.TemporalTime = renderContext.List->Setup.UseTemporalAAJitter ? RenderTools::ComputeTemporalTime() : 0.0f; GBufferPass::SetInputs(renderContext.View, data.GBuffer); context->UpdateCB(_cb0, &data); context->BindCB(0, _cb0); diff --git a/Source/Engine/Renderer/ScreenSpaceReflectionsPass.cpp b/Source/Engine/Renderer/ScreenSpaceReflectionsPass.cpp index b3d0ea6ba..5e758d148 100644 --- a/Source/Engine/Renderer/ScreenSpaceReflectionsPass.cpp +++ b/Source/Engine/Renderer/ScreenSpaceReflectionsPass.cpp @@ -11,7 +11,6 @@ #include "Engine/Graphics/RenderTools.h" #include "Engine/Graphics/RenderTargetPool.h" #include "Engine/Graphics/RenderBuffers.h" -#include "Engine/Engine/Time.h" #include "Engine/Platform/Window.h" #include "Utils/MultiScaler.h" #include "Engine/Engine/Engine.h" @@ -247,13 +246,7 @@ void ScreenSpaceReflectionsPass::Render(RenderContext& renderContext, GPUTexture data.TemporalEffect = useTemporal ? 1.0f : 0.0f; if (useTemporal) { - const float time = Time::Draw.UnscaledTime.GetTotalSeconds(); - - // Keep time in smaller range to prevent temporal noise errors - const double scale = 10; - const double integral = round(time / scale) * scale; - data.TemporalTime = static_cast(time - integral); - + data.TemporalTime = RenderTools::ComputeTemporalTime(); buffers->LastFrameTemporalSSR = Engine::FrameCount; if (!buffers->TemporalSSR || buffers->TemporalSSR->Width() != temporalWidth || buffers->TemporalSSR->Height() != temporalHeight) { diff --git a/Source/Engine/Renderer/ShadowsPass.cpp b/Source/Engine/Renderer/ShadowsPass.cpp index 64f043d28..aa304d43b 100644 --- a/Source/Engine/Renderer/ShadowsPass.cpp +++ b/Source/Engine/Renderer/ShadowsPass.cpp @@ -27,7 +27,8 @@ PACK_STRUCT(struct Data{ ShaderLightData Light; Matrix WVP; Matrix ViewProjectionMatrix; - Float2 Dummy0; + float Dummy0; + float TemporalTime; float ContactShadowsDistance; float ContactShadowsLength; }); @@ -62,7 +63,6 @@ struct ShadowAtlasLight int32 ContextCount; uint16 Resolution; uint16 TilesNeeded; - bool BlendCSM; float Sharpness, Fade, NormalOffsetScale, Bias, FadeDistance; Float4 CascadeSplits; ShadowsAtlasTile* Tiles[MaxTiles]; @@ -294,15 +294,7 @@ void ShadowsPass::SetupLight(RenderContext& renderContext, RenderContextBatch& r Float3 lightDirection = light.Direction; float shadowsDistance = Math::Min(view.Far, light.ShadowsDistance); int32 csmCount = Math::Clamp(light.CascadeCount, 0, MAX_CSM_CASCADES); - bool blendCSM = Graphics::AllowCSMBlending; const auto shadowMapsSize = (float)atlasLight.Resolution; -#if USE_EDITOR - if (IsRunningRadiancePass) - blendCSM = false; -#elif PLATFORM_SWITCH || PLATFORM_IOS || PLATFORM_ANDROID - // Disable cascades blending on low-end platforms - blendCSM = false; -#endif // Views with orthographic cameras cannot use cascades, we force it to 1 shadow map here if (view.Projection.M44 == 1.0f) @@ -397,7 +389,6 @@ void ShadowsPass::SetupLight(RenderContext& renderContext, RenderContextBatch& r // Init shadow data atlasLight.ContextIndex = renderContextBatch.Contexts.Count(); atlasLight.ContextCount = csmCount; - atlasLight.BlendCSM = blendCSM; renderContextBatch.Contexts.AddDefault(atlasLight.ContextCount); // Create the different view and projection matrices for each split @@ -413,9 +404,7 @@ void ShadowsPass::SetupLight(RenderContext& renderContext, RenderContextBatch& r // Calculate cascade split frustum corners in view space for (int32 j = 0; j < 4; j++) { - float overlap = 0; - if (blendCSM) - overlap = 0.2f * (splitMinRatio - oldSplitMinRatio); + float overlap = 0.1f * (splitMinRatio - oldSplitMinRatio); // CSM blending overlap 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; @@ -927,6 +916,7 @@ void ShadowsPass::RenderShadowMask(RenderContextBatch& renderContextBatch, Rende else if (light.IsSpotLight) ((RenderSpotLightData&)light).SetShaderData(sperLight.Light, true); Matrix::Transpose(view.ViewProjection(), sperLight.ViewProjectionMatrix); + sperLight.TemporalTime = renderContext.List->Setup.UseTemporalAAJitter ? RenderTools::ComputeTemporalTime() : 0.0f; sperLight.ContactShadowsDistance = light.ShadowsDistance; sperLight.ContactShadowsLength = EnumHasAnyFlags(view.Flags, ViewFlags::ContactShadows) ? light.ContactShadowsLength : 0.0f; if (isLocalLight) diff --git a/Source/Shaders/Shadows.shader b/Source/Shaders/Shadows.shader index aff46ac8e..92c2d0815 100644 --- a/Source/Shaders/Shadows.shader +++ b/Source/Shaders/Shadows.shader @@ -1,6 +1,7 @@ // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. #define USE_GBUFFER_CUSTOM_DATA +#define SHADOWS_CSM_BLENDING 1 #include "./Flax/Common.hlsl" #include "./Flax/GBuffer.hlsl" @@ -12,7 +13,8 @@ GBufferData GBuffer; LightData Light; float4x4 WVP; float4x4 ViewProjectionMatrix; -float2 Dummy0; +float Dummy0; +float TemporalTime; float ContactShadowsDistance; float ContactShadowsLength; META_CB_END @@ -115,7 +117,7 @@ float4 PS_DirLight(Quad_VS2PS input) : SV_Target0 GBufferSample gBuffer = SampleGBuffer(gBufferData, input.TexCoord); // Sample shadow - ShadowSample shadow = SampleDirectionalLightShadow(Light, ShadowsBuffer, ShadowMap, gBuffer); + ShadowSample shadow = SampleDirectionalLightShadow(Light, ShadowsBuffer, ShadowMap, gBuffer, TemporalTime); #if CONTACT_SHADOWS // Calculate screen-space contact shadow diff --git a/Source/Shaders/ShadowsSampling.hlsl b/Source/Shaders/ShadowsSampling.hlsl index d8510011a..20806daf6 100644 --- a/Source/Shaders/ShadowsSampling.hlsl +++ b/Source/Shaders/ShadowsSampling.hlsl @@ -6,6 +6,9 @@ #include "./Flax/ShadowsCommon.hlsl" #include "./Flax/GBufferCommon.hlsl" #include "./Flax/LightingCommon.hlsl" +#ifdef SHADOWS_CSM_BLENDING +#include "./Flax/Random.hlsl" +#endif #if FEATURE_LEVEL >= FEATURE_LEVEL_SM5 #define SAMPLE_SHADOW_MAP(shadowMap, shadowUV, sceneDepth) shadowMap.SampleCmpLevelZero(ShadowSamplerLinear, shadowUV, sceneDepth) @@ -77,7 +80,7 @@ float SampleShadowMap(Texture2D shadowMap, float2 shadowMapUV, float scen } // Samples the shadow for the given directional light on the material surface (supports subsurface shadowing) -ShadowSample SampleDirectionalLightShadow(LightData light, Buffer shadowsBuffer, Texture2D shadowMap, GBufferSample gBuffer) +ShadowSample SampleDirectionalLightShadow(LightData light, Buffer shadowsBuffer, Texture2D shadowMap, GBufferSample gBuffer, float dither = 0.0f) { #if !LIGHTING_NO_DIRECTIONAL // Skip if surface is in a full shadow @@ -114,6 +117,19 @@ ShadowSample SampleDirectionalLightShadow(LightData light, Buffer shadow if (viewDepth > shadow.CascadeSplits[i]) cascadeIndex = i + 1; } +#ifdef SHADOWS_CSM_BLENDING + const float BlendThreshold = 0.05f; + float nextSplit = shadow.CascadeSplits[cascadeIndex]; + float splitSize = cascadeIndex == 0 ? nextSplit : nextSplit - shadow.CascadeSplits[cascadeIndex - 1]; + float splitDist = (nextSplit - viewDepth) / splitSize; + if (splitDist <= BlendThreshold && cascadeIndex != shadow.TilesCount - 1) + { + // Blend with the next cascade but with screen-space dithering (gets cleaned out by TAA) + float lerpAmount = 1 - splitDist / BlendThreshold; + if (step(RandN2(gBuffer.ViewPos.xy + dither).x, lerpAmount)) + cascadeIndex++; + } +#endif ShadowTileData shadowTile = LoadShadowsBufferTile(shadowsBuffer, light.ShadowsBufferAddress, cascadeIndex); float3 samplePosition = gBuffer.WorldPos; @@ -241,11 +257,11 @@ GBufferSample GetDummyGBufferSample(float3 worldPosition) } // Samples the shadow for the given directional light at custom location -ShadowSample SampleDirectionalLightShadow(LightData light, Buffer shadowsBuffer, Texture2D shadowMap, float3 worldPosition, float viewDepth) +ShadowSample SampleDirectionalLightShadow(LightData light, Buffer shadowsBuffer, Texture2D shadowMap, float3 worldPosition, float viewDepth, float dither = 0.0f) { GBufferSample gBuffer = GetDummyGBufferSample(worldPosition); gBuffer.ViewPos.z = viewDepth; - return SampleDirectionalLightShadow(light, shadowsBuffer, shadowMap, gBuffer); + return SampleDirectionalLightShadow(light, shadowsBuffer, shadowMap, gBuffer, dither); } // Samples the shadow for the given spot light at custom location