From ff010a6e29be3a968d037e6880ea7a28fa444c27 Mon Sep 17 00:00:00 2001 From: Max Van den Eynde Date: Sun, 19 Jul 2026 18:39:45 +0200 Subject: [PATCH] Fixed bloom in path tracing --- atlas/application/window.cpp | 86 +++++++++++++++++---------- include/atlas/core/default_shaders.h | 23 ++++++- include/photon/illuminate.h | 1 + justfile | 4 ++ photon/path_tracing.cpp | 11 +++- shaders/metal/path_tracing/path.metal | 23 ++++++- tests/path-tracing/main.ascene | 1 + 7 files changed, 113 insertions(+), 36 deletions(-) diff --git a/atlas/application/window.cpp b/atlas/application/window.cpp index e0e8117c..0aba4367 100644 --- a/atlas/application/window.cpp +++ b/atlas/application/window.cpp @@ -932,7 +932,8 @@ Window::Window(const WindowConfiguration &config) #ifdef METAL this->externalMetalView = config.metalTargetView; this->renderToExternalMetalView = this->externalMetalView != nullptr; - this->showHostWindow = config.showHostWindow && !this->renderToExternalMetalView; + this->showHostWindow = + config.showHostWindow && !this->renderToExternalMetalView; #else (void)config.metalTargetView; this->externalMetalView = nullptr; @@ -1638,6 +1639,15 @@ bool Window::stepFrame() { pathTracer->copySrcFramebuffer, pathTracer->copyDstFramebuffer, 0); commandBuffer->performResolve(copy); + + pathTracer->copySrcFramebuffer->attachTexture( + pathTracer->pathTracingTextureBright->texture, 0); + pathTracer->copyDstFramebuffer->attachTexture( + target->brightTexture.texture, 0); + auto brightCopy = opal::ResolveAction::createForColorAttachment( + pathTracer->copySrcFramebuffer, pathTracer->copyDstFramebuffer, + 0); + commandBuffer->performResolve(brightCopy); #endif continue; @@ -1710,7 +1720,8 @@ bool Window::stepFrame() { if (obj->canUseDeferredRendering()) { return; } - if (obj && obj->editorOnly && !this->areEditorControlsEnabled()) return; + if (obj && obj->editorOnly && !this->areEditorControlsEnabled()) + return; obj->setViewMatrix(this->camera->calculateViewMatrix()); obj->setProjectionMatrix(calculateProjectionMatrix()); obj->render(getDeltaTime(), commandBuffer, @@ -1729,7 +1740,8 @@ bool Window::stepFrame() { } for (auto &obj : this->lateForwardRenderables) { - if (obj && obj->editorOnly && !this->areEditorControlsEnabled()) continue; + if (obj && obj->editorOnly && !this->areEditorControlsEnabled()) + continue; obj->setViewMatrix(this->camera->calculateViewMatrix()); obj->setProjectionMatrix(calculateProjectionMatrix()); obj->render(getDeltaTime(), commandBuffer, @@ -1748,7 +1760,8 @@ bool Window::stepFrame() { renderEditorGrid(commandBuffer); for (auto &obj : this->firstRenderables) { - if (obj && obj->editorOnly && !this->areEditorControlsEnabled()) continue; + if (obj && obj->editorOnly && !this->areEditorControlsEnabled()) + continue; obj->setViewMatrix(this->camera->calculateViewMatrix()); obj->setProjectionMatrix(calculateProjectionMatrix()); obj->render(getDeltaTime(), commandBuffer, @@ -1759,7 +1772,8 @@ bool Window::stepFrame() { if (obj->renderLateForward) { continue; } - if (obj && obj->editorOnly && !this->areEditorControlsEnabled()) continue; + if (obj && obj->editorOnly && !this->areEditorControlsEnabled()) + continue; obj->setViewMatrix(this->camera->calculateViewMatrix()); obj->setProjectionMatrix(calculateProjectionMatrix()); obj->render(getDeltaTime(), commandBuffer, @@ -1767,7 +1781,8 @@ bool Window::stepFrame() { } updateFluidCaptures(commandBuffer); for (auto &obj : this->lateForwardRenderables) { - if (obj && obj->editorOnly && !this->areEditorControlsEnabled()) continue; + if (obj && obj->editorOnly && !this->areEditorControlsEnabled()) + continue; obj->setViewMatrix(this->camera->calculateViewMatrix()); obj->setProjectionMatrix(calculateProjectionMatrix()); obj->render(getDeltaTime(), commandBuffer, @@ -1812,7 +1827,8 @@ bool Window::stepFrame() { this->currentRenderTarget = this->screenRenderTarget.get(); renderEditorGrid(commandBuffer); for (auto &obj : this->firstRenderables) { - if (obj && obj->editorOnly && !this->areEditorControlsEnabled()) continue; + if (obj && obj->editorOnly && !this->areEditorControlsEnabled()) + continue; obj->setViewMatrix(this->camera->calculateViewMatrix()); obj->setProjectionMatrix(calculateProjectionMatrix()); obj->render(getDeltaTime(), commandBuffer, @@ -1823,7 +1839,8 @@ bool Window::stepFrame() { if (obj->renderLateForward) { continue; } - if (obj && obj->editorOnly && !this->areEditorControlsEnabled()) continue; + if (obj && obj->editorOnly && !this->areEditorControlsEnabled()) + continue; obj->setViewMatrix(this->camera->calculateViewMatrix()); obj->setProjectionMatrix(calculateProjectionMatrix()); obj->render(getDeltaTime(), commandBuffer, @@ -1833,7 +1850,8 @@ bool Window::stepFrame() { updateFluidCaptures(commandBuffer); for (auto &obj : this->lateForwardRenderables) { - if (obj && obj->editorOnly && !this->areEditorControlsEnabled()) continue; + if (obj && obj->editorOnly && !this->areEditorControlsEnabled()) + continue; obj->setViewMatrix(this->camera->calculateViewMatrix()); obj->setProjectionMatrix(calculateProjectionMatrix()); obj->render(getDeltaTime(), commandBuffer, @@ -1846,7 +1864,8 @@ bool Window::stepFrame() { } for (auto &obj : this->preferenceRenderables) { - if (obj && obj->editorOnly && !this->areEditorControlsEnabled()) continue; + if (obj && obj->editorOnly && !this->areEditorControlsEnabled()) + continue; obj->setViewMatrix(this->camera->calculateViewMatrix()); obj->setProjectionMatrix(calculateProjectionMatrix()); obj->render(getDeltaTime(), commandBuffer, shouldRefreshPipeline(obj)); @@ -2068,11 +2087,10 @@ void Window::setEditorKeyboardTransformAxes(int axes) { if (!editorKeyboardTransform) return; editorKeyboardTransformAxes = std::clamp(axes, 1, 7); - editorActiveGizmoAxis = - editorKeyboardTransformAxes == 1 ? 1 - : editorKeyboardTransformAxes == 2 ? 2 - : editorKeyboardTransformAxes == 4 ? 3 - : 0; + editorActiveGizmoAxis = editorKeyboardTransformAxes == 1 ? 1 + : editorKeyboardTransformAxes == 2 ? 2 + : editorKeyboardTransformAxes == 4 ? 3 + : 0; } void Window::finishEditorKeyboardTransform(bool commit) { @@ -2694,12 +2712,15 @@ void Window::updateEditorDrag(float x, float y, float scale) { std::max(0.05f, editorDragStartObjectScale.z + scaleDelta); } if (editorTransformSnapping) { - nextScale.x = std::round(nextScale.x / editorTransformSnapIncrement) * - editorTransformSnapIncrement; - nextScale.y = std::round(nextScale.y / editorTransformSnapIncrement) * - editorTransformSnapIncrement; - nextScale.z = std::round(nextScale.z / editorTransformSnapIncrement) * - editorTransformSnapIncrement; + nextScale.x = + std::round(nextScale.x / editorTransformSnapIncrement) * + editorTransformSnapIncrement; + nextScale.y = + std::round(nextScale.y / editorTransformSnapIncrement) * + editorTransformSnapIncrement; + nextScale.z = + std::round(nextScale.z / editorTransformSnapIncrement) * + editorTransformSnapIncrement; nextScale.x = std::max(0.001f, nextScale.x); nextScale.y = std::max(0.001f, nextScale.y); nextScale.z = std::max(0.001f, nextScale.z); @@ -2735,8 +2756,8 @@ void Window::updateEditorKeyboardTransform(float x, float y, float scale) { const float dx = editorKeyboardAccumulatedX / effectiveScale; const float dy = editorKeyboardAccumulatedY / effectiveScale; const int axes = editorKeyboardTransformAxes; - const float distance = glm::length(editorDragStartPosition.toGlm() - - camera->position.toGlm()); + const float distance = + glm::length(editorDragStartPosition.toGlm() - camera->position.toGlm()); if (editorControlMode == EditorControlMode::Move) { glm::vec3 delta(0.0f); @@ -2752,10 +2773,8 @@ void Window::updateEditorKeyboardTransform(float x, float y, float scale) { const float sensitivity = std::max(0.0025f, distance * 0.0025f); delta = (right * dx + up * dy) * sensitivity; } else { - const float viewWidth = - std::max(1.0f, static_cast(width)); - const float viewHeight = - std::max(1.0f, static_cast(height)); + const float viewWidth = std::max(1.0f, static_cast(width)); + const float viewHeight = std::max(1.0f, static_cast(height)); const glm::mat4 viewProjection = calculateProjectionMatrix() * camera->calculateViewMatrix(); const glm::vec3 center = editorDragStartPosition.toGlm(); @@ -3992,10 +4011,9 @@ void Window::addRenderTarget(RenderTarget *target) { } void Window::removeRenderTarget(RenderTarget *target) { - this->renderTargets.erase( - std::remove(this->renderTargets.begin(), this->renderTargets.end(), - target), - this->renderTargets.end()); + this->renderTargets.erase(std::remove(this->renderTargets.begin(), + this->renderTargets.end(), target), + this->renderTargets.end()); if (this->currentRenderTarget == target) { this->currentRenderTarget = nullptr; } @@ -4870,7 +4888,8 @@ void Window::captureFluidReflection( if (dynamic_cast(obj) == &fluid) { continue; } - if (obj && obj->editorOnly && !this->areEditorControlsEnabled()) continue; + if (obj && obj->editorOnly && !this->areEditorControlsEnabled()) + continue; obj->setViewMatrix(view); obj->setProjectionMatrix(projection); obj->render(getDeltaTime(), commandBuffer, @@ -4997,7 +5016,8 @@ void Window::captureFluidRefraction( if (dynamic_cast(obj) == &fluid) { continue; } - if (obj && obj->editorOnly && !this->areEditorControlsEnabled()) continue; + if (obj && obj->editorOnly && !this->areEditorControlsEnabled()) + continue; obj->setViewMatrix(view); obj->setProjectionMatrix(projection); obj->render(getDeltaTime(), commandBuffer, diff --git a/include/atlas/core/default_shaders.h b/include/atlas/core/default_shaders.h index f6391572..72532f51 100644 --- a/include/atlas/core/default_shaders.h +++ b/include/atlas/core/default_shaders.h @@ -7561,6 +7561,7 @@ R"( float3(vertices[bj2].tangent) * bb2, kernel void main0(texture2d outTex [[texture(0)]], texture2d prevTex [[texture(1)]], + texture2d brightTex [[texture(2)]], instance_acceleration_structure sceneAS [[buffer(0)]], constant CameraUniforms &cam [[buffer(1)]], constant Material *materials [[buffer(2)]], @@ -7632,7 +7633,27 @@ kernel void main0(texture2d outTex [[texture(0)]], float3 accum = (prevColor.xyz * frameIndex + color) / (frameIndex + 1); accum = clampLuminance(accum, 24.0); - outTex.write(float4(accum, 1.0), gid); + + constexpr float bloomThreshold = 1.0; + constexpr float bloomKnee = 0.5; + + float brightness = luminance(accum); + float soft = clamp( + brightness - bloomThreshold + bloomKnee, + 0.0, + bloomKnee * 2.0 + ); + + soft = soft * soft / max(bloomKnee * 4.0, 0.00001); + + float contribution = + max(brightness - bloomThreshold, soft) / + max(brightness, 0.00001); + + float3 brightColor = accum * contribution; + + outTex.write(float4(accum, 1.0), gid); + brightTex.write(float4(brightColor, 1.0), gid); } )", }; diff --git a/include/photon/illuminate.h b/include/photon/illuminate.h index 9aa61588..b7e4f6b6 100644 --- a/include/photon/illuminate.h +++ b/include/photon/illuminate.h @@ -90,6 +90,7 @@ class PathTracing { std::shared_ptr pathTracingTexture; /** @brief Previous frame output texture used for accumulation. */ std::shared_ptr pathTracingTexturePrev; + std::shared_ptr pathTracingTextureBright; /** @brief Rays traced per pixel each dispatch. */ int raysPerPixel = 4; diff --git a/justfile b/justfile index 3f1b1502..b8a19949 100644 --- a/justfile +++ b/justfile @@ -120,3 +120,7 @@ release backend="AUTO": run-docs: just docs python3 -m http.server 8000 --directory docs/html + +editor backend="AUTO" bezel_native="OFF": + just target AtlasEditor {{ backend }} {{ bezel_native }} + "./build/bin/Atlas Engine.app/Contents/MacOS/Atlas Engine" diff --git a/photon/path_tracing.cpp b/photon/path_tracing.cpp index a2126932..ca174444 100644 --- a/photon/path_tracing.cpp +++ b/photon/path_tracing.cpp @@ -178,6 +178,9 @@ void photon::PathTracing::init() { pathTracingTexturePrev = std::make_shared( Texture::create(outputWidth, outputHeight, opal::TextureFormat::Rgba16F, opal::TextureDataFormat::Rgba, TextureType::Color)); + pathTracingTextureBright = std::make_shared( + Texture::create(outputWidth, outputHeight, opal::TextureFormat::Rgba16F, + opal::TextureDataFormat::Rgba, TextureType::Color)); copySrcFramebuffer = std::make_shared(); copyDstFramebuffer = std::make_shared(); @@ -187,7 +190,8 @@ void photon::PathTracing::resizeOutput(int width, int height) { const int newWidth = std::max(1, width); const int newHeight = std::max(1, height); if (newWidth == outputWidth && newHeight == outputHeight && - pathTracingTexture != nullptr && pathTracingTexturePrev != nullptr) { + pathTracingTexture != nullptr && pathTracingTexturePrev != nullptr && + pathTracingTextureBright != nullptr) { return; } @@ -199,6 +203,9 @@ void photon::PathTracing::resizeOutput(int width, int height) { pathTracingTexturePrev = std::make_shared( Texture::create(outputWidth, outputHeight, opal::TextureFormat::Rgba16F, opal::TextureDataFormat::Rgba, TextureType::Color)); + pathTracingTextureBright = std::make_shared( + Texture::create(outputWidth, outputHeight, opal::TextureFormat::Rgba16F, + opal::TextureDataFormat::Rgba, TextureType::Color)); frameIndex = 0; } @@ -743,6 +750,8 @@ void photon::PathTracing::render( pathTracingPipeline->bindTexture("outTex", pathTracingTexture->texture, 0); pathTracingPipeline->bindTexture("prevTex", pathTracingTexturePrev->texture, 1); + pathTracingPipeline->bindTexture("brightTex", + pathTracingTextureBright->texture, 2); static std::shared_ptr fallbackSkyboxTexture = nullptr; if (fallbackSkyboxTexture == nullptr) { diff --git a/shaders/metal/path_tracing/path.metal b/shaders/metal/path_tracing/path.metal index 4634b631..440d4a3a 100644 --- a/shaders/metal/path_tracing/path.metal +++ b/shaders/metal/path_tracing/path.metal @@ -1184,6 +1184,7 @@ float3 sampleRadiance(uint2 gid, uint sampleIndex, uint w, kernel void main0(texture2d outTex [[texture(0)]], texture2d prevTex [[texture(1)]], + texture2d brightTex [[texture(2)]], instance_acceleration_structure sceneAS [[buffer(0)]], constant CameraUniforms &cam [[buffer(1)]], constant Material *materials [[buffer(2)]], @@ -1255,5 +1256,25 @@ kernel void main0(texture2d outTex [[texture(0)]], float3 accum = (prevColor.xyz * frameIndex + color) / (frameIndex + 1); accum = clampLuminance(accum, 24.0); - outTex.write(float4(accum, 1.0), gid); + + constexpr float bloomThreshold = 1.0; + constexpr float bloomKnee = 0.5; + + float brightness = luminance(accum); + float soft = clamp( + brightness - bloomThreshold + bloomKnee, + 0.0, + bloomKnee * 2.0 + ); + + soft = soft * soft / max(bloomKnee * 4.0, 0.00001); + + float contribution = + max(brightness - bloomThreshold, soft) / + max(brightness, 0.00001); + + float3 brightColor = accum * contribution; + + outTex.write(float4(accum, 1.0), gid); + brightTex.write(float4(brightColor, 1.0), gid); } diff --git a/tests/path-tracing/main.ascene b/tests/path-tracing/main.ascene index 7a85fad8..f2c25464 100644 --- a/tests/path-tracing/main.ascene +++ b/tests/path-tracing/main.ascene @@ -132,6 +132,7 @@ "targets": [ { "display": true, + "effects": [], "name": "Main Target", "render": true, "type": "scene"