From 078a45c4b7a5cc5e89498ea4a523a28274fa68e0 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 20 Jul 2026 12:06:13 +0200 Subject: [PATCH] Add half-res DDGI reflections resolving to optimzie rendering on low-end devices --- .../Engine/Graphics/PostProcessSettings.cpp | 1 + Source/Engine/Graphics/PostProcessSettings.h | 13 ++++- .../GI/DynamicDiffuseGlobalIllumination.cpp | 47 ++++++++++++++----- Source/Shaders/GI/DDGI.shader | 9 ++-- 4 files changed, 54 insertions(+), 16 deletions(-) diff --git a/Source/Engine/Graphics/PostProcessSettings.cpp b/Source/Engine/Graphics/PostProcessSettings.cpp index 688871bb3..5cfde224a 100644 --- a/Source/Engine/Graphics/PostProcessSettings.cpp +++ b/Source/Engine/Graphics/PostProcessSettings.cpp @@ -37,6 +37,7 @@ void GlobalIlluminationSettings::BlendWith(GlobalIlluminationSettings& other, fl BLEND_COL(FallbackIrradiance); BLEND_ENUM(IndirectResolution); BLEND_ENUM(Reflections); + BLEND_ENUM(ReflectionsResolution); } void BloomSettings::BlendWith(BloomSettings& other, float weight) diff --git a/Source/Engine/Graphics/PostProcessSettings.h b/Source/Engine/Graphics/PostProcessSettings.h index 3fe51085a..cda101d64 100644 --- a/Source/Engine/Graphics/PostProcessSettings.h +++ b/Source/Engine/Graphics/PostProcessSettings.h @@ -365,10 +365,15 @@ API_ENUM(Attributes="Flags") enum class GlobalIlluminationSettingsOverride : int /// Reflections = 1 << 8, + /// + /// Overrides property. + /// + ReflectionsResolution = 1 << 9, + /// /// All properties. /// - All = Mode | Intensity | TemporalResponse | Distance | FallbackIrradiance | BounceIntensity | IndirectShadowsStrength | IndirectResolution | Reflections, + All = Mode | Intensity | TemporalResponse | Distance | FallbackIrradiance | BounceIntensity | IndirectShadowsStrength | IndirectResolution | Reflections | ReflectionsResolution, }; /// @@ -440,6 +445,12 @@ API_STRUCT() struct FLAXENGINE_API GlobalIlluminationSettings : ISerializable API_FIELD(Attributes = "EditorOrder(100), PostProcessSetting((int)GlobalIlluminationSettingsOverride.Reflections)") ReflectionsMode Reflections = ReflectionsMode::EnvironmentProbes; + /// + /// The indirect specular reflections render resolution. Full gives better quality, but half improves performance. + /// + API_FIELD(Attributes = "EditorOrder(110), PostProcessSetting((int)GlobalIlluminationSettingsOverride.ReflectionsResolution)") + ResolutionMode ReflectionsResolution = ResolutionMode::Full; + public: /// /// Blends the settings using given weight. diff --git a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp index b664e3993..ebf205208 100644 --- a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp +++ b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp @@ -75,6 +75,9 @@ GPU_CB_STRUCT(Data0 { float TestValue; Float3 QuantizationError; int32 FrameIndexMod8; + Float2 Padding0; + float ResolveDitherScaleIndirect; + float ResolveDitherScaleSpecular; }); GPU_CB_STRUCT(Data1 { @@ -182,6 +185,19 @@ public: } }; +void InitData0Shared(const RenderContext& renderContext, const DDGICustomBuffer& ddgiData, Data0& data) +{ + data.DDGI = ddgiData.Result.Constants; + data.TestValue = Graphics::TestValue; + data.TemporalTime = renderContext.List->Setup.UseTemporalAAJitter ? RenderTools::ComputeTemporalTime() : 0.0f; + data.FrameIndexMod8 = (int32)(Engine::FrameCount % 8); + auto& settings = renderContext.List->Settings.GlobalIllumination; + constexpr float DitherScaleHalfRes = 0.1f; // Hardcoded to reduce noise at half-res due to TAA not being able to filter this out (in future BilateralUpscale could try smooth it) + data.ResolveDitherScaleIndirect = settings.IndirectResolution == ResolutionMode::Full ? 1.0f : DitherScaleHalfRes; + data.ResolveDitherScaleSpecular = settings.ReflectionsResolution == ResolutionMode::Full ? 1.0f : DitherScaleHalfRes; + GBufferPass::SetInputs(renderContext.View, data.GBuffer); +} + void CalculateVolumeRandomRotation(Matrix3x3& matrix) { // Reference: James Arvo's algorithm Graphics Gems 3 (pages 117-120) @@ -650,7 +666,6 @@ bool DynamicDiffuseGlobalIlluminationPass::RenderInner(RenderContext& renderCont Quaternion::RotationMatrix(raysRotationMatrix, raysRotation); raysRotation.Conjugate(); - data.DDGI = ddgiData.Result.Constants; data.GlobalSDF = bindingDataSDF.Constants; data.GlobalSurfaceAtlas = bindingDataSurfaceAtlas.Constants; data.ProbesCount = data.DDGI.ProbesCounts[0] * data.DDGI.ProbesCounts[1] * data.DDGI.ProbesCounts[2]; @@ -660,12 +675,10 @@ bool DynamicDiffuseGlobalIlluminationPass::RenderInner(RenderContext& renderCont auto& cascade = ddgiData.Cascades[cascadeIndex]; data.ProbeScrollClears[cascadeIndex] = Int4(cascade.ProbeScrollClears, 0); } - data.TemporalTime = renderContext.List->Setup.UseTemporalAAJitter ? RenderTools::ComputeTemporalTime() : 0.0f; data.ViewDir = renderContext.View.Direction; data.SkyboxIntensity = renderContext.List->Sky ? renderContext.List->Sky->GetIndirectLightingIntensity() : 1.0f; - data.TestValue = Graphics::TestValue; data.QuantizationError = RenderTools::GetColorQuantizationError(ddgiData.ProbesIrradiance->Format()); - data.FrameIndexMod8 = (int32)(Engine::FrameCount % 8); + InitData0Shared(renderContext, ddgiData, data); GBufferPass::SetInputs(renderContext.View, data.GBuffer); context->UpdateCB(_cb0, &data); context->BindCB(0, _cb0); @@ -899,10 +912,7 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext, if (!render) { Data0 data; - data.DDGI = ddgiData->Result.Constants; - data.TemporalTime = renderContext.List->Setup.UseTemporalAAJitter ? RenderTools::ComputeTemporalTime() : 0.0f; - data.FrameIndexMod8 = (int32)(Engine::FrameCount % 8); - GBufferPass::SetInputs(renderContext.View, data.GBuffer); + InitData0Shared(renderContext, *ddgiData, data); context->UpdateCB(_cb0, &data); context->BindCB(0, _cb0); } @@ -1051,10 +1061,7 @@ bool DynamicDiffuseGlobalIlluminationPass::RenderReflections(RenderContext& rend if (!render) { Data0 data; - data.DDGI = ddgiData->Result.Constants; - data.TemporalTime = renderContext.List->Setup.UseTemporalAAJitter ? RenderTools::ComputeTemporalTime() : 0.0f; - data.FrameIndexMod8 = (int32)(Engine::FrameCount % 8); - GBufferPass::SetInputs(renderContext.View, data.GBuffer); + InitData0Shared(renderContext, *ddgiData, data); context->UpdateCB(_cb0, &data); context->BindCB(0, _cb0); } @@ -1066,6 +1073,7 @@ bool DynamicDiffuseGlobalIlluminationPass::RenderReflections(RenderContext& rend context->BindSR(5, ddgiData->Result.ProbesDistance); context->BindSR(6, ddgiData->Result.ProbesRadiance); auto& settings = renderContext.List->Settings.GlobalIllumination; + if (settings.ReflectionsResolution == ResolutionMode::Full || !MultiScaler::Instance()->IsReady()) { // Full-res auto rtAction = GPUDrawPassAction::Store; @@ -1075,6 +1083,21 @@ bool DynamicDiffuseGlobalIlluminationPass::RenderReflections(RenderContext& rend context->SetState(_psSpecularLighting); context->DrawFullscreenTriangle(); } + else + { + // Upscale + auto width = RenderTools::GetResolution((int32)renderContext.View.ScreenSize.X, settings.ReflectionsResolution); + auto height = RenderTools::GetResolution((int32)renderContext.View.ScreenSize.Y, settings.ReflectionsResolution); + auto temp = RenderTargetPool::Get(GPUTextureDescription::New2D(width, height, reflectionsBuffer->GetFormat(), GPUTextureFlags::ShaderResource | GPUTextureFlags::RenderTarget)); + context->SetViewportAndScissors((float)width, (float)height); + context->SetRenderTarget(temp->View()); + context->SetState(_psSpecularLighting); + context->DrawFullscreenTriangle(); + context->ResetRenderTarget(); + MultiScaler::Instance()->BilateralUpscale(context, Viewport(Float2(renderContext.View.ScreenSize)), temp, reflectionsBuffer, renderContext.Buffers->DepthBuffer, renderContext.Buffers->GBuffer1); + RenderTargetPool::Release(temp); + context->SetViewportAndScissors(renderContext.View.ScreenSize.X, renderContext.View.ScreenSize.Y); + } context->ResetSR(); context->ResetRenderTarget(); } diff --git a/Source/Shaders/GI/DDGI.shader b/Source/Shaders/GI/DDGI.shader index 213c6fca9..032e0210d 100644 --- a/Source/Shaders/GI/DDGI.shader +++ b/Source/Shaders/GI/DDGI.shader @@ -52,6 +52,9 @@ float3 ViewDir; float TestValue; float3 QuantizationError; uint FrameIndexMod8; +float2 Padding0; +float ResolveDitherScaleIndirect; +float ResolveDitherScaleSpecular; META_CB_END META_CB_BEGIN(1, Data1) @@ -969,7 +972,7 @@ Texture2D ProbesData : register(t4); Texture2D ProbesDistance : register(t5); // Shared code for both irradiance and specular sampling -#define DDGI_GET_DITHER RandN2(input.TexCoord + TemporalTime).x +#define DDGI_GET_DITHER (RandN2(input.TexCoord + TemporalTime).x) #define DDGI_GET_SAMPLE_POS gBuffer.WorldPos + gBuffer.Normal * (dither * 0.1f + 0.1f) #endif @@ -991,7 +994,7 @@ float4 PS_IndirectLighting(Quad_VS2PS input) : SV_Target0 return float4(0, 0, 0, 0); // Sample irradiance - float dither = DDGI_GET_DITHER; + float dither = DDGI_GET_DITHER * ResolveDitherScaleIndirect; float3 samplePos = DDGI_GET_SAMPLE_POS; float3 irradiance = SampleDDGIIrradiance(DDGI, ProbesData, ProbesDistance, ProbesIrradiance, samplePos, gBuffer.Normal, DDGI_DEFAULT_BIAS, dither); @@ -1018,7 +1021,7 @@ float4 PS_SpecularLighting(Quad_VS2PS input) : SV_Target0 return float4(0, 0, 0, 0); // Sample specular reflection - float dither = DDGI_GET_DITHER; + float dither = DDGI_GET_DITHER * ResolveDitherScaleSpecular; float3 samplePos = DDGI_GET_SAMPLE_POS; float3 specular = SampleDDGISpecular(DDGI, ProbesData, ProbesDistance, ProbesRadiance, samplePos, gBuffer.Normal, gBuffer.Roughness, DDGI_DEFAULT_BIAS, dither);