From 7127ccda37fb5b804f3f21b38ef57a5bdbc7a9c3 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 8 May 2026 09:02:17 +0200 Subject: [PATCH] Fix depth bounds in reversed Z #2684 --- Source/Engine/Graphics/GPUContext.h | 13 ++++-- Source/Engine/Graphics/RenderTools.cpp | 46 ++++++++++++++++++- Source/Engine/Graphics/RenderTools.h | 7 ++- .../Vulkan/GPUContextVulkan.cpp | 3 +- .../Engine/Renderer/AmbientOcclusionPass.cpp | 2 +- Source/Engine/Renderer/LightPass.cpp | 10 ++-- Source/Engine/Renderer/ReflectionsPass.cpp | 8 ++-- Source/Engine/Renderer/ShadowsPass.cpp | 10 ++-- 8 files changed, 73 insertions(+), 26 deletions(-) diff --git a/Source/Engine/Graphics/GPUContext.h b/Source/Engine/Graphics/GPUContext.h index 110770314..9a55957ce 100644 --- a/Source/Engine/Graphics/GPUContext.h +++ b/Source/Engine/Graphics/GPUContext.h @@ -15,9 +15,13 @@ #endif #if FLAX_REVERSE_Z -#define GPU_DEPTH_CLEAR_VALUE 0.0f +#define GPU_DEPTH_MIN_VALUE 1.0f +#define GPU_DEPTH_MAX_VALUE 0.0f +#define GPU_DEPTH_BOUNDS_SWAP(min, max) max, min #else -#define GPU_DEPTH_CLEAR_VALUE 1.0f +#define GPU_DEPTH_MIN_VALUE 0.0f +#define GPU_DEPTH_MAX_VALUE 1.0f +#define GPU_DEPTH_BOUNDS_SWAP(min, max) min, max #endif class GPUConstantBuffer; @@ -214,7 +218,7 @@ public: /// The depth buffer to clear. /// The clear depth value. /// The clear stencil value. - API_FUNCTION() virtual void ClearDepth(GPUTextureView* depthBuffer, float depthValue = GPU_DEPTH_CLEAR_VALUE, uint8 stencilValue = 0) = 0; + API_FUNCTION() virtual void ClearDepth(GPUTextureView* depthBuffer, float depthValue = GPU_DEPTH_MAX_VALUE, uint8 stencilValue = 0) = 0; /// /// Clears an unordered access buffer with a float value. @@ -634,9 +638,10 @@ public: /// /// Sets the minimum and maximum depth values for depth bounds test. /// + /// Both values must be between 0.0 and 1.0, MinDepth must be less than or equal to MaxDepth. /// The minimum value for depth bound test. /// The maximum value for depth bound test. - API_FUNCTION() virtual void SetDepthBounds(float minDepth, float maxDepth) = 0; + API_FUNCTION() virtual void SetDepthBounds(float minDepth = 0.0f, float maxDepth = 1.0f) = 0; public: /// diff --git a/Source/Engine/Graphics/RenderTools.cpp b/Source/Engine/Graphics/RenderTools.cpp index 5a7ca9079..d57c6b743 100644 --- a/Source/Engine/Graphics/RenderTools.cpp +++ b/Source/Engine/Graphics/RenderTools.cpp @@ -684,6 +684,20 @@ float RenderTools::TemporalHalton(int32 index, int32 base) Float2 RenderTools::GetDepthBounds(const RenderView& view, const Float3& nearPoint, const Float3& farPoint) { +#if FLAX_REVERSE_Z + Float3 viewNearPoint = Float3::Transform(nearPoint, view.View); + Float3 viewFarPoint = Float3::Transform(farPoint, view.View); + + viewNearPoint.Z = Math::Max(viewNearPoint.Z, view.Near); + viewFarPoint.Z = Math::Min(viewFarPoint.Z, view.Far); + + Float4 clipNearPoint = Matrix::TransformPosition(view.Projection, Float4(0, 0, viewNearPoint.Z, 1)); + Float4 clipFarPoint = Matrix::TransformPosition(view.Projection, Float4(0, 0, viewFarPoint.Z, 1)); + clipNearPoint /= clipNearPoint.W; + clipFarPoint /= clipFarPoint.W; + + return Float2(clipNearPoint.Z, clipFarPoint.Z); +#else // Point closest the view const Float4 nearPointClip = Matrix::TransformPosition(view.ViewProjection(), Float4(nearPoint, 1.0)); float nearDepth = nearPointClip.Z / nearPointClip.W; @@ -700,8 +714,8 @@ Float2 RenderTools::GetDepthBounds(const RenderView& view, const Float3& nearPoi nearDepth = Math::Clamp(nearDepth, 0.0f, 1.0f); farDepth = Math::Clamp(farDepth, nearDepth, 1.0f); - // TODO: swap depths when using reversed depth buffer return Float2(nearDepth, farDepth); +#endif } Float2 RenderTools::GetDepthBounds(const RenderView& view, const BoundingSphere& bounds) @@ -713,6 +727,24 @@ Float2 RenderTools::GetDepthBounds(const RenderView& view, const BoundingSphere& Float2 RenderTools::GetDepthBounds(const RenderView& view, const Span& points) { +#if FLAX_REVERSE_Z + // Find min and max view depth range for list of points + float nearDepth = view.Far, farDepth = view.Near; + for (int32 i = 0; i < points.Length(); i++) + { + const Float3 viewPoint = Float3::Transform(points[i], view.View); + nearDepth = Math::Min(nearDepth, viewPoint.Z); + farDepth = Math::Max(farDepth, viewPoint.Z); + } + + // Project end points to clip space + Float4 clipNearPoint = Matrix::TransformPosition(view.Projection, Float4(0, 0, nearDepth, 1)); + Float4 clipFarPoint = Matrix::TransformPosition(view.Projection, Float4(0, 0, farDepth, 1)); + clipNearPoint /= clipNearPoint.W; + clipFarPoint /= clipFarPoint.W; + + return Float2(clipNearPoint.Z, clipFarPoint.Z); +#else // Find min and max depth range for list of points float nearDepth = 1.0f, farDepth = 0.0f; for (int32 i = 0; i < points.Length(); i++) @@ -729,8 +761,8 @@ Float2 RenderTools::GetDepthBounds(const RenderView& view, const Span& p nearDepth = Math::Clamp(nearDepth, 0.0f, 1.0f); farDepth = Math::Clamp(farDepth, nearDepth, 1.0f); - // TODO: swap depths when using reversed depth buffer return Float2(nearDepth, farDepth); +#endif } Float2 RenderTools::GetDepthBounds(const RenderView& view, const BoundingBox& bounds) @@ -749,11 +781,21 @@ Float2 RenderTools::GetDepthBounds(const RenderView& view, const OrientedBoundin float RenderTools::GetDepthBounds(const RenderView& view, const Float3& point, bool near) { +#if FLAX_REVERSE_Z + Float3 viewPoint = Float3::Transform(point, view.View); + viewPoint.Z = Math::Clamp(viewPoint.Z, view.Near, view.Far); + + Float4 clipPoint = Matrix::TransformPosition(view.Projection, Float4(0, 0, viewPoint.Z, 1)); + clipPoint /= clipPoint.W; + + return clipPoint.Z; +#else const Float4 pointClip = Matrix::TransformPosition(view.ViewProjection(), Float4(point, 1.0)); float depth = pointClip.Z / pointClip.W; if (depth >= 1.0f) depth = near ? 0.0f : 1.0f; // Point is behind the view return Math::Clamp(depth, 0.0f, 1.0f); +#endif } float RenderTools::GetDepthBounds(const RenderView& view, float viewDistance, bool near) diff --git a/Source/Engine/Graphics/RenderTools.h b/Source/Engine/Graphics/RenderTools.h index 8c565eed5..87880d084 100644 --- a/Source/Engine/Graphics/RenderTools.h +++ b/Source/Engine/Graphics/RenderTools.h @@ -173,7 +173,12 @@ public: static Float2 GetDepthBounds(const RenderView& view, const OrientedBoundingBox& bounds); static float GetDepthBounds(const RenderView& view, const Float3& point, bool near); static float GetDepthBounds(const RenderView& view, float viewDistance, bool near); - static constexpr float DepthBoundMaxBackground = 1.0f - 0.0000001f; // Skip background/sky pixels from shading + // Skip background/sky pixels from shading +#if FLAX_REVERSE_Z + static constexpr float DepthBoundMaxBackground = 0.0000001f; +#else + static constexpr float DepthBoundMaxBackground = 1.0f - 0.0000001f; +#endif // Calculates error for a given render target format to reduce floating-point precision artifacts via QuantizeColor (from Noise.hlsl). static Float3 GetColorQuantizationError(PixelFormat format); diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp index 277272181..0aded637a 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp @@ -796,9 +796,8 @@ void GPUContextVulkan::OnDrawCall() if (_psDirtyFlag && pipelineState && (_rtDepth || _rtCount)) { _psDirtyFlag = false; - const auto cmdBuffer = _cmdBufferManager->GetCmdBuffer()->GetHandle(); const auto pipeline = pipelineState->GetState(_renderPass, _vertexLayout); - vkCmdBindPipeline(cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); + vkCmdBindPipeline(cmdBuffer->GetHandle(), VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); RENDER_STAT_PS_STATE_CHANGE(); if (_depthBoundsEnable && (!_currentState || !_currentState->DepthBoundsEnable)) { diff --git a/Source/Engine/Renderer/AmbientOcclusionPass.cpp b/Source/Engine/Renderer/AmbientOcclusionPass.cpp index 2766fe465..02f03094f 100644 --- a/Source/Engine/Renderer/AmbientOcclusionPass.cpp +++ b/Source/Engine/Renderer/AmbientOcclusionPass.cpp @@ -536,7 +536,7 @@ void AmbientOcclusionPass::Render(RenderContext& renderContext) } // Apply - context->SetDepthBounds(0.0f, RenderTools::GetDepthBounds(renderContext.View, aoSettings.FadeOutDistance, false)); + context->SetDepthBounds(GPU_DEPTH_BOUNDS_SWAP(GPU_DEPTH_MIN_VALUE, RenderTools::GetDepthBounds(renderContext.View, aoSettings.FadeOutDistance, false))); context->BindSR(SSAO_TEXTURE_SLOT0, m_finalResults->ViewArray()); context->SetViewportAndScissors((float)m_sizeX, (float)m_sizeY); context->SetState(settings.SkipHalfPixels ? _psApplyHalf : _psApply); diff --git a/Source/Engine/Renderer/LightPass.cpp b/Source/Engine/Renderer/LightPass.cpp index 1cad5fa31..bde260423 100644 --- a/Source/Engine/Renderer/LightPass.cpp +++ b/Source/Engine/Renderer/LightPass.cpp @@ -283,7 +283,7 @@ void LightPass::RenderLights(RenderContextBatch& renderContextBatch, GPUTextureV if (_depthBounds) { Float2 minMaxDepth = RenderTools::GetDepthBounds(view, BoundingSphere(light.Position, light.Radius)); - context->SetDepthBounds(minMaxDepth.X, minMaxDepth.Y); + context->SetDepthBounds(GPU_DEPTH_BOUNDS_SWAP(minMaxDepth.X, minMaxDepth.Y)); } context->UpdateCB(cb0, &perLight); context->BindCB(0, cb0); @@ -331,7 +331,7 @@ void LightPass::RenderLights(RenderContextBatch& renderContextBatch, GPUTextureV if (_depthBounds) { Float2 minMaxDepth = RenderTools::GetDepthBounds(view, BoundingSphere(light.Position, light.Radius)); - context->SetDepthBounds(minMaxDepth.X, minMaxDepth.Y); + context->SetDepthBounds(GPU_DEPTH_BOUNDS_SWAP(minMaxDepth.X, minMaxDepth.Y)); } context->UpdateCB(cb0, &perLight); context->BindCB(0, cb0); @@ -365,7 +365,7 @@ void LightPass::RenderLights(RenderContextBatch& renderContextBatch, GPUTextureV // Calculate lighting if (_depthBounds) - context->SetDepthBounds(0.0f, RenderTools::DepthBoundMaxBackground); + context->SetDepthBounds(GPU_DEPTH_BOUNDS_SWAP(GPU_DEPTH_MIN_VALUE, RenderTools::DepthBoundMaxBackground)); context->UpdateCB(cb0, &perLight); context->BindCB(0, cb0); context->BindCB(1, cb1); @@ -398,7 +398,7 @@ void LightPass::RenderLights(RenderContextBatch& renderContextBatch, GPUTextureV if (_depthBounds) { Float2 minMaxDepth = RenderTools::GetDepthBounds(view, BoundingSphere(light.Position, light.Radius)); - context->SetDepthBounds(minMaxDepth.X, minMaxDepth.Y); + context->SetDepthBounds(GPU_DEPTH_BOUNDS_SWAP(minMaxDepth.X, minMaxDepth.Y)); } context->UpdateCB(cb0, &perLight); context->BindCB(0, cb0); @@ -411,7 +411,7 @@ void LightPass::RenderLights(RenderContextBatch& renderContextBatch, GPUTextureV // Restore state if (_depthBounds) - context->SetDepthBounds(0, 1); + context->SetDepthBounds(); context->ResetRenderTarget(); context->ResetSR(); context->ResetCB(); diff --git a/Source/Engine/Renderer/ReflectionsPass.cpp b/Source/Engine/Renderer/ReflectionsPass.cpp index 9723b4c30..1dbd0cbc2 100644 --- a/Source/Engine/Renderer/ReflectionsPass.cpp +++ b/Source/Engine/Renderer/ReflectionsPass.cpp @@ -347,7 +347,7 @@ void ReflectionsPass::Render(RenderContext& renderContext, GPUTextureView* light // Setup depth bounds (if device supports it) if (_depthBounds) - context->SetDepthBounds(minMaxDepth.X, minMaxDepth.Y); + context->SetDepthBounds(GPU_DEPTH_BOUNDS_SWAP(minMaxDepth.X, minMaxDepth.Y)); // Pack probe properties buffer probe.SetShaderData(data.PData); @@ -368,7 +368,7 @@ void ReflectionsPass::Render(RenderContext& renderContext, GPUTextureView* light context->UnBindSR(4); context->ResetRenderTarget(); if (_depthBounds) - context->SetDepthBounds(0, 1); + context->SetDepthBounds(); } // Screen Space Reflections pass @@ -417,7 +417,7 @@ void ReflectionsPass::Render(RenderContext& renderContext, GPUTextureView* light if (_depthBounds) { context->SetRenderTarget(depthBufferRTV, lightBuffer); - context->SetDepthBounds(0, RenderTools::DepthBoundMaxBackground); + context->SetDepthBounds(GPU_DEPTH_BOUNDS_SWAP(GPU_DEPTH_MIN_VALUE, RenderTools::DepthBoundMaxBackground)); } else context->SetRenderTarget(lightBuffer); @@ -430,7 +430,7 @@ void ReflectionsPass::Render(RenderContext& renderContext, GPUTextureView* light context->SetState(_psCombinePass); context->DrawFullscreenTriangle(); if (_depthBounds) - context->SetDepthBounds(0, 1); + context->SetDepthBounds(); } RenderTargetPool::Release(ssrBuffer); diff --git a/Source/Engine/Renderer/ShadowsPass.cpp b/Source/Engine/Renderer/ShadowsPass.cpp index b21a37e9d..ccdf2fff5 100644 --- a/Source/Engine/Renderer/ShadowsPass.cpp +++ b/Source/Engine/Renderer/ShadowsPass.cpp @@ -1147,11 +1147,7 @@ void ShadowsPass::SetupLight(ShadowsCustomBuffer& shadows, RenderContext& render void ShadowsPass::ClearShadowMapTile(GPUContext* context, GPUConstantBuffer* quadShaderCB, QuadShaderData& quadShaderData) const { // Color.r is used by PS_DepthClear in Quad shader to clear depth -#if FLAX_REVERSE_Z - quadShaderData.Color = Float4::Zero; -#else - quadShaderData.Color = Float4::One; -#endif + quadShaderData.Color = GPU_DEPTH_MAX_VALUE; context->UpdateCB(quadShaderCB, &quadShaderData); context->BindCB(0, quadShaderCB); @@ -1753,8 +1749,8 @@ void ShadowsPass::RenderShadowMask(RenderContextBatch& renderContextBatch, Rende if (light.IsPointLight || light.IsSpotLight) minMaxDepth = RenderTools::GetDepthBounds(view, BoundingSphere(light.Position, ((RenderLocalLightData&)light).Radius)); else //if (light.IsDirectionalLight) - minMaxDepth = Float2(0.0f, RenderTools::DepthBoundMaxBackground); - context->SetDepthBounds(minMaxDepth.X, minMaxDepth.Y); + minMaxDepth = Float2(GPU_DEPTH_MIN_VALUE, RenderTools::DepthBoundMaxBackground); + context->SetDepthBounds(GPU_DEPTH_BOUNDS_SWAP(minMaxDepth.X, minMaxDepth.Y)); } if (light.IsPointLight) {