From beb0df4e4aa7a5513dac269d0631e97411e759ef Mon Sep 17 00:00:00 2001 From: Max Van den Eynde Date: Tue, 21 Jul 2026 12:59:33 +0200 Subject: [PATCH] Dramatically improve rendering performance --- atlas/application/window.cpp | 99 ++---- atlas/graphics/deferred.cpp | 90 ++++- atlas/graphics/render_target.cpp | 12 +- atlas/object/shader.cpp | 11 + cli/src/create.rs | 7 +- editor/project/projectStore.cpp | 9 +- editor/views/editor/editor.cpp | 36 ++ editor/views/editor/inspector.cpp | 4 +- include/atlas/core/default_shaders.h | 325 +++++++++++++----- include/atlas/core/shader.h | 1 + include/atlas/light.h | 4 +- include/atlas/runtime/atlasScripts.h | 4 +- include/atlas/runtime/context.h | 4 + include/atlas/texture.h | 1 + include/atlas/window.h | 12 + include/opal/opal.h | 5 + include/photon/illuminate.h | 26 +- opal/command_buffer.cpp | 59 +++- opal/metal_state.cpp | 14 + opal/metal_state.h | 5 + opal/texture.cpp | 47 +++ photon/gi.cpp | 28 +- photon/path_tracing.cpp | 244 ++++++++++--- runtime/docs/other.md | 2 +- runtime/lib/context.cpp | 30 +- runtime/lib/runtime.cpp | 7 +- runtime/lib/scripting.cpp | 4 + runtime/scripts/atlas_graphics.js | 3 +- shaders/metal/deferred/deferred.frag.metal | 2 +- shaders/metal/deferred/light.frag.metal | 33 +- shaders/metal/effects/ssr.frag.metal | 53 +-- shaders/metal/path_tracing/path.metal | 96 +++++- shaders/metal/path_tracing/path_denoise.metal | 48 +++ shaders/opengl/deferred/deferred.frag | 2 +- shaders/opengl/deferred/light.frag | 62 ++-- shaders/opengl/effects/ssr.frag | 47 +-- shaders/vulkan/deferred/deferred.frag | 2 +- shaders/vulkan/deferred/light.frag | 65 ++-- shaders/vulkan/effects/ssr.frag | 44 +-- 39 files changed, 1125 insertions(+), 422 deletions(-) create mode 100644 shaders/metal/path_tracing/path_denoise.metal diff --git a/atlas/application/window.cpp b/atlas/application/window.cpp index 1b4ce1ea..9eca9357 100644 --- a/atlas/application/window.cpp +++ b/atlas/application/window.cpp @@ -1613,6 +1613,8 @@ bool Window::stepFrame() { continue; } this->currentRenderTarget = target; + const glm::mat4 targetView = this->camera->calculateViewMatrix(); + const glm::mat4 targetProjection = calculateProjectionMatrix(); setViewportState(0, 0, target->getWidth(), target->getHeight()); updatePipelineStateField(this->depthCompareOp, opal::CompareOp::Less); updatePipelineStateField(this->writeDepth, true); @@ -1630,24 +1632,8 @@ bool Window::stepFrame() { } #ifdef METAL pathTracer->resizeOutput(target->getWidth(), target->getHeight()); - pathTracer->render(commandBuffer); - pathTracer->copySrcFramebuffer->attachTexture( - pathTracer->pathTracingTexture->texture, 0); - pathTracer->copyDstFramebuffer->attachTexture( - target->texture.texture, 0); - auto copy = opal::ResolveAction::createForColorAttachment( - 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); + pathTracer->render(commandBuffer, target->texture.texture, + target->brightTexture.texture); #endif continue; @@ -1712,10 +1698,8 @@ bool Window::stepFrame() { if (meshObject->canUseDeferredRendering()) { continue; } - meshObject->setViewMatrix( - this->camera->calculateViewMatrix()); - meshObject->setProjectionMatrix( - calculateProjectionMatrix()); + meshObject->setViewMatrix(targetView); + meshObject->setProjectionMatrix(targetProjection); meshObject->render(getDeltaTime(), commandBuffer, shouldRefreshPipeline(meshObject)); } @@ -1726,8 +1710,8 @@ bool Window::stepFrame() { } if (obj && obj->editorOnly && !this->areEditorControlsEnabled()) return; - obj->setViewMatrix(this->camera->calculateViewMatrix()); - obj->setProjectionMatrix(calculateProjectionMatrix()); + obj->setViewMatrix(targetView); + obj->setProjectionMatrix(targetProjection); obj->render(getDeltaTime(), commandBuffer, shouldRefreshPipeline(obj)); }; @@ -1746,8 +1730,8 @@ bool Window::stepFrame() { for (auto &obj : this->lateForwardRenderables) { if (obj && obj->editorOnly && !this->areEditorControlsEnabled()) continue; - obj->setViewMatrix(this->camera->calculateViewMatrix()); - obj->setProjectionMatrix(calculateProjectionMatrix()); + obj->setViewMatrix(targetView); + obj->setProjectionMatrix(targetProjection); obj->render(getDeltaTime(), commandBuffer, shouldRefreshPipeline(obj)); } @@ -1766,8 +1750,8 @@ bool Window::stepFrame() { for (auto &obj : this->firstRenderables) { if (obj && obj->editorOnly && !this->areEditorControlsEnabled()) continue; - obj->setViewMatrix(this->camera->calculateViewMatrix()); - obj->setProjectionMatrix(calculateProjectionMatrix()); + obj->setViewMatrix(targetView); + obj->setProjectionMatrix(targetProjection); obj->render(getDeltaTime(), commandBuffer, shouldRefreshPipeline(obj)); } @@ -1778,8 +1762,8 @@ bool Window::stepFrame() { } if (obj && obj->editorOnly && !this->areEditorControlsEnabled()) continue; - obj->setViewMatrix(this->camera->calculateViewMatrix()); - obj->setProjectionMatrix(calculateProjectionMatrix()); + obj->setViewMatrix(targetView); + obj->setProjectionMatrix(targetProjection); obj->render(getDeltaTime(), commandBuffer, shouldRefreshPipeline(obj)); } @@ -1787,8 +1771,8 @@ bool Window::stepFrame() { for (auto &obj : this->lateForwardRenderables) { if (obj && obj->editorOnly && !this->areEditorControlsEnabled()) continue; - obj->setViewMatrix(this->camera->calculateViewMatrix()); - obj->setProjectionMatrix(calculateProjectionMatrix()); + obj->setViewMatrix(targetView); + obj->setProjectionMatrix(targetProjection); obj->render(getDeltaTime(), commandBuffer, shouldRefreshPipeline(obj)); } @@ -1825,6 +1809,8 @@ bool Window::stepFrame() { commandBuffer->clearColor(this->clearColor.r, this->clearColor.g, this->clearColor.b, this->clearColor.a); commandBuffer->clearDepth(1.0f); + const glm::mat4 screenView = this->camera->calculateViewMatrix(); + const glm::mat4 screenProjection = calculateProjectionMatrix(); if (this->renderTargets.empty() && !usesModeScreenTarget) { updateBackbufferTarget(fbWidth, fbHeight); @@ -1833,8 +1819,8 @@ bool Window::stepFrame() { for (auto &obj : this->firstRenderables) { if (obj && obj->editorOnly && !this->areEditorControlsEnabled()) continue; - obj->setViewMatrix(this->camera->calculateViewMatrix()); - obj->setProjectionMatrix(calculateProjectionMatrix()); + obj->setViewMatrix(screenView); + obj->setProjectionMatrix(screenProjection); obj->render(getDeltaTime(), commandBuffer, shouldRefreshPipeline(obj)); } @@ -1845,8 +1831,8 @@ bool Window::stepFrame() { } if (obj && obj->editorOnly && !this->areEditorControlsEnabled()) continue; - obj->setViewMatrix(this->camera->calculateViewMatrix()); - obj->setProjectionMatrix(calculateProjectionMatrix()); + obj->setViewMatrix(screenView); + obj->setProjectionMatrix(screenProjection); obj->render(getDeltaTime(), commandBuffer, shouldRefreshPipeline(obj)); } @@ -1856,8 +1842,8 @@ bool Window::stepFrame() { for (auto &obj : this->lateForwardRenderables) { if (obj && obj->editorOnly && !this->areEditorControlsEnabled()) continue; - obj->setViewMatrix(this->camera->calculateViewMatrix()); - obj->setProjectionMatrix(calculateProjectionMatrix()); + obj->setViewMatrix(screenView); + obj->setProjectionMatrix(screenProjection); obj->render(getDeltaTime(), commandBuffer, shouldRefreshPipeline(obj)); } @@ -1870,8 +1856,8 @@ bool Window::stepFrame() { for (auto &obj : this->preferenceRenderables) { if (obj && obj->editorOnly && !this->areEditorControlsEnabled()) continue; - obj->setViewMatrix(this->camera->calculateViewMatrix()); - obj->setProjectionMatrix(calculateProjectionMatrix()); + obj->setViewMatrix(screenView); + obj->setProjectionMatrix(screenProjection); obj->render(getDeltaTime(), commandBuffer, shouldRefreshPipeline(obj)); } @@ -1888,7 +1874,7 @@ bool Window::stepFrame() { obj->render(getDeltaTime(), commandBuffer, shouldRefreshPipeline(obj)); } - this->lastViewMatrix = this->camera->calculateViewMatrix(); + this->lastViewMatrix = screenView; commandBuffer->endPass(); commandBuffer->commit(); @@ -4139,7 +4125,7 @@ void Window::setDefaultFramebufferRenderingEnabled(bool enabled) { void Window::renderLightsToShadowMaps( std::shared_ptr commandBuffer) { - if (this->currentScene == nullptr) { + if (this->currentScene == nullptr || this->usePathTracing) { return; } @@ -4152,22 +4138,8 @@ void Window::renderLightsToShadowMaps( this->shadowUpdateCooldown = std::max(0.0f, this->shadowUpdateCooldown - this->deltaTime); - - bool cameraMoved = false; - if (this->camera != nullptr) { - glm::vec3 currentPos = this->camera->position.toGlm(); - glm::vec3 currentDir = this->camera->getFrontVector().toGlm(); - if (!this->lastShadowCameraPosition.has_value() || - !this->lastShadowCameraDirection.has_value()) { - cameraMoved = true; - } else { - glm::vec3 lastPos = this->lastShadowCameraPosition->toGlm(); - glm::vec3 lastDir = this->lastShadowCameraDirection->toGlm(); - if ((glm::length(currentPos - lastPos) > 0.25f) || - glm::length(currentDir - lastDir) > 0.25f) { - cameraMoved = true; - } - } + if (this->shadowUpdateCooldown > 0.0f) { + return; } bool lightsChanged = false; @@ -4313,7 +4285,7 @@ void Window::renderLightsToShadowMaps( !this->hasShadowCasterSignature || this->lastShadowCasterSignature != shadowCasterSignature; - if (cameraMoved || lightsChanged || castersMoved) { + if (lightsChanged || castersMoved) { this->shadowMapsDirty = true; } @@ -4321,11 +4293,6 @@ void Window::renderLightsToShadowMaps( return; } - if (this->shadowUpdateCooldown > 0.0f && !cameraMoved && !lightsChanged && - !castersMoved) { - return; - } - this->shadowMapsDirty = false; this->shadowUpdateCooldown = this->shadowUpdateInterval; @@ -4817,8 +4784,10 @@ void Window::useDeferredRendering() { RenderTarget(*this, RenderTargetType::Scene)); this->volumetricBuffer = volumetricTarget; auto ssrTarget = std::make_shared( - RenderTarget(*this, RenderTargetType::Scene)); + RenderTarget(*this, RenderTargetType::SSR, this->ssrQuality)); this->ssrFramebuffer = ssrTarget; + this->ssrHistoryFramebuffer = std::make_shared( + RenderTarget(*this, RenderTargetType::SSR, this->ssrQuality)); this->ssaoMapsDirty = true; } diff --git a/atlas/graphics/deferred.cpp b/atlas/graphics/deferred.cpp index 5a98727a..d70197cc 100644 --- a/atlas/graphics/deferred.cpp +++ b/atlas/graphics/deferred.cpp @@ -254,7 +254,6 @@ buildGPUAreaLights(const std::vector &lights, int maxCount) { #ifdef METAL void Window::enableGlobalIllumination() { usesGlobalIllumination = true; - useSSR = true; ddgiSystem = std::make_shared(); ddgiSystem->sampleNormalMaps = false; ddgiSystem->init(); @@ -293,9 +292,16 @@ void Window::deferredRendering( this->volumetricBuffer->getHeight() != targetHeight) { recreateDeferredTargets = true; } + const float ssrScale = this->ssrQuality == 2 ? 0.75f : 0.5f; + const int ssrWidth = std::max(1, static_cast(targetWidth * ssrScale)); + const int ssrHeight = + std::max(1, static_cast(targetHeight * ssrScale)); if (this->ssrFramebuffer == nullptr || - this->ssrFramebuffer->getWidth() != targetWidth || - this->ssrFramebuffer->getHeight() != targetHeight) { + this->ssrFramebuffer->getWidth() != ssrWidth || + this->ssrFramebuffer->getHeight() != ssrHeight || + this->ssrHistoryFramebuffer == nullptr || + this->ssrHistoryFramebuffer->getWidth() != ssrWidth || + this->ssrHistoryFramebuffer->getHeight() != ssrHeight) { recreateDeferredTargets = true; } if (recreateDeferredTargets) { @@ -304,7 +310,9 @@ void Window::deferredRendering( this->volumetricBuffer = std::make_shared( RenderTarget(*this, RenderTargetType::Scene)); this->ssrFramebuffer = std::make_shared( - RenderTarget(*this, RenderTargetType::Scene)); + RenderTarget(*this, RenderTargetType::SSR, this->ssrQuality)); + this->ssrHistoryFramebuffer = std::make_shared( + RenderTarget(*this, RenderTargetType::SSR, this->ssrQuality)); this->ssaoBuffer = std::make_shared( RenderTarget(*this, RenderTargetType::SSAO)); this->ssaoBlurBuffer = std::make_shared( @@ -417,6 +425,8 @@ void Window::deferredRendering( } } + const glm::mat4 deferredView = this->camera->calculateViewMatrix(); + const glm::mat4 deferredProjection = calculateProjectionMatrix(); auto renderDeferredRenderable = [&](Renderable *obj) { if (obj == nullptr || !obj->canUseDeferredRendering()) { return; @@ -454,8 +464,8 @@ void Window::deferredRendering( pipelineEntry.rasterizerMode = this->rasterizerMode; } - obj->setViewMatrix(this->camera->calculateViewMatrix()); - obj->setProjectionMatrix(calculateProjectionMatrix()); + obj->setViewMatrix(deferredView); + obj->setProjectionMatrix(deferredProjection); obj->setPipeline(pipelineEntry.pipeline); if (auto *coreObject = dynamic_cast(obj)) { ShaderProgram originalProgram = coreObject->shaderProgram; @@ -475,6 +485,9 @@ void Window::deferredRendering( commandBuffer->endPass(); this->gBuffer->unbind(); + if (this->useSSR) { + commandBuffer->generateMipmaps(this->gBuffer->depthTexture.texture); + } #ifdef METAL if (usesGlobalIllumination) { @@ -765,7 +778,9 @@ void Window::deferredRendering( #endif // Cycle though directional lights - for (auto *light : scene->directionalLights) { + for (size_t lightIndex = 0; lightIndex < scene->directionalLights.size(); + ++lightIndex) { + auto *light = scene->directionalLights[lightIndex]; if (!light->doesCastShadows) { continue; } @@ -794,7 +809,7 @@ void Window::deferredRendering( gpuShadow.bias = shadowParams.bias; gpuShadow.textureIndex = shadow2DSamplerIndex; gpuShadow.farPlane = 0.0f; - gpuShadow._pad1 = 0.0f; + gpuShadow.lightIndex = static_cast(lightIndex); gpuShadow.lightPos = glm::vec3(0.0f); gpuShadow.lightType = 0; gpuShadowParams.push_back(gpuShadow); @@ -805,6 +820,8 @@ void Window::deferredRendering( shadowParams.lightProjection); lightPipeline->setUniform1f(baseName + ".bias", shadowParams.bias); lightPipeline->setUniform1i(baseName + ".lightType", 0); + lightPipeline->setUniform1i(baseName + ".lightIndex", + static_cast(lightIndex)); #endif boundParameters++; @@ -813,7 +830,9 @@ void Window::deferredRendering( } // Cycle though spotlights - for (auto *light : scene->spotlights) { + for (size_t lightIndex = 0; lightIndex < scene->spotlights.size(); + ++lightIndex) { + auto *light = scene->spotlights[lightIndex]; if (!light->doesCastShadows) { continue; } @@ -842,7 +861,7 @@ void Window::deferredRendering( gpuShadow.bias = shadowParams.bias; gpuShadow.textureIndex = shadow2DSamplerIndex; gpuShadow.farPlane = 0.0f; - gpuShadow._pad1 = 0.0f; + gpuShadow.lightIndex = static_cast(lightIndex); gpuShadow.lightPos = glm::vec3(0.0f); gpuShadow.lightType = 1; gpuShadowParams.push_back(gpuShadow); @@ -853,6 +872,8 @@ void Window::deferredRendering( shadowParams.lightProjection); lightPipeline->setUniform1f(baseName + ".bias", shadowParams.bias); lightPipeline->setUniform1i(baseName + ".lightType", 1); + lightPipeline->setUniform1i(baseName + ".lightIndex", + static_cast(lightIndex)); #endif boundParameters++; @@ -860,7 +881,9 @@ void Window::deferredRendering( boundTextures++; } - for (auto *light : scene->areaLights) { + for (size_t lightIndex = 0; lightIndex < scene->areaLights.size(); + ++lightIndex) { + auto *light = scene->areaLights[lightIndex]; if (!light->doesCastShadows) { continue; } @@ -889,7 +912,7 @@ void Window::deferredRendering( gpuShadow.bias = shadowParams.bias; gpuShadow.textureIndex = shadow2DSamplerIndex; gpuShadow.farPlane = 0.0f; - gpuShadow._pad1 = 0.0f; + gpuShadow.lightIndex = static_cast(lightIndex); gpuShadow.lightPos = glm::vec3(0.0f); gpuShadow.lightType = 2; gpuShadowParams.push_back(gpuShadow); @@ -900,6 +923,8 @@ void Window::deferredRendering( shadowParams.lightProjection); lightPipeline->setUniform1f(baseName + ".bias", shadowParams.bias); lightPipeline->setUniform1i(baseName + ".lightType", 2); + lightPipeline->setUniform1i(baseName + ".lightIndex", + static_cast(lightIndex)); #endif boundParameters++; @@ -907,7 +932,9 @@ void Window::deferredRendering( boundTextures++; } - for (auto *light : scene->pointLights) { + for (size_t lightIndex = 0; lightIndex < scene->pointLights.size(); + ++lightIndex) { + auto *light = scene->pointLights[lightIndex]; if (!light->doesCastShadows) { continue; } @@ -931,7 +958,7 @@ void Window::deferredRendering( gpuShadow.bias = 0.0f; gpuShadow.textureIndex = boundCubemaps; gpuShadow.farPlane = light->distance; - gpuShadow._pad1 = 0.0f; + gpuShadow.lightIndex = static_cast(lightIndex); gpuShadow.lightPos = glm::vec3(static_cast(light->position.x), static_cast(light->position.y), static_cast(light->position.z)); @@ -942,6 +969,8 @@ void Window::deferredRendering( lightPipeline->setUniform3f(baseName + ".lightPos", light->position.x, light->position.y, light->position.z); lightPipeline->setUniform1i(baseName + ".lightType", 3); + lightPipeline->setUniform1i(baseName + ".lightIndex", + static_cast(lightIndex)); #endif boundParameters++; @@ -1065,6 +1094,12 @@ void Window::deferredRendering( commandBuffer->endPass(); } + if (useSSR && this->ssrFramebuffer == nullptr) { + this->ssrFramebuffer = std::make_shared( + RenderTarget(*this, RenderTargetType::SSR, this->ssrQuality)); + this->ssrHistoryFramebuffer = std::make_shared( + RenderTarget(*this, RenderTargetType::SSR, this->ssrQuality)); + } if (this->ssrFramebuffer != nullptr && useSSR) { if (targetPassActive) { commandBuffer->endPass(); @@ -1097,6 +1132,8 @@ void Window::deferredRendering( ssrPipeline->bindTexture2D("gMaterial", gBuffer->gMaterial.id, 3); ssrPipeline->bindTexture2D("sceneColor", target->texture.id, 4); ssrPipeline->bindTexture2D("gDepth", gBuffer->depthTexture.id, 5); + ssrPipeline->bindTexture2D("historyTexture", + ssrHistoryFramebuffer->texture.id, 7); if (scene->skybox != nullptr && scene->skybox->cubemap.id != 0) { ssrPipeline->bindTextureCubemap("skybox", scene->skybox->cubemap.id, 6); @@ -1116,11 +1153,25 @@ void Window::deferredRendering( glm::inverse(projectionMatrix)); ssrPipeline->setUniform3f("cameraPosition", camera->position.x, camera->position.y, camera->position.z); - ssrPipeline->setUniform1f("maxDistance", 40.0f); - ssrPipeline->setUniform1f("resolution", 0.5f); - ssrPipeline->setUniform1i("steps", 64); - ssrPipeline->setUniform1f("thickness", 1.0f); - ssrPipeline->setUniform1f("maxRoughness", 0.5f); + static constexpr float maxDistances[] = {20.0f, 32.0f, 48.0f}; + static constexpr int stepCounts[] = {20, 36, 56}; + static constexpr float thicknesses[] = {1.5f, 1.0f, 0.65f}; + static constexpr float roughnessLimits[] = {0.3f, 0.45f, 0.6f}; + const int quality = std::clamp(this->ssrQuality, 0, 2); + ssrPipeline->setUniform1f("maxDistance", maxDistances[quality]); + ssrPipeline->setUniform1f("resolution", quality == 2 ? 0.75f : 0.5f); + ssrPipeline->setUniform1i("steps", stepCounts[quality]); + ssrPipeline->setUniform1f("thickness", thicknesses[quality]); + ssrPipeline->setUniform1f("maxRoughness", roughnessLimits[quality]); + float viewDelta = 0.0f; + for (int column = 0; column < 4; ++column) { + viewDelta += glm::length(viewMatrix[column] - + this->lastViewMatrix[column]); + } + ssrPipeline->setUniform1f("historyWeight", + viewDelta < 0.001f ? 0.85f : 0.0f); + ssrPipeline->setUniform1i("debugMode", + this->ssrDebugMode ? 1 : 0); commandBuffer->bindDrawingState(quadState); commandBuffer->bindPipeline(ssrPipeline); @@ -1137,6 +1188,7 @@ void Window::deferredRendering( } if (hasSSRTexture && ssrFramebuffer != nullptr) { target->ssrTexture = ssrFramebuffer->texture; + std::swap(ssrFramebuffer, ssrHistoryFramebuffer); } target->gPosition = gBuffer->gPosition; diff --git a/atlas/graphics/render_target.cpp b/atlas/graphics/render_target.cpp index 6e0a2f8e..1bcea882 100644 --- a/atlas/graphics/render_target.cpp +++ b/atlas/graphics/render_target.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -38,6 +39,8 @@ RenderTarget::RenderTarget(Window &window, RenderTargetType type, float targetScale = window.getRenderScale(); if (type == RenderTargetType::SSAO || type == RenderTargetType::SSAOBlur) { targetScale = window.getSSAORenderScale(); + } else if (type == RenderTargetType::SSR) { + targetScale *= resolution == 2 ? 0.75f : 0.5f; } targetScale = std::clamp(targetScale, 0.1f, 1.0f); @@ -47,7 +50,7 @@ RenderTarget::RenderTarget(Window &window, RenderTargetType type, const auto height = static_cast(scaledHeight); this->type = type; - if (type == RenderTargetType::Scene) { + if (type == RenderTargetType::Scene || type == RenderTargetType::SSR) { fb = opal::Framebuffer::create(width, height); std::vector> colorTextures; for (unsigned int i = 0; i < 2; i++) { @@ -361,10 +364,13 @@ RenderTarget::RenderTarget(Window &window, RenderTargetType type, gMaterial.creationData.height = scaledHeight; gMaterial.type = TextureType::Color; + const uint depthMipLevels = static_cast( + std::floor(std::log2(std::max(width, height)))) + 1; auto gbufferDepth = opal::Texture::create( opal::TextureType::Texture2D, opal::TextureFormat::DepthComponent24, - width, height, opal::TextureDataFormat::DepthComponent, nullptr, 1); - gbufferDepth->setFilterMode(opal::TextureFilterMode::Nearest, + width, height, opal::TextureDataFormat::DepthComponent, nullptr, + depthMipLevels); + gbufferDepth->setFilterMode(opal::TextureFilterMode::NearestMipmapNearest, opal::TextureFilterMode::Nearest); gbufferDepth->setWrapMode(opal::TextureAxis::S, opal::TextureWrapMode::ClampToEdge); diff --git a/atlas/object/shader.cpp b/atlas/object/shader.cpp index 85d986f7..e228df34 100644 --- a/atlas/object/shader.cpp +++ b/atlas/object/shader.cpp @@ -265,6 +265,17 @@ ComputeShader ComputeShader::fromDefaultShader(AtlasComputeShader shader) { #else throw std::runtime_error( "AtlasComputeShader::PathTracer is only supported on Metal"); +#endif + } + case AtlasComputeShader::PathDenoiser: { +#ifdef METAL + computeShader = ComputeShader::fromSource(PATH_DENOISE); + computeShader.fromDefaultShaderType = shader; + ComputeShader::computeShaderCache[shader] = computeShader; + break; +#else + throw std::runtime_error( + "AtlasComputeShader::PathDenoiser is only supported on Metal"); #endif } default: diff --git a/cli/src/create.rs b/cli/src/create.rs index cd7c61aa..63d57317 100644 --- a/cli/src/create.rs +++ b/cli/src/create.rs @@ -30,6 +30,11 @@ supported_platforms = "all" [renderer] default = "((RENDERER_DEFAULT))" global_illumination = ((GLOBAL_ILLUMINATION)) +ssr = false +ssr_quality = 1 +ssr_debug = false +use_upscaling = true +upscaling_ratio = 0.5 [window] dimensions = [1280, 720] @@ -79,7 +84,7 @@ const SCENE_TEMPLATE: &str = r#"{ "globalLight": { "enabled": true, "castsShadows": true, - "shadowResolution": 4096, + "shadowResolution": 2048, }, }, }, diff --git a/editor/project/projectStore.cpp b/editor/project/projectStore.cpp index 4e890656..9bf109a1 100644 --- a/editor/project/projectStore.cpp +++ b/editor/project/projectStore.cpp @@ -69,7 +69,12 @@ QString projectConfig(const QString& name, stream << "[renderer]\n"; stream << "default = \"" << renderer << "\"\n"; stream << "global_illumination = " - << (globalIllumination ? "true" : "false") << "\n\n"; + << (globalIllumination ? "true" : "false") << "\n"; + stream << "ssr = false\n"; + stream << "ssr_quality = 1\n"; + stream << "ssr_debug = false\n"; + stream << "use_upscaling = true\n"; + stream << "upscaling_ratio = 0.5\n\n"; stream << "[window]\n"; stream << "dimensions = [1280, 720]\n"; stream << "mouse_capture = false\n"; @@ -124,7 +129,7 @@ QByteArray starterScene(AtlasProjectTemplate projectTemplate) { "globalLight": { "enabled": true, "castsShadows": true, - "shadowResolution": 4096 + "shadowResolution": 2048 } } } diff --git a/editor/views/editor/editor.cpp b/editor/views/editor/editor.cpp index 4796661a..f9a6d41a 100644 --- a/editor/views/editor/editor.cpp +++ b/editor/views/editor/editor.cpp @@ -1005,7 +1005,28 @@ void EditorWindow::showProjectSettings() { auto *frameLimit = new QSpinBox(&dialog); frameLimit->setRange(0, 1000); frameLimit->setValue(settings.value("project/frameLimit", 0).toInt()); + auto *ssr = new QCheckBox("Enable screen-space reflections", &dialog); + ssr->setChecked(settings.value("project/ssr", false).toBool()); + auto *ssrQuality = new QComboBox(&dialog); + ssrQuality->addItems({"Low", "Medium", "High"}); + ssrQuality->setCurrentIndex( + settings.value("project/ssrQuality", 1).toInt()); + auto *ssrDebug = new QCheckBox("Show SSR hit confidence", &dialog); + ssrDebug->setChecked(settings.value("project/ssrDebug", false).toBool()); + auto *upscaling = new QCheckBox("Enable Metal upscaling", &dialog); + upscaling->setChecked( + settings.value("project/useUpscaling", true).toBool()); + auto *internalScale = new QSpinBox(&dialog); + internalScale->setRange(50, 100); + internalScale->setSuffix("%"); + internalScale->setValue( + settings.value("project/internalScale", 50).toInt()); rendering->addRow("Renderer", renderer); + rendering->addRow(QString(), ssr); + rendering->addRow("SSR quality", ssrQuality); + rendering->addRow(QString(), ssrDebug); + rendering->addRow(QString(), upscaling); + rendering->addRow("Internal render scale", internalScale); rendering->addRow("Frame limit (0 = unlimited)", frameLimit); auto *physics = addPage("Physics", styling::Icon::Wrench, "#A1957D"); auto *gravity = new QLineEdit( @@ -1076,6 +1097,11 @@ void EditorWindow::showProjectSettings() { settings.setValue("project/fullscreen", fullscreen->isChecked()); settings.setValue("project/renderer", renderer->currentText()); settings.setValue("project/frameLimit", frameLimit->value()); + settings.setValue("project/ssr", ssr->isChecked()); + settings.setValue("project/ssrQuality", ssrQuality->currentIndex()); + settings.setValue("project/ssrDebug", ssrDebug->isChecked()); + settings.setValue("project/useUpscaling", upscaling->isChecked()); + settings.setValue("project/internalScale", internalScale->value()); settings.setValue("project/gravity", gravity->text()); settings.setValue("project/fixedStep", fixedStep->text()); settings.setValue("project/inputMap", inputMap->text()); @@ -1114,6 +1140,16 @@ void EditorWindow::showProjectSettings() { setTomlValue(&lines, "renderer", "global_illumination", renderer->currentText() == "PBR + DDGI" ? "true" : "false"); + setTomlValue(&lines, "renderer", "ssr", + ssr->isChecked() ? "true" : "false"); + setTomlValue(&lines, "renderer", "ssr_quality", + QString::number(ssrQuality->currentIndex())); + setTomlValue(&lines, "renderer", "ssr_debug", + ssrDebug->isChecked() ? "true" : "false"); + setTomlValue(&lines, "renderer", "use_upscaling", + upscaling->isChecked() ? "true" : "false"); + setTomlValue(&lines, "renderer", "upscaling_ratio", + QString::number(internalScale->value() / 100.0, 'f', 2)); QSaveFile outputFile(projectFile); const QByteArray contents = lines.join('\n').toUtf8(); if (!outputFile.open(QIODevice::WriteOnly) || diff --git a/editor/views/editor/inspector.cpp b/editor/views/editor/inspector.cpp index 213c367c..d9ea29d4 100644 --- a/editor/views/editor/inspector.cpp +++ b/editor/views/editor/inspector.cpp @@ -279,7 +279,7 @@ QJsonObject environmentSchema() { {"globalLight", QJsonObject{{"enabled", true}, {"castsShadows", true}, - {"shadowResolution", 4096}}}, + {"shadowResolution", 2048}}}, {"clouds", QJsonObject{{"enabled", false}, {"frequency", 4}, @@ -443,7 +443,7 @@ QJsonObject lightSchema(const QString &type) { common.insert("shadowResolution", 2048); if (normalized == "directionallight" || normalized == "sun") { common.insert("direction", QJsonArray{0.0, -1.0, 0.0}); - common.insert("shadowResolution", 4096); + common.insert("shadowResolution", 2048); } else if (normalized == "pointlight") { common.insert("distance", 50.0); } else if (normalized == "spotlight") { diff --git a/include/atlas/core/default_shaders.h b/include/atlas/core/default_shaders.h index 9a48a9ca..a8efab9e 100644 --- a/include/atlas/core/default_shaders.h +++ b/include/atlas/core/default_shaders.h @@ -1831,7 +1831,7 @@ R"(ial.roughness; a = float3(0.0); } out.gAlbedoSpec = float4(a, aoValue); - out.gMaterial = float4(metallicValue, roughnessValue, aoValue, 1.0); + out.gMaterial = float4(metallicValue, roughnessValue, aoValue, fast::clamp(material.reflectivity, 0.0, 1.0)); return out; } )", @@ -3642,7 +3642,7 @@ struct ShadowParameters { float bias0; int textureIndex; float farPlane; - float _pad1; + int lightIndex; float3 lightPos; int lightType; }; @@ -3709,7 +3709,7 @@ struct ShadowParameters_1 { float bias0; int textureIndex; float farPlane; - float _pad1; + int lightIndex; packed_float3 lightPos; int lightType; }; @@ -3822,8 +3822,8 @@ constant spvUnsafeArray _660 = spvUnsafeArray( float2(-0.839999973773956298828125, -0.07400000095367431640625), float2(-0.69599997997283935546875, 0.4569999873638153076171875), float2(-0.20299999415874481201171875, 0.620999991893768310546875), - float2(0.96200001239776)", -R"(611328125, -0.194999992847442626953125), + float2(0.96200001)", +R"(239776611328125, -0.194999992847442626953125), float2(0.472999989986419677734375, -0.4799999892711639404296875), float2(0.518999993801116943359375, 0.767000019550323486328125), float2(0.185000002384185791015625, -0.89300000667572021484375), @@ -3967,6 +3967,9 @@ static inline __attribute__((always_inline)) float calculatePointShadow( } float3 fragToLight = fragPos - shadowParam.lightPos; float currentDepth = length(fragToLight); + if (currentDepth >= shadowParam.farPlane) { + return 0.0; + } float bias0 = 0.0500000007450580596923828125; float shadow = 0.0; float diskRadius = (1.0 + (currentDepth / shadowParam.farPlane)) * @@ -4003,10 +4006,10 @@ static inline __attribute__((always_inline)) float4 sampleTextureAt( if (textureIndex == 1) { return texture2.sample(texture2Smplr, uv); } else { - if (textureIndex == 2) { + if (textureIn)", +R"(dex == 2) { return texture3.sample(texture3Smplr, uv); - )", -R"( } else { + } else { if (textureIndex == 3) { return texture4.sample(texture4Smplr, uv); } else { @@ -4238,10 +4241,10 @@ static inline __attribute__((always_inline)) float3 calcDirectionalLight( thread const float3 &albedo, thread const float &metallic, thread const float &roughness) { float3 L = fast::normalize(-light.direction); - float3 radiance = light.diffuse * fast::max(light.intensity, 0.0); + float3 radiance = light.diffuse * fast:)", +R"(:max(light.intensity, 0.0); float3 param = L; - float3 param_1 = radiance;)", -R"( + float3 param_1 = radiance; float3 param_2 = N; float3 param_3 = V; float3 param_4 = F0; @@ -4452,12 +4455,12 @@ static inline float4 sampleProbeDirectionalRadiance(texture2d ddgiTexture, constant ProbeSpace &ps, uint probeIndex, uint atlasW, uint atlasH, float3 dirWS) { - float2 uv = ddgiAtlasUV(probeIndex, dirWS, ps, atlasW, atlasH); + float2 uv = ddgiAtlasUV(probeIndex, dirWS, ps, atlasW)", +R"(, atlasH); return sampleDDGITextureBilinear(ddgiTexture, uv); } -static inli)", -R"(ne float3 sampleDDGIIrradiance(texture2d ddgiTexture, +static inline float3 sampleDDGIIrradiance(texture2d ddgiTexture, texture2d ddgiDistance, constant ProbeSpace &ps, float3 posWS, float3 normalWS) { @@ -4626,9 +4629,9 @@ R"(ne float3 sampleDDGIIrradiance(texture2d ddgiTexture, fragment main0_out main0( main0_in in [[stage_in]], constant UBO &_526 [[buffer(0)]], constant Environment &environment [[buffer(1)]], - constant PushConstants &_1355 [[buffer(2)]], - device ShadowParams &_1372 [[b)", -R"(uffer(3)]], + )", +R"( constant PushConstants &_1355 [[buffer(2)]], + device ShadowParams &_1372 [[buffer(3)]], device DirectionalLights &_1422 [[buffer(4)]], device PointLights &_1465 [[buffer(5)]], device SpotLights &_1510 [[buffer(6)]], @@ -4747,13 +4750,19 @@ R"(uffer(3)]], int shadowCount = _1355.shadowParamCount; for (int i = 0; i < shadowCount; i++) { if (_1372.shadowParams[i].lightType == 3) { + int lightIndex = _1372.shadowParams[i].lightIndex; + if (lightIndex < 0 || lightIndex >= _1355.pointLightCount || + distance(float3(_1465.pointLights[lightIndex].position), + FragPos) >= _1465.pointLights[lightIndex].radius) { + continue; + } ShadowParameters _1386; _1386.lightView = _1372.shadowParams[i].lightView; _1386.lightProjection = _1372.shadowParams[i].lightProjection; _1386.bias0 = _1372.shadowParams[i].bias0; _1386.textureIndex = _1372.shadowParams[i].textureIndex; _1386.farPlane = _1372.shadowParams[i].farPlane; - _1386._pad1 = _1372.shadowParams[i]._pad1; + _1386.lightIndex = _1372.shadowParams[i].lightIndex; _1386.lightPos = float3(_1372.shadowParams[i].lightPos); _1386.lightType = _1372.shadowParams[i].lightType; ShadowParameters param = _1386; @@ -4768,13 +4777,19 @@ R"(uffer(3)]], cubeMap3Smplr, cubeMap4, cubeMap4Smplr, cubeMap5, cubeMap5Smplr)); } else if (_1372.shadowParams[i].lightType == 1) { + int lightIndex = _1372.shadowParams[i].lightIndex; + if (lightIndex < 0 || lightIndex >= _1355.spotlightCount || + distance(float3(_1510.spotlights[lightIndex].position), + FragPos) >= _1510.spotlights[lightIndex].range) { + continue; + } ShadowParameters _1397; _1397.lightView = _1372.shadowParams[i].lightView; _1397.lightProjection = _1372.shadowParams[i].lightProjection; _1397.bias0 = _1372.shadowParams[i].bias0; _1397.textureIndex = _1372.shadowParams[i].textureIndex; _1397.farPlane = _1372.shadowParams[i].farPlane; - _1397._pad1 = _1372.shadowParams[i]._pad1; + _1397.lightIndex = _1372.shadowParams[i].lightIndex; _1397.lightPos = float3(_1372.shadowParams[i].lightPos); _1397.lightType = _1372.shadowParams[i].lightType; ShadowParameters param_2 = _1397; @@ -4782,20 +4797,26 @@ R"(uffer(3)]], float3 param_4 = shadowNormal; spotShadow = fast::max( spotShadow, - calculateShadow(param_2, param_3, param_4, texture1, + calculateShadow(param_2, param_3, pa)", +R"(ram_4, texture1, texture1Smplr, texture2, texture2Smplr, texture3, texture3Smplr, texture4, texture4Smplr, texture5, texture5Smplr, _526)); } else if (_1372.shadowParams[i].lightType == 2) { + int lightIndex = _1372.shadowParams[i].lightIndex; + if (lightIndex < 0 || lightIndex >= _1355.areaLightCount || + distance(float3(_1552.areaLights[lightIndex].position), + FragPos) >= _1552.areaLights[lightIndex].range) { + continue; + } ShadowParameters _1397; _1397.lightView = _1372.shadowParams[i].lightView; _1397.lightProjection = _1372.shadowParams[i].lightProjection; _1397.bias0 = _1372.shadowParams[i].bias0; _1397.textureIndex = _1372.shadowParams[i].textureIndex; _1397.farPlane = _1372.shadowParams[i].farPlane; - _1397._pad1 = _1372.shadowParams[i]._pad1; - _1397.lightPos = float3(_13)", -R"(72.shadowParams[i].lightPos); + _1397.lightIndex = _1372.shadowParams[i].lightIndex; + _1397.lightPos = float3(_1372.shadowParams[i].lightPos); _1397.lightType = _1372.shadowParams[i].lightType; ShadowParameters param_2 = _1397; float3 param_3 = FragPos; @@ -4812,7 +4833,7 @@ R"(72.shadowParams[i].lightPos); _1397.bias0 = _1372.shadowParams[i].bias0; _1397.textureIndex = _1372.shadowParams[i].textureIndex; _1397.farPlane = _1372.shadowParams[i].farPlane; - _1397._pad1 = _1372.shadowParams[i]._pad1; + _1397.lightIndex = _1372.shadowParams[i].lightIndex; _1397.lightPos = float3(_1372.shadowParams[i].lightPos); _1397.lightType = _1372.shadowParams[i].lightType; ShadowParameters param_2 = _1397; @@ -4938,7 +4959,8 @@ R"(72.shadowParams[i].lightPos); float attenuation = 1.0 / ((1.0 + (dist / range)) + ((dist * dist) / (range * range))); float fade = 1.0 - smoothstep(range * 0.89999997615814208984375, - range, dist); + )", +R"( range, dist); float3 radiance = (((float3(_1552.areaLights[i_4].diffuse) * fast::max(_1552.areaLights[i_4].intensity, 0.0)) * @@ -4967,8 +4989,7 @@ R"(72.shadowParams[i].lightPos); float3 param_40 = albedo; float param_41 = metallic; float param_42 = roughness; - float3 _1714 = getRimLight(param_36, )", -R"(param_37, param_38, param_39, param_40, + float3 _1714 = getRimLight(param_36, param_37, param_38, param_39, param_40, param_41, param_42, _526, environment); float3 rimResult = _1714; float3 lighting = @@ -6604,6 +6625,7 @@ using namespace raytracing; struct CameraUniforms { float4x4 invViewProj; + float4x4 prevViewProj; float3 camPos; float _pad0; }; @@ -6849,8 +6871,8 @@ constexpr sampler materialTexSampler(coord::normalized, address::repeat, texture2d materialTexture32, \ texture2d materialTexture33, \ texture2d materialTexture34, \ - texture2d materialTexture35, )", -R"( \ + texture2d materialTexture35, )", +R"( \ texture2d materialTexture36, \ texture2d materialTexture37, \ texture2d materialTexture38, \ @@ -6981,9 +7003,9 @@ float4 sampleMaterialTexture(int textureIndex, float2 uv, case 22: return materialTexture22.sample(materialTexSampler, uv); case 23: - return materialTexture23.sample(materialTexSampler, uv); - case 24:)", -R"( + return materialTexture23.sample(materialTe)", +R"(xSampler, uv); + case 24: return materialTexture24.sample(materialTexSampler, uv); case 25: return materialTexture25.sample(materialTexSampler, uv); @@ -7037,6 +7059,26 @@ R"( return float4(0.0); } +struct MaterialTextureArguments { + array, 256> textures [[id(0)]]; +}; + +float4 sampleMaterialTexture( + int textureIndex, float2 uv, + constant MaterialTextureArguments &materialTextureArguments) { + return materialTextureArguments.textures[textureIndex].sample( + materialTexSampler, uv); +} + +#undef PT_MATERIAL_TEXTURE_PARAMS +#undef PT_MATERIAL_TEXTURE_ARGS +#undef PT_MATERIAL_TEXTURE_BINDINGS +#define PT_MATERIAL_TEXTURE_PARAMS \ + constant MaterialTextureArguments &materialTextureArguments +#define PT_MATERIAL_TEXTURE_ARGS materialTextureArguments +#define PT_MATERIAL_TEXTURE_BINDINGS \ + constant MaterialTextureArguments &materialTextureArguments [[buffer(12)]] + void resolveMaterialParameters(Material mat, float2 uv, uint textureCount, PT_MATERIAL_TEXTURE_PARAMS, thread float3 &albedo, thread float &metallic, @@ -7160,7 +7202,8 @@ bool isOccludedDirectionalLight(DirectionalLightData light, float3 P, float3 N, float2 u = float2(rand(rng), rand(rng)); float r = sunRadius * sqrt(u.x); float phi = 2.0 * M_PI_F * u.y; - float3 jittered = + )", +R"( float3 jittered = baseL + basis[0] * (r * cos(phi)) + basis[1] * (r * sin(phi)); float3 L = normalize(jittered); return isOccluded(isect, sceneAS, P, N, L, 1e30); @@ -7179,8 +7222,7 @@ bool isOccludedPointLight(PointLight light, float3 P, float3 N, float3(r * cos(phi), r * sin(phi), z) * lightRadius; float3 sampledLightPos = light.position + sphereOffset; - float3 toL)", -R"(ight = sampledLightPos - P; + float3 toLight = sampledLightPos - P; float dist2 = max(dot(toLight, toLight), 0.001); float dist = sqrt(dist2); float3 L = toLight / dist; @@ -7346,7 +7388,8 @@ float3 evalDirectLightingPBR(intersector isect, max(dirLight.intensity, 0.0)); float3 s = evalSubsurface(albedo, N, V, L, dirLight.color, max(dirLight.intensity, 0.0), roughness, - sssStrength, sssThickness); + )", +R"( sssStrength, sssThickness); float3 t = evalTransmission(albedo, N, V, L, dirLight.color, max(dirLight.intensity, 0.0), roughness, ior) * @@ -7364,8 +7407,7 @@ float3 evalDirectLightingPBR(intersector isect, float dist = max(length(toLight), 1e-4); float3 L = toLight / dist; float lightRange = max(pointLights[i].range, 1e-4); - float minDist = max)", -R"((lightRange * 0.08, 0.15); + float minDist = max(lightRange * 0.08, 0.15); float distSq = dist * dist + minDist * minDist; float rangeFade = 1.0 - smoothstep(lightRange * 0.75, lightRange, dist); float atten = rangeFade / max(distSq, 1e-4); @@ -7464,7 +7506,14 @@ float3 sampleRadiance(uint2 gid, uint sampleIndex, uint w, constant PointLight *pointLights, constant SpotLight *spotLights, constant AreaLight *areaLights, - PT_MATERIAL_TEXTURE_PARAMS, texturecube skybox) { + PT_MATERIAL_TEXTURE_PARAMS, texturecube skybox, + thread float3 &primaryAlbedo, + thread float3 &primaryNormal, + thread float3 &primaryPosition, + thread float &primaryDepth, + thread float &primaryRoughness, + thread float &primaryHitDistance, + thread uint &primaryObjectId) { uint rng = seedBase(gid, w, sceneData.frameIndex, sampleIndex); ray surfaceRay = primaryRay; @@ -7509,7 +7558,8 @@ float3 sampleRadiance(uint2 gid, uint sampleIndex, uint w, float3(vertices[i1].tangent) * b1 + float3(vertices[i2].tangent) * b2, float3(1.0, 0.0, 0.0)); - localB = normalizeOr(float3(vertices[i0].bitangent) * b0 + + localB)", +R"( = normalizeOr(float3(vertices[i0].bitangent) * b0 + float3(vertices[i1].bitangent) * b1 + float3(vertices[i2].bitangent) * b2, float3(0.0, 0.0, 1.0)); @@ -7539,8 +7589,7 @@ float3 sampleRadiance(uint2 gid, uint sampleIndex, uint w, return skyColor(surfaceRay.direction, 0.0, skybox, sceneData); } - float3 N = resolveShadingNormal(mat, texUV, localN, localT, localB, inst)", -R"(, + float3 N = resolveShadingNormal(mat, texUV, localN, localT, localB, inst, sceneData.materialTextureCount, PT_MATERIAL_TEXTURE_ARGS); float3 P = surfaceRay.origin + surfaceRay.direction * hit.distance; @@ -7559,6 +7608,13 @@ R"(, resolveMaterialParameters(mat, texUV, sceneData.materialTextureCount, PT_MATERIAL_TEXTURE_ARGS, albedo, metallic, roughness, ao, emissive, ior, transmittance); + primaryAlbedo = albedo; + primaryNormal = N; + primaryPosition = P; + primaryDepth = length(P - primaryRay.origin); + primaryRoughness = roughness; + primaryHitDistance = hit.distance; + primaryObjectId = hit.instance_id; float reflectivity = clamp(mat.reflectivity, 0.0, 1.0); float sssStrength = clamp(1.0 - mat.albedo.w, 0.0, 1.0) * (1.0 - metallic); float sssThickness = mix(0.25, 1.75, ao); @@ -7697,7 +7753,8 @@ R"(, float3 tLocalB = normalizeOr(float3(vertices[tj0].bitangent) * tb0 + float3(vertices[tj1].bitangent) * tb1 + float3(vertices[tj2].bitangent) * tb2, - float3(0.0, 0.0, 1.0)); + )", +R"( float3(0.0, 0.0, 1.0)); float3 tN = resolveShadingNormal(tmat, tUV, tLocalN, tLocalT, tLocalB, tinst, @@ -7729,8 +7786,7 @@ R"(, bool enteringShell = dot(tN, tV) > 0.0; float etaShell = enteringShell ? (1.0 / tIor) : tIor; float3 faceNShell = enteringShell ? tN : -tN; - float)", -R"(3 refractShell = refract(-tV, faceNShell, etaShell); + float3 refractShell = refract(-tV, faceNShell, etaShell); if (length(refractShell) < 1e-5) { refractShell = reflect(-tV, faceNShell); } @@ -7818,6 +7874,11 @@ R"(3 refractShell = refract(-tV, faceNShell, etaShell); isect, sceneAS, bP, bN, bV, bAlbedo, bMetallic, bRoughness, bIor, bTransmittance, bSssStrength, bSssThickness, rng, dirLight, sceneData, pointLights, spotLights, areaLights); + if (choseTransmission) { + float causticFocus = mix(1.0, 4.0, + transmittance * (1.0 - roughness)); + bounceDirect *= causticFocus; + } float3 bAmbient = bAlbedo * max(sceneData.ambientIntensity, 0.0) * (1.0 - bMetallic) * bAo; @@ -7839,8 +7900,13 @@ R"(3 refractShell = refract(-tV, faceNShell, etaShell); } kernel void main0(texture2d outTex [[texture(0)]], - texture2d prevTex [[texture(1)]], + texture2d historyTex [[texture(1)]], texture2d brightTex [[texture(2)]], + texture2d albedoRoughnessTex [[texture(3)]], + texture2d normalDepthTex [[texture(4)]], + texture2d motionObjectTex [[texture(5)]], + texture2d momentsHitTex [[texture(6)]], + texture2d historyGuideTex [[texture(7)]], instance_acceleration_structure sceneAS [[buffer(0)]], constant CameraUniforms &cam [[buffer(1)]], constant Material *materials [[buffer(2)]], @@ -7855,7 +7921,8 @@ kernel void main0(texture2d outTex [[texture(0)]], constant AreaLight *areaLights [[buffer(11)]], PT_MATERIAL_TEXTURE_BINDINGS, texturecube skybox [[texture(60)]], - uint2 gid [[thread_position_in_grid]]) { + uint2 gid [[thread_position_i)", +R"(n_grid]]) { uint w = outTex.get_width(); uint h = outTex.get_height(); if (gid.x >= w || gid.y >= h) @@ -7877,6 +7944,13 @@ kernel void main0(texture2d outTex [[texture(0)]], isect.set_triangle_cull_mode(triangle_cull_mode::none); float3 color = float3(0.0); + float3 primaryAlbedo = float3(0.0); + float3 primaryNormal = float3(0.0); + float3 primaryPosition = float3(0.0); + float primaryDepth = 0.0; + float primaryRoughness = 1.0; + float primaryHitDistance = 0.0; + uint primaryObjectId = 0xFFFFFFFFu; uint spp = max(sceneData.raysPerPixel, 1u); for (uint s = 0; s < spp; ++s) { @@ -7889,7 +7963,9 @@ kernel void main0(texture2d outTex [[texture(0)]], float3 sample = sampleRadiance( gid, s, w, isect, sceneAS, primaryRay, materials, meshData, vertices, indices, instanceData, dirLight, sceneData, pointLights, - spotLights, areaLights, PT_MATERIAL_TEXTURE_ARGS, skybox); + spotLights, areaLights, PT_MATERIAL_TEXTURE_ARGS, skybox, + primaryAlbedo, primaryNormal, primaryPosition, primaryDepth, + primaryRoughness, primaryHitDistance, primaryObjectId); color += clampLuminance(sample, 24.0); } @@ -7897,9 +7973,22 @@ kernel void main0(texture2d outTex [[texture(0)]], int frameIndex = int(sceneData.frameIndex); - float4 prevColor = prevTex.read(gid); + float4 prevColor = historyTex.read(gid); + float4 previousGuide = historyGuideTex.read(gid); + float objectIdValue = primaryObjectId == 0xFFFFFFFFu + ? -1.0 + : float(primaryObjectId); + float4 currentGuide = + float4(primaryNormal.xy, primaryDepth, objectIdValue); + bool historyValid = frameIndex > 0 && + abs(previousGuide.z - primaryDepth) < + max(0.05, primaryDepth * 0.02) && + distance(previousGuide.xy, primaryNormal.xy) < 0.12 && + abs(previousGuide.w - objectIdValue) < 0.5; if (frameIndex == 0) prevColor = float4(0, 0, 0, 1); + if (!historyValid) + prevColor = float4(color, 1.0); if (frameIndex > 2) { float prevL = luminance(prevColor.xyz); @@ -7910,7 +7999,12 @@ kernel void main0(texture2d outTex [[texture(0)]], } } - float3 accum = (prevColor.xyz * frameIndex + color) / (frameIndex + 1); + float historyLength = historyValid ? min(float(frameIndex), 31.0) : 0.0; + float3 lower = min(prevColor.xyz, color) - float3(0.35); + float3 upper = max(prevColor.xyz, color) + float3(0.35); + float3 clippedHistory = clamp(prevColor.xyz, lower, upper); + float3 accum = mix(color, clippedHistory, + historyLength / (historyLength + 1.0)); accum = clampLuminance(accum, 24.0); constexpr float bloomThreshold = 1.0; @@ -7925,13 +8019,24 @@ kernel void main0(texture2d outTex [[texture(0)]], soft = soft * soft / max(bloomKnee * 4.0, 0.00001); - float c)", -R"(ontribution = + float contribution = max(brightness - bloomThreshold, soft) / max(brightness, 0.00001); float3 brightColor = accum * contribution; - + float4 previousClip = cam.prevViewProj * float4(primaryPosition, 1.0); + float2 previousUv = previousClip.xy / max(abs(previousClip.w), 0.0001); + previousUv = previousUv * 0.5 + 0.5; + float2 motion = uv - previousUv; + float moment = luminance(color); + + historyTex.write(float4(accum, 1.0), gid); + historyGuideTex.write(currentGuide, gid); + albedoRoughnessTex.write(float4(primaryAlbedo, primaryRoughness), gid); + normalDepthTex.write(float4(primaryNormal, primaryDepth), gid); + motionObjectTex.write(float4(motion, objectIdValue, 1.0), gid); + momentsHitTex.write(float4(moment, moment * moment, + primaryRoughness, primaryHitDistance), gid); outTex.write(float4(accum, 1.0), gid); brightTex.write(float4(brightColor, 1.0), gid); } @@ -7939,6 +8044,59 @@ R"(ontribution = }; static const AtlasPackedShaderSource PATH = {PATH_PARTS, 8}; +static const char* const PATH_DENOISE_PARTS[] = { +R"(#include +using namespace metal; + +struct DenoiseParameters { + int stepWidth; +}; + +kernel void main0(texture2d inputTexture [[texture(0)]], + texture2d outputTexture [[texture(1)]], + texture2d brightTexture [[texture(2)]], + texture2d guideTexture [[texture(3)]], + constant DenoiseParameters ¶meters [[buffer(0)]], + uint2 gid [[thread_position_in_grid]]) { + uint width = outputTexture.get_width(); + uint height = outputTexture.get_height(); + if (gid.x >= width || gid.y >= height) return; + + constexpr int2 offsets[9] = { + int2(0, 0), int2(1, 0), int2(-1, 0), int2(0, 1), int2(0, -1), + int2(1, 1), int2(-1, 1), int2(1, -1), int2(-1, -1)}; + constexpr float weights[9] = {0.28, 0.12, 0.12, 0.12, 0.12, + 0.06, 0.06, 0.06, 0.06}; + float3 center = inputTexture.read(gid).xyz; + float4 centerGuide = guideTexture.read(gid); + float centerLuminance = dot(center, float3(0.2126, 0.7152, 0.0722)); + float3 filtered = float3(0.0); + float totalWeight = 0.0; + for (int i = 0; i < 9; ++i) { + int2 samplePosition = clamp(int2(gid) + offsets[i] * parameters.stepWidth, + int2(0), int2(width - 1, height - 1)); + float3 sampleColor = inputTexture.read(uint2(samplePosition)).xyz; + float4 sampleGuide = guideTexture.read(uint2(samplePosition)); + float sampleLuminance = dot(sampleColor, float3(0.2126, 0.7152, 0.0722)); + float edgeWeight = exp(-abs(sampleLuminance - centerLuminance) * 6.0); + float normalWeight = + pow(max(dot(centerGuide.xyz, sampleGuide.xyz), 0.0), 24.0); + float depthWeight = exp(-abs(sampleGuide.w - centerGuide.w) / + max(0.05, centerGuide.w * 0.02)); + float weight = weights[i] * edgeWeight * normalWeight * depthWeight; + filtered += sampleColor * weight; + totalWeight += weight; + } + float3 result = filtered / max(totalWeight, 0.0001); + float brightness = dot(result, float3(0.2126, 0.7152, 0.0722)); + float contribution = smoothstep(0.5, 1.5, brightness); + outputTexture.write(float4(result, 1.0), gid); + brightTexture.write(float4(result * contribution, 1.0), gid); +} +)", +}; +static const AtlasPackedShaderSource PATH_DENOISE = {PATH_DENOISE_PARTS, 1}; + static const char* const POINT_DEPTH_FRAG_PARTS[] = { R"(#include #include @@ -8716,6 +8874,8 @@ struct SSRParameters int steps; float thickness; float maxRoughness; + float historyWeight; + int debugMode; }; struct main0_out @@ -8753,17 +8913,17 @@ float3 sampleSkyReflection(thread const float3& normal, thread const float3& vie } static inline __attribute__((always_inline)) -float4 SSR(thread const float3& worldPos, thread const float3& normal, thread const float3& viewDir, thread const float& roughness, thread const float& metallic, thread const float3& albedo, constant Uniforms& _42, constant SSRParameters& _96, thread float4& gl_FragCoord, texture2d gPosition, sampler gPositionSmplr, texture2d sceneColor, sampler sceneColorSmplr, texture2d gNormal, sampler gNormalSmplr, texture2d gDepth, sampler gDepthSmplr, texturecube skybox, sampler skyboxSmplr) +float4 SSR(thread const float3& worldPos, thread const float3& normal, thread const float3& viewDir, thread const float& roughness, thread const float& metallic, thread const float& reflectivity, thread const float3& albedo, constant Uniforms& _42, constant SSRParameters& _96, thread float4& gl_FragCoord, texture2d gPosition, sampler gPositionSmplr, texture2d sceneColor, sampler sceneColorSmplr, texture2d gNormal, sampler gNormalSmplr, texture2d gDepth, sampler gDepthSmplr, texturecube skybox, sampler skyboxSmplr) { float3 viewPos = (_42.view * float4(worldPos, 1.0)).xyz; float3 viewNormal = fast::normalize((_42.view * float4(normal, 0.0)).xyz); - float mirrorFactor = fast::clamp(metallic * (1.0 - roughness), 0.0, 1.0); + float mirrorFactor = fast::clamp(fast::max(metallic, reflectivity) * (1.0 - roughness), 0.0, 1.0); float3 skyReflection = sampleSkyReflection(normal, viewDir, albedo, metallic, skybox, skyboxSmplr); float3 viewDirection = fast::normalize(viewPos); float3 viewReflect = fast::normalize(reflect(viewDirection, viewNormal)); if (viewReflect.z > 0.0) { - return float4(skyReflection, mirrorFactor); + return float4(0.0); } float3 rayOrigin = viewPos + (viewNormal * 0.00999999977648258209228515625); float3 rayDir = viewReflect; @@ -8827,9 +8987,13 @@ float4 SSR(thread const float3& worldPos, thread const float3& normal, thread co { break; } - float rawDepth = gDepth.sample(gDepthSmplr, screenUV).x; + float hierarchyLevel = fast::clamp(floor(log2(1.0 + float(i) * 0.25)), + 0.0, 5.0); + float rawDepth = gDepth.sample(gDepthSmplr, screenUV, + level(hierarchyLevel)).x; if (rawDepth >= 0.99989998340606689453125) { + currentPos += rayDir * stepSize * (exp2(hierarchyLevel) - 1.0); lastPos = currentPos; continue; } @@ -8918,14 +9082,14 @@ float4 SSR(thread const float3& worldPos, thread const float3& normal, thread co binarySearchStart = midPoint; continue; } - float3 midSampleWorldPos = gPosition.sample(gPositionSmplr, midUV).xyz; + float3 midSampleWorldPos = gPositio)", +R"(n.sample(gPositionSmplr, midUV).xyz; float3 midSampleViewPos = (_42.view * float4(midSampleWorldPos, 1.0)).xyz; float midSampleDepth = -midSampleViewPos.z; float midCurrentDepth = -midPoint.z; if (midCurrentDepth < midSampleDepth) { - binarySearchStart)", -R"( = midPoint; + binarySearchStart = midPoint; } else { @@ -8948,23 +9112,9 @@ R"( = midPoint; } if (!hit) { - float3 fallbackReflection = skyReflection; - if (hasFallbackUV) - { - float mipLevel = roughness * 5.0; - float3 screenFallback = sceneColor.sample(sceneColorSmplr, fallbackUV, level(mipLevel)).xyz; - float fallbackDepth = gDepth.sample(gDepthSmplr, fallbackUV).x; - float screenValidity = 1.0 - smoothstep(0.99800002574920654296875, 1.0, fallbackDepth); - float fallbackLuma = dot(screenFallback, float3(0.2125999927520751953125, 0.715200006961822509765625, 0.072200000286102294921875)); - screenValidity *= smoothstep(0.004999999888241291046142578125, 0.02999999932944774627685546875, fallbackLuma); - float tintStrength = metallic * 0.3499999940395355224609375; - float3 metalTint = mix(float3(1.0), albedo, float3(tintStrength)); - fallbackReflection = mix(skyReflection, screenFallback * metalTint, float3(screenValidity)); - } - return float4(fallbackReflection, mirrorFactor); - } - float mipLevel = roughness * 5.0; - float3 hitColor = sceneColor.sample(sceneColorSmplr, hitUV, level(mipLevel)).xyz; + return float4(0.0); + } + float3 hitColor = sceneColor.sample(sceneColorSmplr, hitUV).xyz; float tintStrength = metallic * 0.3499999940395355224609375; float3 metalTint = mix(float3(1.0), albedo, float3(tintStrength)); hitColor *= metalTint; @@ -8999,7 +9149,7 @@ R"( = midPoint; return float4(reflectionColor, finalFade); } -fragment main0_out main0(main0_in in [[stage_in]], constant Uniforms& _42 [[buffer(0)]], constant SSRParameters& _96 [[buffer(1)]], texture2d gPosition [[texture(0)]], texture2d sceneColor [[texture(1)]], texture2d gNormal [[texture(2)]], texture2d gAlbedoSpec [[texture(3)]], texture2d gMaterial [[texture(4)]], texture2d gDepth [[texture(5)]], texturecube skybox [[texture(6)]], sampler gPositionSmplr [[sampler(0)]], sampler sceneColorSmplr [[sampler(1)]], sampler gNormalSmplr [[sampler(2)]], sampler gAlbedoSpecSmplr [[sampler(3)]], sampler gMaterialSmplr [[sampler(4)]], sampler gDepthSmplr [[sampler(5)]], sampler skyboxSmplr [[sampler(6)]], float4 gl_FragCoord [[position]]) +fragment main0_out main0(main0_in in [[stage_in]], constant Uniforms& _42 [[buffer(0)]], constant SSRParameters& _96 [[buffer(1)]], texture2d gPosition [[texture(0)]], texture2d sceneColor [[texture(1)]], texture2d gNormal [[texture(2)]], texture2d gAlbedoSpec [[texture(3)]], texture2d gMaterial [[texture(4)]], texture2d gDepth [[texture(5)]], texturecube skybox [[texture(6)]], texture2d historyTexture [[texture(7)]], sampler gPositionSmplr [[sampler(0)]], sampler sceneColorSmplr [[sampler(1)]], sampler gNormalSmplr [[sampler(2)]], sampler gAlbedoSpecSmplr [[sampler(3)]], sampler gMaterialSmplr [[sampler(4)]], sampler gDepthSmplr [[sampler(5)]], sampler skyboxSmplr [[sampler(6)]], sampler historyTextureSmplr [[sampler(7)]], float4 gl_FragCoord [[position]]) { main0_out out = {}; float3 worldPos = gPosition.sample(gPositionSmplr, in.TexCoord).xyz; @@ -9008,12 +9158,13 @@ fragment main0_out main0(main0_in in [[stage_in]], constant Uniforms& _42 [[buff float4 material = gMaterial.sample(gMaterialSmplr, in.TexCoord); float metallic = material.x; float roughness = material.y; + float reflectivity = material.w; if (length(normal) < 0.001000000047497451305389404296875) { out.FragColor = float4(0.0); return out; } - if (roughness > _96.maxRoughness) + if (roughness > _96.maxRoughness || fast::max(metallic, reflectivity) < 0.02) { out.FragColor = float4(0.0); return out; @@ -9024,8 +9175,20 @@ fragment main0_out main0(main0_in in [[stage_in]], constant Uniforms& _42 [[buff float3 param_2 = viewDir; float param_3 = roughness; float param_4 = metallic; - float3 param_5 = albedo; - float4 reflection = SSR(param, param_1, param_2, param_3, param_4, param_5, _42, _96, gl_FragCoord, gPosition, gPositionSmplr, sceneColor, sceneColorSmplr, gNormal, gNormalSmplr, gDepth, gDepthSmplr, skybox, skyboxSmplr); + float param_5 = reflectivity; + float3 param_6 = albedo; + float4 reflection = SSR(param, param_1, param_2, param_3, param_4, param_5, param_6, _42, _96, gl_FragCoord, gPosition, gPositionSmplr, sceneColor, sceneColorSmplr, gNormal, gNormalSmplr, gDepth, gDepthSmplr, skybox, skyboxSmplr); + if (_96.debugMode != 0) + { + out.FragColor = float4(1.0 - reflection.w, reflection.w, 0.0, 1.0); + return out; + } + if (reflection.w > 0.0 && _96.historyWeight > 0.0) + { + float4 history = historyTexture.sample(historyTextureSmplr, in.TexCoord); + float validHistory = step(0.001, history.w); + reflection = mix(reflection, history, _96.historyWeight * validHistory * reflection.w); + } out.FragColor = reflection; return out; } diff --git a/include/atlas/core/shader.h b/include/atlas/core/shader.h index 8bcf9d6a..e514d4a9 100644 --- a/include/atlas/core/shader.h +++ b/include/atlas/core/shader.h @@ -117,6 +117,7 @@ enum class AtlasComputeShader { DDGI_WRITE, /** @brief Compute shader used for path tracing output generation. */ PathTracer, + PathDenoiser, }; /** diff --git a/include/atlas/light.h b/include/atlas/light.h index b0355ad7..f9cf03d5 100644 --- a/include/atlas/light.h +++ b/include/atlas/light.h @@ -111,7 +111,7 @@ struct alignas(16) GPUShadowParams { float bias; int textureIndex; float farPlane; - float _pad1; + int lightIndex; glm::vec3 lightPos; int lightType; }; @@ -404,7 +404,7 @@ class DirectionalLight { * @param window The window in which to cast shadows. * @param resolution The resolution to use for the shadow map. */ - void castShadows(Window &window, int resolution = 4096); + void castShadows(Window &window, int resolution = 2048); private: bool doesCastShadows = false; diff --git a/include/atlas/runtime/atlasScripts.h b/include/atlas/runtime/atlasScripts.h index 11304118..d90e95d0 100644 --- a/include/atlas/runtime/atlasScripts.h +++ b/include/atlas/runtime/atlasScripts.h @@ -26,8 +26,8 @@ static const char* const ATLAS_AUDIO_PARTS[] = { static const AtlasPackedScriptSource ATLAS_AUDIO = {ATLAS_AUDIO_PARTS, 1}; static const char* const ATLAS_GRAPHICS_PARTS[] = { - "import { Resource, ResourceType } from \"atlas\";\nimport { Color, Position3d, Size2d } from \"atlas/units\";\n\nexport const TextureType = Object.freeze({\n Color: 0,\n Specular: 1,\n Cubemap: 2,\n Depth: 3,\n DepthCube: 4,\n Normal: 5,\n Parallax: 6,\n SSAONoise: 7,\n SSAO: 8,\n Metallic: 9,\n Roughness: 10,\n AO: 11,\n Opacity: 12,\n HDR: 13,\n PBRPack: 14,\n});\n\nexport const RenderTargetType = Object.freeze({\n Scene: 0,\n Multisampled: 1,\n Shadow: 2,\n CubeShadow: 3,\n GBuffer: 4,\n SSAO: 5,\n SSAOBlur: 6,\n});\n\nexport const RenderPassType = Object.freeze({\n Deferred: 0,\n Forward: 1,\n PathTracing: 2,\n});\n\nexport const Effects = Object.freeze({\n Inversion: { type: \"Inversion\" },\n Grayscale: { type: \"Grayscale\" },\n Sharpen: { type: \"Sharpen\" },\n Blur: { type: \"Blur\", magnitude: 16 },\n EdgeDetection: { type: \"EdgeDetection\" },\n ColorCorrection: {\n type: \"ColorCorrection\",\n exposure: 0,\n contrast: 1,\n saturation: 1,\n gamma: 1,\n temperature: 0,\n tint: 0,\n },\n MotionBlur: { type: \"MotionBlur\", size: 8, separation: 1 },\n ChromaticAberration: {\n type: \"ChromaticAberration\",\n red: 0.01,\n green: 0.006,\n blue: -0.006,\n direction: { x: 0, y: 0 },\n },\n Posterization: { type: \"Posterization\", levels: 5 },\n Pixelation: { type: \"Pixelation\", pixelSize: 8 },\n Dialation: { type: \"Dilation\", size: 8, separation: 1 },\n Dilation: { type: \"Dilation\", size: 8, separation: 1 },\n FilmGrain: { type: \"FilmGrain\", amount: 0.05 },\n});\n\nexport class Texture {\n constructor() {\n this.type = TextureType.Color;\n this.resource = new Resource(ResourceType.File, \"\", \"\");\n this.width = 0;\n this.height = 0;\n this.channels = 0;\n this.id = 0;\n this.borderColor = Color.black();\n }\n\n static fromResource(resource, type = TextureType.Color) {\n return globalThis.__atlasCreateTextureFromResource(resource, type);\n }\n\n static createEmpty(\n width,\n height,\n type = TextureType.Color,\n borderColor = new Color(0, 0, 0, 0),\n ) {\n return globalThis.__atlasCreateEmptyTexture(\n width,\n height,\n type,\n borderColor,\n );\n }\n\n static createColor(color, type = TextureType.Color, width = 1, height = 1) {\n return globalThis.__atlasCreateColorTexture(color, type, width, height);\n }\n\n createCheckerboard(width, height, checkSize, color1, color2) {\n return globalThis.__atlasCreateCheckerboardTexture(\n this,\n width,\n height,\n checkSize,\n color1,\n color2,\n );\n }\n\n createDoubleCheckerboard(\n width,\n height,\n checkSizeBig,\n checkSizeSmall,\n color1,\n color2,\n color3,\n ) {\n return globalThis.__atlasCreateDoubleCheckerboardTexture(\n this,\n width,\n height,\n checkSizeBig,\n checkSizeSmall,\n color1,\n color2,\n color3,\n );\n }\n\n displayToWindow() {\n return globalThis.__atlasDisplayTexture(this);\n }\n}\n\nexport class Cubemap {\n constructor(resources) {\n this.resources = resources;\n this.id = 0;\n return globalThis.__atlasCreateCubemap(resources);\n }\n\n getAverageColor() {\n return globalThis.__atlasGetCubemapAverageColor(this);\n }\n\n static fromResourceGroup(resourceGroup) {\n if (resourceGroup == null) {\n return null;\n }\n return globalThis.__atlasCreateCubemapFromGroup(resourceGroup.resources);\n }\n\n updateWithColors(colors) {\n return globalThis.__atlasUpdateCubemapWithColors(this, colors);\n }\n}\n\nexport class RenderTarget {\n constructor(type = RenderTargetType.Scene, resolution = 1024) {\n this.type = type;\n this.resolution = resolution;\n this.outTextures = [];\n this.depthTexture = null;\n return globalThis.__atlasCreateRenderTarget(type, resolution);\n }\n\n addEffect(effect) {\n return globalThis.__atlasAddRenderTargetEffect(this, effect);\n }\n\n addToPassQueue(type) {\n return globalThis.__atlasAddRenderTargetToPassQueue(this, type);\n }\n\n addToPass(type) {\n return this.addToPassQueue(type);\n }\n\n display() {\n return globalThis.__atlasDisplayRenderTarget(this);\n }\n}\n\nexport class Skybox {\n constructor(cubemap) {\n this.cubemap = cubemap;\n return globalThis.__atlasCreateSkybox(cubemap);\n }\n}\n\nexport class AmbientLight {\n constructor(color = Color.white(), intensity = 0.125) {\n this.color = color;\n this.intensity = intensity;\n }\n}\n\nexport class Light {\n constructor(\n position = Position3d.zero(),\n color = Color.white(),\n distance = 50,\n shineColor = Color.white(),\n intensity = 1,\n ) {\n this.position = position;\n this.color = color;\n this.shineColor = shineColor;\n this.intensity = intensity;\n this.distance = distance;\n return globalThis.__atlasCreatePointLight(this);\n }\n\n setColor(color) {\n this.color = color;\n return globalThis.__atlasUpdatePointLight(this);\n }\n\n createDebugObject() {\n return globalThis.__atlasCreatePointLightDebugObject(this);\n }\n\n castShadows(resolution = 2048) {\n return globalThis.__atlasCastPointLightShadows(this, resolution);\n }\n}\n\nexport class DirectionalLight {\n constructor(\n direction = Position3d.down(),\n color = Color.white(),\n shineColor = Color.white(),\n intensity = 1,\n ) {\n this.direction = direction;\n this.color = color;\n this.shineColor = shineColor;\n this.intensity = intensity;\n return globalThis.__atlasCreateDirectionalLight(this);\n }\n\n setColor(color) {\n this.color = color;\n return globalThis.__atlasUpdateDirectionalLight(this);\n }\n\n castShadows(resolution = 4096) {\n return globalThis.__atlasCastDirectionalLightShadows(\n this,\n resolution,\n );\n }\n}\n\nexport class SpotLight {\n constructor(\n position = Position3d.zero(),\n direction = Position3d.down(),\n color = Color.white(),\n cutOff = 35,\n outerCutOff = 40,\n shineColor = Color.white(),\n intensity = 1,\n range = 50,\n ) {\n this.position = position;\n this.direction = direction;\n this.color = color;\n this.shineColor = shineColor;\n this.range = range;\n this.cutOff = cutOff;\n this.outerCutOff = outerCutOff;\n this.intensity = intensity;\n return globalThis.__atlasCreateSpotLight(this);\n }\n\n setColor(color) {\n this.color = color;\n return globalThis.__atlasUpdateSpotLight(this);\n }\n\n createDebugObject() {\n return globalThis.__atlasCreateSpotLightDebugObject(this);\n }\n\n lookAt(target) {\n return globalThis.__atlasLookAtSpotLight(this, target);\n }\n\n castShadows(resolution = 2048) {\n return globalThis.__atlasCastSpotLightShadows(this, resolution);\n }\n}\n\nexport class AreaLight {\n constructor(\n position = Position3d.zero(),\n right = Position3d.right(),\n up = Position3d.up(),\n size = new Size2d(1, 1),\n color = Color.white(),\n shineColor = Color.white(),\n intensity = 1,\n range = 50,\n angle = 90,\n castsBothSides = false,\n rotation = Position3d.zero(),\n ) {\n this.position = position;\n this.right = right;\n this.up = up;\n this.size = size;\n this.color = color;\n this.shineColor = shineColor;\n this.intensity = intensity;\n this.range = range;\n this.angle = angle;\n this.castsBothSides = castsBothSides;\n this.rotation = rotation;\n return globalThis.__atlasCreateAreaLight(this);\n }\n\n getNormal() {\n return globalThis.__atlasGetAreaLightNorm", - "al(this);\n }\n\n setColor(color) {\n this.color = color;\n return globalThis.__atlasUpdateAreaLight(this);\n }\n\n setRotation(rotation) {\n this.rotation = rotation;\n return globalThis.__atlasSetAreaLightRotation(this, rotation);\n }\n\n rotate(delta) {\n return globalThis.__atlasRotateAreaLight(this, delta);\n }\n\n createDebugObject() {\n return globalThis.__atlasCreateAreaLightDebugObject(this);\n }\n\n castShadows(resolution = 2048) {\n return globalThis.__atlasCastAreaLightShadows(this, resolution);\n }\n}\n", + "import { Resource, ResourceType } from \"atlas\";\nimport { Color, Position3d, Size2d } from \"atlas/units\";\n\nexport const TextureType = Object.freeze({\n Color: 0,\n Specular: 1,\n Cubemap: 2,\n Depth: 3,\n DepthCube: 4,\n Normal: 5,\n Parallax: 6,\n SSAONoise: 7,\n SSAO: 8,\n Metallic: 9,\n Roughness: 10,\n AO: 11,\n Opacity: 12,\n HDR: 13,\n PBRPack: 14,\n});\n\nexport const RenderTargetType = Object.freeze({\n Scene: 0,\n Multisampled: 1,\n Shadow: 2,\n CubeShadow: 3,\n GBuffer: 4,\n SSAO: 5,\n SSAOBlur: 6,\n SSR: 7,\n});\n\nexport const RenderPassType = Object.freeze({\n Deferred: 0,\n Forward: 1,\n PathTracing: 2,\n});\n\nexport const Effects = Object.freeze({\n Inversion: { type: \"Inversion\" },\n Grayscale: { type: \"Grayscale\" },\n Sharpen: { type: \"Sharpen\" },\n Blur: { type: \"Blur\", magnitude: 16 },\n EdgeDetection: { type: \"EdgeDetection\" },\n ColorCorrection: {\n type: \"ColorCorrection\",\n exposure: 0,\n contrast: 1,\n saturation: 1,\n gamma: 1,\n temperature: 0,\n tint: 0,\n },\n MotionBlur: { type: \"MotionBlur\", size: 8, separation: 1 },\n ChromaticAberration: {\n type: \"ChromaticAberration\",\n red: 0.01,\n green: 0.006,\n blue: -0.006,\n direction: { x: 0, y: 0 },\n },\n Posterization: { type: \"Posterization\", levels: 5 },\n Pixelation: { type: \"Pixelation\", pixelSize: 8 },\n Dialation: { type: \"Dilation\", size: 8, separation: 1 },\n Dilation: { type: \"Dilation\", size: 8, separation: 1 },\n FilmGrain: { type: \"FilmGrain\", amount: 0.05 },\n});\n\nexport class Texture {\n constructor() {\n this.type = TextureType.Color;\n this.resource = new Resource(ResourceType.File, \"\", \"\");\n this.width = 0;\n this.height = 0;\n this.channels = 0;\n this.id = 0;\n this.borderColor = Color.black();\n }\n\n static fromResource(resource, type = TextureType.Color) {\n return globalThis.__atlasCreateTextureFromResource(resource, type);\n }\n\n static createEmpty(\n width,\n height,\n type = TextureType.Color,\n borderColor = new Color(0, 0, 0, 0),\n ) {\n return globalThis.__atlasCreateEmptyTexture(\n width,\n height,\n type,\n borderColor,\n );\n }\n\n static createColor(color, type = TextureType.Color, width = 1, height = 1) {\n return globalThis.__atlasCreateColorTexture(color, type, width, height);\n }\n\n createCheckerboard(width, height, checkSize, color1, color2) {\n return globalThis.__atlasCreateCheckerboardTexture(\n this,\n width,\n height,\n checkSize,\n color1,\n color2,\n );\n }\n\n createDoubleCheckerboard(\n width,\n height,\n checkSizeBig,\n checkSizeSmall,\n color1,\n color2,\n color3,\n ) {\n return globalThis.__atlasCreateDoubleCheckerboardTexture(\n this,\n width,\n height,\n checkSizeBig,\n checkSizeSmall,\n color1,\n color2,\n color3,\n );\n }\n\n displayToWindow() {\n return globalThis.__atlasDisplayTexture(this);\n }\n}\n\nexport class Cubemap {\n constructor(resources) {\n this.resources = resources;\n this.id = 0;\n return globalThis.__atlasCreateCubemap(resources);\n }\n\n getAverageColor() {\n return globalThis.__atlasGetCubemapAverageColor(this);\n }\n\n static fromResourceGroup(resourceGroup) {\n if (resourceGroup == null) {\n return null;\n }\n return globalThis.__atlasCreateCubemapFromGroup(resourceGroup.resources);\n }\n\n updateWithColors(colors) {\n return globalThis.__atlasUpdateCubemapWithColors(this, colors);\n }\n}\n\nexport class RenderTarget {\n constructor(type = RenderTargetType.Scene, resolution = 1024) {\n this.type = type;\n this.resolution = resolution;\n this.outTextures = [];\n this.depthTexture = null;\n return globalThis.__atlasCreateRenderTarget(type, resolution);\n }\n\n addEffect(effect) {\n return globalThis.__atlasAddRenderTargetEffect(this, effect);\n }\n\n addToPassQueue(type) {\n return globalThis.__atlasAddRenderTargetToPassQueue(this, type);\n }\n\n addToPass(type) {\n return this.addToPassQueue(type);\n }\n\n display() {\n return globalThis.__atlasDisplayRenderTarget(this);\n }\n}\n\nexport class Skybox {\n constructor(cubemap) {\n this.cubemap = cubemap;\n return globalThis.__atlasCreateSkybox(cubemap);\n }\n}\n\nexport class AmbientLight {\n constructor(color = Color.white(), intensity = 0.125) {\n this.color = color;\n this.intensity = intensity;\n }\n}\n\nexport class Light {\n constructor(\n position = Position3d.zero(),\n color = Color.white(),\n distance = 50,\n shineColor = Color.white(),\n intensity = 1,\n ) {\n this.position = position;\n this.color = color;\n this.shineColor = shineColor;\n this.intensity = intensity;\n this.distance = distance;\n return globalThis.__atlasCreatePointLight(this);\n }\n\n setColor(color) {\n this.color = color;\n return globalThis.__atlasUpdatePointLight(this);\n }\n\n createDebugObject() {\n return globalThis.__atlasCreatePointLightDebugObject(this);\n }\n\n castShadows(resolution = 2048) {\n return globalThis.__atlasCastPointLightShadows(this, resolution);\n }\n}\n\nexport class DirectionalLight {\n constructor(\n direction = Position3d.down(),\n color = Color.white(),\n shineColor = Color.white(),\n intensity = 1,\n ) {\n this.direction = direction;\n this.color = color;\n this.shineColor = shineColor;\n this.intensity = intensity;\n return globalThis.__atlasCreateDirectionalLight(this);\n }\n\n setColor(color) {\n this.color = color;\n return globalThis.__atlasUpdateDirectionalLight(this);\n }\n\n castShadows(resolution = 2048) {\n return globalThis.__atlasCastDirectionalLightShadows(\n this,\n resolution,\n );\n }\n}\n\nexport class SpotLight {\n constructor(\n position = Position3d.zero(),\n direction = Position3d.down(),\n color = Color.white(),\n cutOff = 35,\n outerCutOff = 40,\n shineColor = Color.white(),\n intensity = 1,\n range = 50,\n ) {\n this.position = position;\n this.direction = direction;\n this.color = color;\n this.shineColor = shineColor;\n this.range = range;\n this.cutOff = cutOff;\n this.outerCutOff = outerCutOff;\n this.intensity = intensity;\n return globalThis.__atlasCreateSpotLight(this);\n }\n\n setColor(color) {\n this.color = color;\n return globalThis.__atlasUpdateSpotLight(this);\n }\n\n createDebugObject() {\n return globalThis.__atlasCreateSpotLightDebugObject(this);\n }\n\n lookAt(target) {\n return globalThis.__atlasLookAtSpotLight(this, target);\n }\n\n castShadows(resolution = 2048) {\n return globalThis.__atlasCastSpotLightShadows(this, resolution);\n }\n}\n\nexport class AreaLight {\n constructor(\n position = Position3d.zero(),\n right = Position3d.right(),\n up = Position3d.up(),\n size = new Size2d(1, 1),\n color = Color.white(),\n shineColor = Color.white(),\n intensity = 1,\n range = 50,\n angle = 90,\n castsBothSides = false,\n rotation = Position3d.zero(),\n ) {\n this.position = position;\n this.right = right;\n this.up = up;\n this.size = size;\n this.color = color;\n this.shineColor = shineColor;\n this.intensity = intensity;\n this.range = range;\n this.angle = angle;\n this.castsBothSides = castsBothSides;\n this.rotation = rotation;\n return globalThis.__atlasCreateAreaLight(this);\n }\n\n getNormal() {\n return globalThis.__atlasGetA", + "reaLightNormal(this);\n }\n\n setColor(color) {\n this.color = color;\n return globalThis.__atlasUpdateAreaLight(this);\n }\n\n setRotation(rotation) {\n this.rotation = rotation;\n return globalThis.__atlasSetAreaLightRotation(this, rotation);\n }\n\n rotate(delta) {\n return globalThis.__atlasRotateAreaLight(this, delta);\n }\n\n createDebugObject() {\n return globalThis.__atlasCreateAreaLightDebugObject(this);\n }\n\n castShadows(resolution = 2048) {\n return globalThis.__atlasCastAreaLightShadows(this, resolution);\n }\n}\n", }; static const AtlasPackedScriptSource ATLAS_GRAPHICS = {ATLAS_GRAPHICS_PARTS, 2}; diff --git a/include/atlas/runtime/context.h b/include/atlas/runtime/context.h index 0d3bb549..2c99ad23 100644 --- a/include/atlas/runtime/context.h +++ b/include/atlas/runtime/context.h @@ -46,6 +46,10 @@ class ProjectConfig { std::string mainScene; std::string inputActions; bool useUpscaling = false; + float upscalingRatio = 0.5f; + bool screenSpaceReflections = false; + int screenSpaceReflectionQuality = 1; + bool screenSpaceReflectionDebug = false; std::vector assetDirectories; }; diff --git a/include/atlas/texture.h b/include/atlas/texture.h index 5034f3c9..03ad9bda 100644 --- a/include/atlas/texture.h +++ b/include/atlas/texture.h @@ -392,6 +392,7 @@ enum class RenderTargetType { GBuffer, SSAO, SSAOBlur, + SSR, }; class Effect; diff --git a/include/atlas/window.h b/include/atlas/window.h index 42b7fd9f..f02aad36 100644 --- a/include/atlas/window.h +++ b/include/atlas/window.h @@ -748,6 +748,15 @@ class Window { void enableSSR(bool enabled = true) { this->useSSR = enabled; } bool isSSREnabled() const { return this->useSSR; } + void setSSRDebugMode(bool enabled) { this->ssrDebugMode = enabled; } + void setSSRQuality(int quality) { + const int clamped = std::clamp(quality, 0, 2); + if (this->ssrQuality != clamped) { + this->ssrQuality = clamped; + this->ssrFramebuffer.reset(); + this->ssrHistoryFramebuffer.reset(); + } + } /** * @brief Points to the render target currently bound for drawing. @@ -857,6 +866,7 @@ class Window { std::shared_ptr volumetricBuffer; std::shared_ptr lightBuffer; std::shared_ptr ssrFramebuffer; + std::shared_ptr ssrHistoryFramebuffer; std::shared_ptr bloomBuffer; bool waitForTracer = false; @@ -953,6 +963,8 @@ class Window { bool debug = false; bool useSSR = false; + int ssrQuality = 1; + bool ssrDebugMode = false; /** * @brief Whether to use multi-pass point light shadow rendering. diff --git a/include/opal/opal.h b/include/opal/opal.h index 6c84aade..057f5089 100644 --- a/include/opal/opal.h +++ b/include/opal/opal.h @@ -723,6 +723,10 @@ class Pipeline { int callerId = -1); void bindTextureCubemap(const std::string &name, uint textureId, int unit, int callerId = -1); +#ifdef METAL + void bindTextureArray(const std::vector> &textures, + uint32_t bufferIndex); +#endif #ifdef VULKAN VkPipelineLayout pipelineLayout = VK_NULL_HANDLE; @@ -1212,6 +1216,7 @@ class CommandBuffer { void dispatch(uint threadCountX, uint threadCountY = 1, uint threadCountZ = 1); void computeBarrier(); + void generateMipmaps(const std::shared_ptr &texture); void performResolve(const std::shared_ptr &resolveAction); bool performSpatialUpscale(const std::shared_ptr &sourceTexture); diff --git a/include/photon/illuminate.h b/include/photon/illuminate.h index 0b7a0603..18363de3 100644 --- a/include/photon/illuminate.h +++ b/include/photon/illuminate.h @@ -75,25 +75,24 @@ class PathTracing { public: #ifdef METAL /** @brief Runs one path tracing pass into the active output texture. */ - void render(const std::shared_ptr &commandBuffer); + void render(const std::shared_ptr &commandBuffer, + const std::shared_ptr &output, + const std::shared_ptr &brightOutput); /** @brief Rebuilds BLAS/TLAS data for the current scene geometry. */ void buildAccelerationStructure( const std::shared_ptr &commandBuffer); /** @brief Uploads light lists used by path tracing shaders. */ - void createLightBuffers(); + bool createLightBuffers(); /** @brief Initializes compute pipelines and internal buffers. */ void init(); /** @brief Resizes path tracing output and history textures. */ void resizeOutput(int width, int height); /** @brief Current frame output texture. */ - 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; + int raysPerPixel = 1; /** @brief Maximum bounce count for indirect transport. */ int maxBounces = 1; /** @brief Scalar multiplier for indirect lighting contribution. */ @@ -103,12 +102,6 @@ class PathTracing { /** @brief Strength multiplier applied to sampled normal maps. */ float normalMapStrength = 1.0f; - /** @brief Source framebuffer used when copying accumulation history. */ - std::shared_ptr copySrcFramebuffer; - /** @brief Destination framebuffer used when copying accumulation history. - */ - std::shared_ptr copyDstFramebuffer; - private: std::shared_ptr pointLights; std::shared_ptr spotLights; @@ -122,16 +115,25 @@ class PathTracing { std::vector> materialTextures; std::shared_ptr sceneTLAS; std::shared_ptr pathTracingPipeline; + std::shared_ptr pathDenoisePipeline; std::shared_ptr computePathTracer; + std::shared_ptr computePathDenoiser; + std::array, 2> denoiseTextures; + std::array, 4> pathTracingAovTextures; + std::shared_ptr pathTracingHistoryGuide; std::unordered_map> objectBLAS; std::vector cachedObjects; + std::vector cachedInstanceTransforms; + std::vector cachedObjectStateHashes; + uint64_t cachedLightHash = 0; int frameIndex = 0; int outputWidth = 0; int outputHeight = 0; glm::mat4 cachedInvViewProj = glm::mat4(1.0f); + glm::mat4 previousViewProj = glm::mat4(1.0f); glm::vec3 cachedDirectionalLightDirection = glm::vec3(0.0f, -1.0f, 0.0f); glm::vec3 cachedDirectionalLightColor = glm::vec3(1.0f, 1.0f, 1.0f); float cachedDirectionalLightIntensity = -1.0f; diff --git a/opal/command_buffer.cpp b/opal/command_buffer.cpp index 5a720aad..6268f59a 100644 --- a/opal/command_buffer.cpp +++ b/opal/command_buffer.cpp @@ -865,6 +865,18 @@ void bindComputeTextures(const std::shared_ptr &pipeline, } auto &pipelineState = metal::pipelineState(pipeline.get()); + if (pipelineState.textureArgumentBuffer != nullptr) { + encoder->setBuffer(pipelineState.textureArgumentBuffer, 0, + pipelineState.textureArgumentBufferIndex); + for (const auto &texture : pipelineState.textureArgumentTextures) { + if (texture == nullptr) { + continue; + } + auto &textureState = metal::textureState(texture.get()); + encoder->useResource(textureState.texture, + MTL::ResourceUsageRead); + } + } std::array desiredTextures{}; std::array desiredSamplers{}; desiredTextures.fill(nullptr); @@ -1161,6 +1173,24 @@ void CommandBuffer::start() { vkResetFences(device->logicalDevice, 1, &inFlightFences[currentFrame]); #elif defined(METAL) auto &state = metal::commandBufferState(this); + state.inFlightCommandBuffers.erase( + std::remove_if(state.inFlightCommandBuffers.begin(), + state.inFlightCommandBuffers.end(), + [](MTL::CommandBuffer *buffer) { + if (buffer->status() < MTL::CommandBufferStatusCompleted) { + return false; + } + buffer->release(); + return true; + }), + state.inFlightCommandBuffers.end()); + if (state.inFlightCommandBuffers.size() >= 3) { + auto *oldest = state.inFlightCommandBuffers.front(); + oldest->waitUntilCompleted(); + oldest->release(); + state.inFlightCommandBuffers.erase( + state.inFlightCommandBuffers.begin()); + } if (state.autoreleasePool != nullptr) { state.autoreleasePool->release(); } @@ -1495,8 +1525,9 @@ void CommandBuffer::commit() { state.commandBuffer->presentDrawable(state.drawable); } + state.commandBuffer->retain(); + state.inFlightCommandBuffers.push_back(state.commandBuffer); state.commandBuffer->commit(); - state.commandBuffer->waitUntilCompleted(); state.commandBuffer = nullptr; state.passDescriptor = nullptr; state.drawable = nullptr; @@ -2131,6 +2162,32 @@ void CommandBuffer::computeBarrier() { #endif } +void CommandBuffer::generateMipmaps(const std::shared_ptr &texture) { + if (texture == nullptr) { + return; + } +#ifdef OPENGL + texture->generateMipmaps(0); +#elif defined(METAL) + auto &state = metal::commandBufferState(this); + auto &textureState = metal::textureState(texture.get()); + if (state.commandBuffer == nullptr || textureState.texture == nullptr) { + return; + } + if (state.encoder != nullptr) { + state.encoder->endEncoding(); + state.encoder = nullptr; + } + if (state.computeEncoder != nullptr) { + state.computeEncoder->endEncoding(); + state.computeEncoder = nullptr; + } + auto *blitEncoder = state.commandBuffer->blitCommandEncoder(); + blitEncoder->generateMipmaps(textureState.texture); + blitEncoder->endEncoding(); +#endif +} + bool CommandBuffer::performSpatialUpscale( const std::shared_ptr &sourceTexture) { #ifdef METAL diff --git a/opal/metal_state.cpp b/opal/metal_state.cpp index 1622442b..7736ae4b 100644 --- a/opal/metal_state.cpp +++ b/opal/metal_state.cpp @@ -742,6 +742,15 @@ void releasePipelineState(Pipeline *pipeline) { state.shaderBuffers.clear(); state.uniformData.clear(); state.texturesByUnit.clear(); + state.textureArgumentTextures.clear(); + if (state.textureArgumentBuffer != nullptr) { + state.textureArgumentBuffer->release(); + state.textureArgumentBuffer = nullptr; + } + if (state.textureArgumentEncoder != nullptr) { + state.textureArgumentEncoder->release(); + state.textureArgumentEncoder = nullptr; + } if (state.depthStencilState != nullptr) { state.depthStencilState->release(); state.depthStencilState = nullptr; @@ -787,6 +796,11 @@ void releaseCommandBufferState(CommandBuffer *commandBuffer) { state.passDescriptor->release(); state.passDescriptor = nullptr; } + for (auto *submitted : state.inFlightCommandBuffers) { + submitted->waitUntilCompleted(); + submitted->release(); + } + state.inFlightCommandBuffers.clear(); state.commandBuffer = nullptr; state.drawable = nullptr; state.boundVertexTextures.fill(nullptr); diff --git a/opal/metal_state.h b/opal/metal_state.h index 2f8b9bda..f3dab6f5 100644 --- a/opal/metal_state.h +++ b/opal/metal_state.h @@ -126,6 +126,10 @@ struct PipelineState { std::unordered_map uniformBuffers; std::unordered_map> shaderBuffers; std::unordered_map> texturesByUnit; + MTL::ArgumentEncoder *textureArgumentEncoder = nullptr; + MTL::Buffer *textureArgumentBuffer = nullptr; + uint32_t textureArgumentBufferIndex = 0; + std::vector> textureArgumentTextures; MTL::PrimitiveType primitiveType = MTL::PrimitiveTypeTriangle; MTL::CullMode cullMode = MTL::CullModeBack; MTL::Winding frontFace = MTL::WindingCounterClockwise; @@ -154,6 +158,7 @@ struct FramebufferState { struct CommandBufferState { NS::AutoreleasePool *autoreleasePool = nullptr; MTL::CommandBuffer *commandBuffer = nullptr; + std::vector inFlightCommandBuffers; MTL::RenderCommandEncoder *encoder = nullptr; MTL::ComputeCommandEncoder *computeEncoder = nullptr; MTL::RenderPassDescriptor *passDescriptor = nullptr; diff --git a/opal/texture.cpp b/opal/texture.cpp index 16b68410..b21b19dd 100644 --- a/opal/texture.cpp +++ b/opal/texture.cpp @@ -1016,6 +1016,53 @@ void Pipeline::bindTexture(const std::string &name, resourceInfo.send(); } +#ifdef METAL +void Pipeline::bindTextureArray( + const std::vector> &textures, + uint32_t bufferIndex) { + if (shaderProgram == nullptr || Device::globalInstance == nullptr) { + return; + } + auto &state = metal::pipelineState(this); + if (state.textureArgumentBuffer != nullptr && + state.textureArgumentBufferIndex == bufferIndex && + state.textureArgumentTextures == textures) { + return; + } + auto &programState = metal::programState(shaderProgram.get()); + if (programState.computeFunction == nullptr) { + return; + } + if (state.textureArgumentBuffer != nullptr) { + state.textureArgumentBuffer->release(); + state.textureArgumentBuffer = nullptr; + } + if (state.textureArgumentEncoder != nullptr) { + state.textureArgumentEncoder->release(); + } + state.textureArgumentEncoder = + programState.computeFunction->newArgumentEncoder(bufferIndex); + if (state.textureArgumentEncoder == nullptr) { + return; + } + auto &deviceState = metal::deviceState(Device::globalInstance); + state.textureArgumentBuffer = deviceState.device->newBuffer( + state.textureArgumentEncoder->encodedLength(), + MTL::ResourceStorageModeShared); + state.textureArgumentEncoder->setArgumentBuffer( + state.textureArgumentBuffer, 0); + for (size_t i = 0; i < textures.size(); ++i) { + if (textures[i] == nullptr) { + continue; + } + auto &textureState = metal::textureState(textures[i].get()); + state.textureArgumentEncoder->setTexture(textureState.texture, i); + } + state.textureArgumentBufferIndex = bufferIndex; + state.textureArgumentTextures = textures; +} +#endif + void Pipeline::bindTexture2D(const std::string &name, uint textureId, int unit, int callerId) { #ifdef OPENGL diff --git a/photon/gi.cpp b/photon/gi.cpp index 10b5d35f..60f57243 100644 --- a/photon/gi.cpp +++ b/photon/gi.cpp @@ -556,24 +556,20 @@ void photon::GlobalIllumination::updateProbeLayout() { accelerationStructureDirty = false; } - float layoutPad = spacing * 0.25f; - Position3d minWs = hasGeometry ? Position3d(boundsMin.x - layoutPad, - boundsMin.y - layoutPad, - boundsMin.z - layoutPad) + float layoutInset = spacing * 0.5f; + Position3d minWs = hasGeometry ? Position3d(boundsMin.x + layoutInset, + boundsMin.y + layoutInset, + boundsMin.z + layoutInset) : Position3d(-spacing, -spacing, -spacing); - Position3d maxWs = hasGeometry ? Position3d(boundsMax.x + layoutPad, - boundsMax.y + layoutPad, - boundsMax.z + layoutPad) + Position3d maxWs = hasGeometry ? Position3d(boundsMax.x - layoutInset, + boundsMax.y - layoutInset, + boundsMax.z - layoutInset) : Position3d(spacing, spacing, spacing); - - auto snapDown = [&](float v) { return std::floor(v / spacing) * spacing; }; - auto snapUp = [&](float v) { return std::ceil(v / spacing) * spacing; }; - minWs.x = snapDown(minWs.x); - minWs.y = snapDown(minWs.y); - minWs.z = snapDown(minWs.z); - maxWs.x = snapUp(maxWs.x); - maxWs.y = snapUp(maxWs.y); - maxWs.z = snapUp(maxWs.z); + if (hasGeometry) { + if (minWs.x > maxWs.x) minWs.x = maxWs.x = (boundsMin.x + boundsMax.x) * 0.5f; + if (minWs.y > maxWs.y) minWs.y = maxWs.y = (boundsMin.y + boundsMax.y) * 0.5f; + if (minWs.z > maxWs.z) minWs.z = maxWs.z = (boundsMin.z + boundsMax.z) * 0.5f; + } Position3d extent = maxWs - minWs; extent.x = std::max(0.0f, extent.x); diff --git a/photon/path_tracing.cpp b/photon/path_tracing.cpp index e673364a..1bb1bcbc 100644 --- a/photon/path_tracing.cpp +++ b/photon/path_tracing.cpp @@ -24,8 +24,7 @@ #ifdef METAL namespace { -constexpr int kPathTracerMaterialTextureUnitStart = 12; -constexpr int kPathTracerMaxMaterialTextures = 48; +constexpr int kPathTracerMaxMaterialTextures = 256; constexpr int kPathTracerSkyboxTextureUnit = 60; std::shared_ptr createFallbackSkyboxTexture() { @@ -64,6 +63,41 @@ bool mat4ApproximatelyEqual(const glm::mat4 &a, const glm::mat4 &b, return true; } +uint64_t pathTracingObjectStateHash(const CoreObject *object) { + uint64_t hash = 1469598103934665603ULL; + auto append = [&hash](const void *data, size_t size) { + const auto *bytes = static_cast(data); + for (size_t i = 0; i < size; ++i) { + hash ^= bytes[i]; + hash *= 1099511628211ULL; + } + }; + const auto &material = object->material; + append(&material.albedo, sizeof(material.albedo)); + append(&material.metallic, sizeof(material.metallic)); + append(&material.roughness, sizeof(material.roughness)); + append(&material.ao, sizeof(material.ao)); + append(&material.reflectivity, sizeof(material.reflectivity)); + append(&material.emissiveColor, sizeof(material.emissiveColor)); + append(&material.emissiveIntensity, sizeof(material.emissiveIntensity)); + append(&material.normalMapStrength, sizeof(material.normalMapStrength)); + append(&material.useNormalMap, sizeof(material.useNormalMap)); + append(&material.textureScale, sizeof(material.textureScale)); + append(&material.textureOffset, sizeof(material.textureOffset)); + append(&material.transmittance, sizeof(material.transmittance)); + append(&material.ior, sizeof(material.ior)); + const size_t vertexCount = object->vertices.size(); + const size_t indexCount = object->indices.size(); + append(&vertexCount, sizeof(vertexCount)); + append(&indexCount, sizeof(indexCount)); + for (const auto &texture : object->textures) { + const auto *textureAddress = texture.texture.get(); + append(&textureAddress, sizeof(textureAddress)); + append(&texture.type, sizeof(texture.type)); + } + return hash; +} + int registerMaterialTextureSlot( const Texture &texture, std::vector> &materialTextures, @@ -168,43 +202,73 @@ void photon::PathTracing::init() { pathTracingPipeline->setComputeThreadgroupSize(8, 8, 1); pathTracingPipeline->build(); + ComputeShader pathDenoiserShader = + ComputeShader::fromDefaultShader(AtlasComputeShader::PathDenoiser); + pathDenoiserShader.compile(); + computePathDenoiser = std::make_shared(); + computePathDenoiser->computeShader = pathDenoiserShader; + computePathDenoiser->compile(); + pathDenoisePipeline = opal::Pipeline::create(); + pathDenoisePipeline->setShaderProgram(computePathDenoiser->shader); + pathDenoisePipeline->setComputeThreadgroupSize(8, 8, 1); + pathDenoisePipeline->build(); + outputWidth = std::max(1, Window::mainWindow->viewportWidth); outputHeight = std::max(1, Window::mainWindow->viewportHeight); - pathTracingTexture = std::make_shared( - Texture::create(outputWidth, outputHeight, opal::TextureFormat::Rgba16F, - opal::TextureDataFormat::Rgba, TextureType::Color)); - 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, + for (auto &texture : denoiseTextures) { + texture = std::make_shared( + Texture::create(outputWidth, outputHeight, + opal::TextureFormat::Rgba16F, + opal::TextureDataFormat::Rgba, + TextureType::Color)); + } + for (auto &texture : pathTracingAovTextures) { + texture = std::make_shared( + Texture::create(outputWidth, outputHeight, + opal::TextureFormat::Rgba16F, + opal::TextureDataFormat::Rgba, + TextureType::Color)); + } + pathTracingHistoryGuide = std::make_shared( + Texture::create(outputWidth, outputHeight, + opal::TextureFormat::Rgba16F, opal::TextureDataFormat::Rgba, TextureType::Color)); - - copySrcFramebuffer = std::make_shared(); - copyDstFramebuffer = std::make_shared(); } 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 && - pathTracingTextureBright != nullptr) { + pathTracingTexturePrev != nullptr) { return; } outputWidth = newWidth; outputHeight = newHeight; - pathTracingTexture = std::make_shared( - Texture::create(outputWidth, outputHeight, opal::TextureFormat::Rgba16F, - opal::TextureDataFormat::Rgba, TextureType::Color)); 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, + for (auto &texture : denoiseTextures) { + texture = std::make_shared( + Texture::create(outputWidth, outputHeight, + opal::TextureFormat::Rgba16F, + opal::TextureDataFormat::Rgba, + TextureType::Color)); + } + for (auto &texture : pathTracingAovTextures) { + texture = std::make_shared( + Texture::create(outputWidth, outputHeight, + opal::TextureFormat::Rgba16F, + opal::TextureDataFormat::Rgba, + TextureType::Color)); + } + pathTracingHistoryGuide = std::make_shared( + Texture::create(outputWidth, outputHeight, + opal::TextureFormat::Rgba16F, opal::TextureDataFormat::Rgba, TextureType::Color)); frameIndex = 0; } @@ -279,6 +343,14 @@ void photon::PathTracing::buildAccelerationStructure( bool needsRebuild = objectBLAS.empty() || cachedObjects.size() != traceableObjects.size(); + std::vector objectStateHashes; + objectStateHashes.reserve(traceableObjects.size()); + for (const auto *object : traceableObjects) { + objectStateHashes.push_back(pathTracingObjectStateHash(object)); + } + if (cachedObjectStateHashes != objectStateHashes) { + needsRebuild = true; + } if (!needsRebuild) { for (size_t i = 0; i < traceableObjects.size(); ++i) { if (cachedObjects[i] != traceableObjects[i]) { @@ -293,6 +365,7 @@ void photon::PathTracing::buildAccelerationStructure( objectBLAS.clear(); materialTextures.clear(); cachedObjects = traceableObjects; + cachedObjectStateHashes = objectStateHashes; std::unordered_map textureSlots; for (auto *object : traceableObjects) { @@ -499,15 +572,34 @@ void photon::PathTracing::buildAccelerationStructure( instanceData.push_back(d); } - instanceDataBuffer = opal::Buffer::create( - opal::BufferUsage::ShaderRead, - instanceData.size() * sizeof(InstanceData), instanceData.data()); - - sceneTLAS = opal::InstanceAccelerationStructure::create(instances); - commandBuffer->buildInstanceAccelerationStructure(sceneTLAS); + bool transformsChanged = needsRebuild || + cachedInstanceTransforms.size() != + traceableObjects.size(); + if (!transformsChanged) { + for (size_t i = 0; i < traceableObjects.size(); ++i) { + if (!mat4ApproximatelyEqual(cachedInstanceTransforms[i], + traceableObjects[i]->model, 0.000001f)) { + transformsChanged = true; + break; + } + } + } + if (transformsChanged) { + cachedInstanceTransforms.clear(); + cachedInstanceTransforms.reserve(traceableObjects.size()); + for (auto *object : traceableObjects) { + cachedInstanceTransforms.push_back(object->model); + } + instanceDataBuffer = opal::Buffer::create( + opal::BufferUsage::ShaderRead, + instanceData.size() * sizeof(InstanceData), instanceData.data()); + sceneTLAS = opal::InstanceAccelerationStructure::create(instances); + commandBuffer->buildInstanceAccelerationStructure(sceneTLAS); + frameIndex = 0; + } } -void photon::PathTracing::createLightBuffers() { +bool photon::PathTracing::createLightBuffers() { struct PointLightData { float position[3]; float intensity; @@ -621,49 +713,64 @@ void photon::PathTracing::createLightBuffers() { if (pointLightData.empty()) { pointLightData.push_back(PointLightData{}); } - this->pointLights = opal::Buffer::create( - opal::BufferUsage::ShaderRead, - pointLightData.size() * sizeof(PointLightData), pointLightData.data()); - if (spotLightData.empty()) { spotLightData.push_back(SpotLightData{}); } - this->spotLights = opal::Buffer::create( - opal::BufferUsage::ShaderRead, - spotLightData.size() * sizeof(SpotLightData), spotLightData.data()); - if (areaLightData.empty()) { areaLightData.push_back(AreaLightData{}); } + uint64_t lightHash = 1469598103934665603ULL; + auto hashBytes = [&lightHash](const void *data, size_t size) { + const auto *bytes = static_cast(data); + for (size_t i = 0; i < size; ++i) { + lightHash ^= bytes[i]; + lightHash *= 1099511628211ULL; + } + }; + hashBytes(pointLightData.data(), + pointLightData.size() * sizeof(PointLightData)); + hashBytes(spotLightData.data(), spotLightData.size() * sizeof(SpotLightData)); + hashBytes(areaLightData.data(), areaLightData.size() * sizeof(AreaLightData)); + if (cachedLightHash == lightHash && pointLights != nullptr && + spotLights != nullptr && areaLights != nullptr) { + return false; + } + cachedLightHash = lightHash; + this->pointLights = opal::Buffer::create( + opal::BufferUsage::ShaderRead, + pointLightData.size() * sizeof(PointLightData), pointLightData.data()); + this->spotLights = opal::Buffer::create( + opal::BufferUsage::ShaderRead, + spotLightData.size() * sizeof(SpotLightData), spotLightData.data()); this->areaLights = opal::Buffer::create( opal::BufferUsage::ShaderRead, areaLightData.size() * sizeof(AreaLightData), areaLightData.data()); + return true; } void photon::PathTracing::render( - const std::shared_ptr &commandBuffer) { - - // Copy textures - copySrcFramebuffer->attachTexture(pathTracingTexture->texture, 0); - copyDstFramebuffer->attachTexture(pathTracingTexturePrev->texture, 0); - auto copy = opal::ResolveAction::createForColorAttachment( - copySrcFramebuffer, copyDstFramebuffer, 0); - commandBuffer->performResolve(copy); + const std::shared_ptr &commandBuffer, + const std::shared_ptr &output, + const std::shared_ptr &brightOutput) { auto view = Window::mainWindow->getCamera()->calculateViewMatrix(); auto proj = Window::mainWindow->calculateProjectionMatrix(); auto invViewProj = glm::inverse(proj * view); + auto viewProj = proj * view; if (frameIndex == 0) { cachedInvViewProj = invViewProj; + previousViewProj = viewProj; } else if (!mat4ApproximatelyEqual(cachedInvViewProj, invViewProj, 0.00001f)) { frameIndex = 0; cachedInvViewProj = invViewProj; + previousViewProj = viewProj; } pathTracingPipeline->setUniformMat4f("cam.invViewProj", invViewProj); + pathTracingPipeline->setUniformMat4f("cam.prevViewProj", previousViewProj); pathTracingPipeline->setUniform3f( "cam.camPos", Window::mainWindow->getCamera()->position.x, Window::mainWindow->getCamera()->position.y, @@ -784,13 +891,24 @@ void photon::PathTracing::render( this->indirectStrength); this->buildAccelerationStructure(commandBuffer); - this->createLightBuffers(); + if (this->createLightBuffers()) { + frameIndex = 0; + } commandBuffer->bindPipeline(this->pathTracingPipeline); - pathTracingPipeline->bindTexture("outTex", pathTracingTexture->texture, 0); - pathTracingPipeline->bindTexture("prevTex", pathTracingTexturePrev->texture, - 1); - pathTracingPipeline->bindTexture("brightTex", - pathTracingTextureBright->texture, 2); + pathTracingPipeline->bindTexture("outTex", output, 0); + pathTracingPipeline->bindTexture("historyTex", + pathTracingTexturePrev->texture, 1); + pathTracingPipeline->bindTexture("brightTex", brightOutput, 2); + pathTracingPipeline->bindTexture( + "albedoRoughnessTex", pathTracingAovTextures[0]->texture, 3); + pathTracingPipeline->bindTexture( + "normalDepthTex", pathTracingAovTextures[1]->texture, 4); + pathTracingPipeline->bindTexture( + "motionObjectTex", pathTracingAovTextures[2]->texture, 5); + pathTracingPipeline->bindTexture( + "momentsHitTex", pathTracingAovTextures[3]->texture, 6); + pathTracingPipeline->bindTexture( + "historyGuideTex", pathTracingHistoryGuide->texture, 7); static std::shared_ptr fallbackSkyboxTexture = nullptr; if (fallbackSkyboxTexture == nullptr) { @@ -839,20 +957,34 @@ void photon::PathTracing::render( std::min(static_cast(materialTextures.size()), kPathTracerMaxMaterialTextures)); - for (int i = 0; i < kPathTracerMaxMaterialTextures; ++i) { - std::shared_ptr texture = nullptr; - if (i < static_cast(materialTextures.size())) { - texture = materialTextures[static_cast(i)]; - } - pathTracingPipeline->bindTexture( - "materialTexture" + std::to_string(i), texture, - kPathTracerMaterialTextureUnitStart + i); - } + pathTracingPipeline->bindTextureArray(materialTextures, 12); commandBuffer->dispatch(outputWidth, outputHeight, 1); commandBuffer->computeBarrier(); + const std::array denoiseSteps = {1, 2, 4}; + for (size_t pass = 0; pass < denoiseSteps.size(); ++pass) { + const auto &input = pass == 0 + ? output + : denoiseTextures[(pass - 1) % 2]->texture; + const auto &denoisedOutput = + pass + 1 == denoiseSteps.size() + ? output + : denoiseTextures[pass % 2]->texture; + commandBuffer->bindPipeline(pathDenoisePipeline); + pathDenoisePipeline->bindTexture("inputTexture", input, 0); + pathDenoisePipeline->bindTexture("outputTexture", denoisedOutput, 1); + pathDenoisePipeline->bindTexture("brightTexture", brightOutput, 2); + pathDenoisePipeline->bindTexture( + "guideTexture", pathTracingAovTextures[1]->texture, 3); + pathDenoisePipeline->setUniform1i("parameters.stepWidth", + denoiseSteps[pass]); + commandBuffer->dispatch(outputWidth, outputHeight, 1); + commandBuffer->computeBarrier(); + } + + previousViewProj = viewProj; frameIndex++; } diff --git a/runtime/docs/other.md b/runtime/docs/other.md index a8759959..9b5836c0 100644 --- a/runtime/docs/other.md +++ b/runtime/docs/other.md @@ -135,7 +135,7 @@ The scene can define an `environment` object. This combines the scene `Environme "globalLight": { "enabled": true, "castsShadows": true, - "shadowResolution": 4096 + "shadowResolution": 2048 }, "clouds": { "enabled": true, diff --git a/runtime/lib/context.cpp b/runtime/lib/context.cpp index d946bf6a..85057acd 100644 --- a/runtime/lib/context.cpp +++ b/runtime/lib/context.cpp @@ -102,7 +102,7 @@ struct RuntimeEnvironmentDefinition { bool useAtmosphereSkybox = false; bool useGlobalLight = false; bool atmosphereCastsShadows = false; - int atmosphereShadowResolution = 4096; + int atmosphereShadowResolution = 2048; }; class RuntimeScriptComponent final : public Component { @@ -1662,7 +1662,7 @@ void syncEditorLightObject(Context &context, GameObject &object) { tryReadColorAny(*source, {"shineColor"}, it->second->shineColor); tryReadFloatAny(*source, {"intensity"}, it->second->intensity); bool castsShadows = false; - int resolution = 4096; + int resolution = 2048; tryReadBoolAny(*source, {"castsShadows"}, castsShadows); tryReadIntAny(*source, {"shadowResolution"}, resolution); if (castsShadows && it->second->shadowRenderTarget == nullptr && @@ -4051,6 +4051,8 @@ makeContextWithWindowOptions(std::string projectFile, void *metalView, bool mouseCaptured = false; bool multisampling = false; float ssaoScale = 0.4f; + float renderScale = 0.5f; + bool useUpscaling = false; bool editorControls = false; if (auto *windowTable = configTable["window"].as_table()) { @@ -4065,6 +4067,12 @@ makeContextWithWindowOptions(std::string projectFile, void *metalView, multisampling = (*windowTable)["multisampling"].value_or(false); ssaoScale = (*windowTable)["ssaoScale"].value_or(0.4f); } + if (auto *rendererTable = configTable["renderer"].as_table()) { + useUpscaling = + (*rendererTable)["use_upscaling"].value_or(false); + renderScale = std::clamp( + (*rendererTable)["upscaling_ratio"].value_or(0.5f), 0.5f, 1.0f); + } if (auto *editorTable = configTable["editor"].as_table()) { editorControls = (*editorTable)["controls"].value_or(false); } @@ -4075,7 +4083,7 @@ makeContextWithWindowOptions(std::string projectFile, void *metalView, .title = "Atlas Runtime", .width = embedded ? 1 : resWidth, .height = embedded ? 1 : resHeight, - .renderScale = 1.f, + .renderScale = useUpscaling ? renderScale : 1.0f, .mouseCaptured = embedded ? false : mouseCaptured, .multisampling = multisampling, .decorations = !embedded, @@ -5836,11 +5844,21 @@ void Context::loadProject() { std::string mainScene = "main.ascene"; std::vector assetDirectories; bool useUpscaling = false; + float upscalingRatio = 0.5f; + bool screenSpaceReflections = false; + int screenSpaceReflectionQuality = 1; + bool screenSpaceReflectionDebug = false; if (auto *renderer = configTable["renderer"].as_table()) { defaultRenderer = (*renderer)["default"].value_or("normal"); globalIllumination = (*renderer)["global_illumination"].value_or(false); useUpscaling = (*renderer)["use_upscaling"].value_or(false); + upscalingRatio = (*renderer)["upscaling_ratio"].value_or(0.5f); + screenSpaceReflections = (*renderer)["ssr"].value_or(false); + screenSpaceReflectionQuality = + std::clamp((*renderer)["ssr_quality"].value_or(1), 0, 2); + screenSpaceReflectionDebug = + (*renderer)["ssr_debug"].value_or(false); } if (auto *gameTable = configTable["game"].as_table()) { @@ -5874,6 +5892,10 @@ void Context::loadProject() { config.mainScene = mainScene; config.assetDirectories = assetDirectories; config.useUpscaling = useUpscaling; + config.upscalingRatio = std::clamp(upscalingRatio, 0.5f, 1.0f); + config.screenSpaceReflections = screenSpaceReflections; + config.screenSpaceReflectionQuality = screenSpaceReflectionQuality; + config.screenSpaceReflectionDebug = screenSpaceReflectionDebug; Workspace::get().setRootPath(projectDir); initializeScripting(); @@ -6223,7 +6245,7 @@ void Context::loadScene(Window &window, const json &sceneData) { Color color = Color::white(); Color shineColor = Color::white(); float intensity = 1.0f; - int shadowResolution = 4096; + int shadowResolution = 2048; bool castsShadows = false; tryReadVec3(lightData, "direction", direction); diff --git a/runtime/lib/runtime.cpp b/runtime/lib/runtime.cpp index a2f569bf..95fffba2 100644 --- a/runtime/lib/runtime.cpp +++ b/runtime/lib/runtime.cpp @@ -23,13 +23,18 @@ void RuntimeScene::initialize(Window &window) { if (runtimeContext->config.globalIllumination) { window.enableGlobalIllumination(); } + window.enableSSR(runtimeContext->config.screenSpaceReflections); + window.setSSRQuality( + runtimeContext->config.screenSpaceReflectionQuality); + window.setSSRDebugMode( + runtimeContext->config.screenSpaceReflectionDebug); } else if (runtimeContext->config.renderer == "pathtracing") { window.enablePathTracing(); } if (runtimeContext->config.useUpscaling) { #ifdef METAL - window.useMetalUpscaling(); + window.useMetalUpscaling(runtimeContext->config.upscalingRatio); #endif } diff --git a/runtime/lib/scripting.cpp b/runtime/lib/scripting.cpp index c5a71f6b..c55fced9 100644 --- a/runtime/lib/scripting.cpp +++ b/runtime/lib/scripting.cpp @@ -376,6 +376,8 @@ RenderTargetType toNativeRenderTargetType(int type) { return RenderTargetType::SSAO; case 6: return RenderTargetType::SSAOBlur; + case 7: + return RenderTargetType::SSR; case 0: default: return RenderTargetType::Scene; @@ -396,6 +398,8 @@ int toScriptRenderTargetType(RenderTargetType type) { return 5; case RenderTargetType::SSAOBlur: return 6; + case RenderTargetType::SSR: + return 7; case RenderTargetType::Scene: default: return 0; diff --git a/runtime/scripts/atlas_graphics.js b/runtime/scripts/atlas_graphics.js index 8bac607d..fbba08b7 100644 --- a/runtime/scripts/atlas_graphics.js +++ b/runtime/scripts/atlas_graphics.js @@ -27,6 +27,7 @@ export const RenderTargetType = Object.freeze({ GBuffer: 4, SSAO: 5, SSAOBlur: 6, + SSR: 7, }); export const RenderPassType = Object.freeze({ @@ -247,7 +248,7 @@ export class DirectionalLight { return globalThis.__atlasUpdateDirectionalLight(this); } - castShadows(resolution = 4096) { + castShadows(resolution = 2048) { return globalThis.__atlasCastDirectionalLightShadows( this, resolution, diff --git a/shaders/metal/deferred/deferred.frag.metal b/shaders/metal/deferred/deferred.frag.metal index c1cd629c..c8cef9ea 100644 --- a/shaders/metal/deferred/deferred.frag.metal +++ b/shaders/metal/deferred/deferred.frag.metal @@ -405,6 +405,6 @@ fragment main0_out main0(main0_in in [[stage_in]], constant UBO& _46 [[buffer(0) a = float3(0.0); } out.gAlbedoSpec = float4(a, aoValue); - out.gMaterial = float4(metallicValue, roughnessValue, aoValue, 1.0); + out.gMaterial = float4(metallicValue, roughnessValue, aoValue, fast::clamp(material.reflectivity, 0.0, 1.0)); return out; } diff --git a/shaders/metal/deferred/light.frag.metal b/shaders/metal/deferred/light.frag.metal index a10f9aed..a59d533f 100644 --- a/shaders/metal/deferred/light.frag.metal +++ b/shaders/metal/deferred/light.frag.metal @@ -107,7 +107,7 @@ struct ShadowParameters { float bias0; int textureIndex; float farPlane; - float _pad1; + int lightIndex; float3 lightPos; int lightType; }; @@ -174,7 +174,7 @@ struct ShadowParameters_1 { float bias0; int textureIndex; float farPlane; - float _pad1; + int lightIndex; packed_float3 lightPos; int lightType; }; @@ -431,6 +431,9 @@ static inline __attribute__((always_inline)) float calculatePointShadow( } float3 fragToLight = fragPos - shadowParam.lightPos; float currentDepth = length(fragToLight); + if (currentDepth >= shadowParam.farPlane) { + return 0.0; + } float bias0 = 0.0500000007450580596923828125; float shadow = 0.0; float diskRadius = (1.0 + (currentDepth / shadowParam.farPlane)) * @@ -1207,13 +1210,19 @@ fragment main0_out main0( int shadowCount = _1355.shadowParamCount; for (int i = 0; i < shadowCount; i++) { if (_1372.shadowParams[i].lightType == 3) { + int lightIndex = _1372.shadowParams[i].lightIndex; + if (lightIndex < 0 || lightIndex >= _1355.pointLightCount || + distance(float3(_1465.pointLights[lightIndex].position), + FragPos) >= _1465.pointLights[lightIndex].radius) { + continue; + } ShadowParameters _1386; _1386.lightView = _1372.shadowParams[i].lightView; _1386.lightProjection = _1372.shadowParams[i].lightProjection; _1386.bias0 = _1372.shadowParams[i].bias0; _1386.textureIndex = _1372.shadowParams[i].textureIndex; _1386.farPlane = _1372.shadowParams[i].farPlane; - _1386._pad1 = _1372.shadowParams[i]._pad1; + _1386.lightIndex = _1372.shadowParams[i].lightIndex; _1386.lightPos = float3(_1372.shadowParams[i].lightPos); _1386.lightType = _1372.shadowParams[i].lightType; ShadowParameters param = _1386; @@ -1228,13 +1237,19 @@ fragment main0_out main0( cubeMap3Smplr, cubeMap4, cubeMap4Smplr, cubeMap5, cubeMap5Smplr)); } else if (_1372.shadowParams[i].lightType == 1) { + int lightIndex = _1372.shadowParams[i].lightIndex; + if (lightIndex < 0 || lightIndex >= _1355.spotlightCount || + distance(float3(_1510.spotlights[lightIndex].position), + FragPos) >= _1510.spotlights[lightIndex].range) { + continue; + } ShadowParameters _1397; _1397.lightView = _1372.shadowParams[i].lightView; _1397.lightProjection = _1372.shadowParams[i].lightProjection; _1397.bias0 = _1372.shadowParams[i].bias0; _1397.textureIndex = _1372.shadowParams[i].textureIndex; _1397.farPlane = _1372.shadowParams[i].farPlane; - _1397._pad1 = _1372.shadowParams[i]._pad1; + _1397.lightIndex = _1372.shadowParams[i].lightIndex; _1397.lightPos = float3(_1372.shadowParams[i].lightPos); _1397.lightType = _1372.shadowParams[i].lightType; ShadowParameters param_2 = _1397; @@ -1247,13 +1262,19 @@ fragment main0_out main0( texture3, texture3Smplr, texture4, texture4Smplr, texture5, texture5Smplr, _526)); } else if (_1372.shadowParams[i].lightType == 2) { + int lightIndex = _1372.shadowParams[i].lightIndex; + if (lightIndex < 0 || lightIndex >= _1355.areaLightCount || + distance(float3(_1552.areaLights[lightIndex].position), + FragPos) >= _1552.areaLights[lightIndex].range) { + continue; + } ShadowParameters _1397; _1397.lightView = _1372.shadowParams[i].lightView; _1397.lightProjection = _1372.shadowParams[i].lightProjection; _1397.bias0 = _1372.shadowParams[i].bias0; _1397.textureIndex = _1372.shadowParams[i].textureIndex; _1397.farPlane = _1372.shadowParams[i].farPlane; - _1397._pad1 = _1372.shadowParams[i]._pad1; + _1397.lightIndex = _1372.shadowParams[i].lightIndex; _1397.lightPos = float3(_1372.shadowParams[i].lightPos); _1397.lightType = _1372.shadowParams[i].lightType; ShadowParameters param_2 = _1397; @@ -1271,7 +1292,7 @@ fragment main0_out main0( _1397.bias0 = _1372.shadowParams[i].bias0; _1397.textureIndex = _1372.shadowParams[i].textureIndex; _1397.farPlane = _1372.shadowParams[i].farPlane; - _1397._pad1 = _1372.shadowParams[i]._pad1; + _1397.lightIndex = _1372.shadowParams[i].lightIndex; _1397.lightPos = float3(_1372.shadowParams[i].lightPos); _1397.lightType = _1372.shadowParams[i].lightType; ShadowParameters param_2 = _1397; diff --git a/shaders/metal/effects/ssr.frag.metal b/shaders/metal/effects/ssr.frag.metal index 720374e2..6fbe25b7 100644 --- a/shaders/metal/effects/ssr.frag.metal +++ b/shaders/metal/effects/ssr.frag.metal @@ -21,6 +21,8 @@ struct SSRParameters int steps; float thickness; float maxRoughness; + float historyWeight; + int debugMode; }; struct main0_out @@ -58,17 +60,17 @@ float3 sampleSkyReflection(thread const float3& normal, thread const float3& vie } static inline __attribute__((always_inline)) -float4 SSR(thread const float3& worldPos, thread const float3& normal, thread const float3& viewDir, thread const float& roughness, thread const float& metallic, thread const float3& albedo, constant Uniforms& _42, constant SSRParameters& _96, thread float4& gl_FragCoord, texture2d gPosition, sampler gPositionSmplr, texture2d sceneColor, sampler sceneColorSmplr, texture2d gNormal, sampler gNormalSmplr, texture2d gDepth, sampler gDepthSmplr, texturecube skybox, sampler skyboxSmplr) +float4 SSR(thread const float3& worldPos, thread const float3& normal, thread const float3& viewDir, thread const float& roughness, thread const float& metallic, thread const float& reflectivity, thread const float3& albedo, constant Uniforms& _42, constant SSRParameters& _96, thread float4& gl_FragCoord, texture2d gPosition, sampler gPositionSmplr, texture2d sceneColor, sampler sceneColorSmplr, texture2d gNormal, sampler gNormalSmplr, texture2d gDepth, sampler gDepthSmplr, texturecube skybox, sampler skyboxSmplr) { float3 viewPos = (_42.view * float4(worldPos, 1.0)).xyz; float3 viewNormal = fast::normalize((_42.view * float4(normal, 0.0)).xyz); - float mirrorFactor = fast::clamp(metallic * (1.0 - roughness), 0.0, 1.0); + float mirrorFactor = fast::clamp(fast::max(metallic, reflectivity) * (1.0 - roughness), 0.0, 1.0); float3 skyReflection = sampleSkyReflection(normal, viewDir, albedo, metallic, skybox, skyboxSmplr); float3 viewDirection = fast::normalize(viewPos); float3 viewReflect = fast::normalize(reflect(viewDirection, viewNormal)); if (viewReflect.z > 0.0) { - return float4(skyReflection, mirrorFactor); + return float4(0.0); } float3 rayOrigin = viewPos + (viewNormal * 0.00999999977648258209228515625); float3 rayDir = viewReflect; @@ -132,9 +134,13 @@ float4 SSR(thread const float3& worldPos, thread const float3& normal, thread co { break; } - float rawDepth = gDepth.sample(gDepthSmplr, screenUV).x; + float hierarchyLevel = fast::clamp(floor(log2(1.0 + float(i) * 0.25)), + 0.0, 5.0); + float rawDepth = gDepth.sample(gDepthSmplr, screenUV, + level(hierarchyLevel)).x; if (rawDepth >= 0.99989998340606689453125) { + currentPos += rayDir * stepSize * (exp2(hierarchyLevel) - 1.0); lastPos = currentPos; continue; } @@ -252,23 +258,9 @@ float4 SSR(thread const float3& worldPos, thread const float3& normal, thread co } if (!hit) { - float3 fallbackReflection = skyReflection; - if (hasFallbackUV) - { - float mipLevel = roughness * 5.0; - float3 screenFallback = sceneColor.sample(sceneColorSmplr, fallbackUV, level(mipLevel)).xyz; - float fallbackDepth = gDepth.sample(gDepthSmplr, fallbackUV).x; - float screenValidity = 1.0 - smoothstep(0.99800002574920654296875, 1.0, fallbackDepth); - float fallbackLuma = dot(screenFallback, float3(0.2125999927520751953125, 0.715200006961822509765625, 0.072200000286102294921875)); - screenValidity *= smoothstep(0.004999999888241291046142578125, 0.02999999932944774627685546875, fallbackLuma); - float tintStrength = metallic * 0.3499999940395355224609375; - float3 metalTint = mix(float3(1.0), albedo, float3(tintStrength)); - fallbackReflection = mix(skyReflection, screenFallback * metalTint, float3(screenValidity)); - } - return float4(fallbackReflection, mirrorFactor); + return float4(0.0); } - float mipLevel = roughness * 5.0; - float3 hitColor = sceneColor.sample(sceneColorSmplr, hitUV, level(mipLevel)).xyz; + float3 hitColor = sceneColor.sample(sceneColorSmplr, hitUV).xyz; float tintStrength = metallic * 0.3499999940395355224609375; float3 metalTint = mix(float3(1.0), albedo, float3(tintStrength)); hitColor *= metalTint; @@ -303,7 +295,7 @@ float4 SSR(thread const float3& worldPos, thread const float3& normal, thread co return float4(reflectionColor, finalFade); } -fragment main0_out main0(main0_in in [[stage_in]], constant Uniforms& _42 [[buffer(0)]], constant SSRParameters& _96 [[buffer(1)]], texture2d gPosition [[texture(0)]], texture2d sceneColor [[texture(1)]], texture2d gNormal [[texture(2)]], texture2d gAlbedoSpec [[texture(3)]], texture2d gMaterial [[texture(4)]], texture2d gDepth [[texture(5)]], texturecube skybox [[texture(6)]], sampler gPositionSmplr [[sampler(0)]], sampler sceneColorSmplr [[sampler(1)]], sampler gNormalSmplr [[sampler(2)]], sampler gAlbedoSpecSmplr [[sampler(3)]], sampler gMaterialSmplr [[sampler(4)]], sampler gDepthSmplr [[sampler(5)]], sampler skyboxSmplr [[sampler(6)]], float4 gl_FragCoord [[position]]) +fragment main0_out main0(main0_in in [[stage_in]], constant Uniforms& _42 [[buffer(0)]], constant SSRParameters& _96 [[buffer(1)]], texture2d gPosition [[texture(0)]], texture2d sceneColor [[texture(1)]], texture2d gNormal [[texture(2)]], texture2d gAlbedoSpec [[texture(3)]], texture2d gMaterial [[texture(4)]], texture2d gDepth [[texture(5)]], texturecube skybox [[texture(6)]], texture2d historyTexture [[texture(7)]], sampler gPositionSmplr [[sampler(0)]], sampler sceneColorSmplr [[sampler(1)]], sampler gNormalSmplr [[sampler(2)]], sampler gAlbedoSpecSmplr [[sampler(3)]], sampler gMaterialSmplr [[sampler(4)]], sampler gDepthSmplr [[sampler(5)]], sampler skyboxSmplr [[sampler(6)]], sampler historyTextureSmplr [[sampler(7)]], float4 gl_FragCoord [[position]]) { main0_out out = {}; float3 worldPos = gPosition.sample(gPositionSmplr, in.TexCoord).xyz; @@ -312,12 +304,13 @@ fragment main0_out main0(main0_in in [[stage_in]], constant Uniforms& _42 [[buff float4 material = gMaterial.sample(gMaterialSmplr, in.TexCoord); float metallic = material.x; float roughness = material.y; + float reflectivity = material.w; if (length(normal) < 0.001000000047497451305389404296875) { out.FragColor = float4(0.0); return out; } - if (roughness > _96.maxRoughness) + if (roughness > _96.maxRoughness || fast::max(metallic, reflectivity) < 0.02) { out.FragColor = float4(0.0); return out; @@ -328,8 +321,20 @@ fragment main0_out main0(main0_in in [[stage_in]], constant Uniforms& _42 [[buff float3 param_2 = viewDir; float param_3 = roughness; float param_4 = metallic; - float3 param_5 = albedo; - float4 reflection = SSR(param, param_1, param_2, param_3, param_4, param_5, _42, _96, gl_FragCoord, gPosition, gPositionSmplr, sceneColor, sceneColorSmplr, gNormal, gNormalSmplr, gDepth, gDepthSmplr, skybox, skyboxSmplr); + float param_5 = reflectivity; + float3 param_6 = albedo; + float4 reflection = SSR(param, param_1, param_2, param_3, param_4, param_5, param_6, _42, _96, gl_FragCoord, gPosition, gPositionSmplr, sceneColor, sceneColorSmplr, gNormal, gNormalSmplr, gDepth, gDepthSmplr, skybox, skyboxSmplr); + if (_96.debugMode != 0) + { + out.FragColor = float4(1.0 - reflection.w, reflection.w, 0.0, 1.0); + return out; + } + if (reflection.w > 0.0 && _96.historyWeight > 0.0) + { + float4 history = historyTexture.sample(historyTextureSmplr, in.TexCoord); + float validHistory = step(0.001, history.w); + reflection = mix(reflection, history, _96.historyWeight * validHistory * reflection.w); + } out.FragColor = reflection; return out; } diff --git a/shaders/metal/path_tracing/path.metal b/shaders/metal/path_tracing/path.metal index 7d75c23b..99de74a6 100644 --- a/shaders/metal/path_tracing/path.metal +++ b/shaders/metal/path_tracing/path.metal @@ -5,6 +5,7 @@ using namespace raytracing; struct CameraUniforms { float4x4 invViewProj; + float4x4 prevViewProj; float3 camPos; float _pad0; }; @@ -436,6 +437,26 @@ float4 sampleMaterialTexture(int textureIndex, float2 uv, return float4(0.0); } +struct MaterialTextureArguments { + array, 256> textures [[id(0)]]; +}; + +float4 sampleMaterialTexture( + int textureIndex, float2 uv, + constant MaterialTextureArguments &materialTextureArguments) { + return materialTextureArguments.textures[textureIndex].sample( + materialTexSampler, uv); +} + +#undef PT_MATERIAL_TEXTURE_PARAMS +#undef PT_MATERIAL_TEXTURE_ARGS +#undef PT_MATERIAL_TEXTURE_BINDINGS +#define PT_MATERIAL_TEXTURE_PARAMS \ + constant MaterialTextureArguments &materialTextureArguments +#define PT_MATERIAL_TEXTURE_ARGS materialTextureArguments +#define PT_MATERIAL_TEXTURE_BINDINGS \ + constant MaterialTextureArguments &materialTextureArguments [[buffer(12)]] + void resolveMaterialParameters(Material mat, float2 uv, uint textureCount, PT_MATERIAL_TEXTURE_PARAMS, thread float3 &albedo, thread float &metallic, @@ -861,7 +882,14 @@ float3 sampleRadiance(uint2 gid, uint sampleIndex, uint w, constant PointLight *pointLights, constant SpotLight *spotLights, constant AreaLight *areaLights, - PT_MATERIAL_TEXTURE_PARAMS, texturecube skybox) { + PT_MATERIAL_TEXTURE_PARAMS, texturecube skybox, + thread float3 &primaryAlbedo, + thread float3 &primaryNormal, + thread float3 &primaryPosition, + thread float &primaryDepth, + thread float &primaryRoughness, + thread float &primaryHitDistance, + thread uint &primaryObjectId) { uint rng = seedBase(gid, w, sceneData.frameIndex, sampleIndex); ray surfaceRay = primaryRay; @@ -955,6 +983,13 @@ float3 sampleRadiance(uint2 gid, uint sampleIndex, uint w, resolveMaterialParameters(mat, texUV, sceneData.materialTextureCount, PT_MATERIAL_TEXTURE_ARGS, albedo, metallic, roughness, ao, emissive, ior, transmittance); + primaryAlbedo = albedo; + primaryNormal = N; + primaryPosition = P; + primaryDepth = length(P - primaryRay.origin); + primaryRoughness = roughness; + primaryHitDistance = hit.distance; + primaryObjectId = hit.instance_id; float reflectivity = clamp(mat.reflectivity, 0.0, 1.0); float sssStrength = clamp(1.0 - mat.albedo.w, 0.0, 1.0) * (1.0 - metallic); float sssThickness = mix(0.25, 1.75, ao); @@ -1213,6 +1248,11 @@ float3 sampleRadiance(uint2 gid, uint sampleIndex, uint w, isect, sceneAS, bP, bN, bV, bAlbedo, bMetallic, bRoughness, bIor, bTransmittance, bSssStrength, bSssThickness, rng, dirLight, sceneData, pointLights, spotLights, areaLights); + if (choseTransmission) { + float causticFocus = mix(1.0, 4.0, + transmittance * (1.0 - roughness)); + bounceDirect *= causticFocus; + } float3 bAmbient = bAlbedo * max(sceneData.ambientIntensity, 0.0) * (1.0 - bMetallic) * bAo; @@ -1234,8 +1274,13 @@ float3 sampleRadiance(uint2 gid, uint sampleIndex, uint w, } kernel void main0(texture2d outTex [[texture(0)]], - texture2d prevTex [[texture(1)]], + texture2d historyTex [[texture(1)]], texture2d brightTex [[texture(2)]], + texture2d albedoRoughnessTex [[texture(3)]], + texture2d normalDepthTex [[texture(4)]], + texture2d motionObjectTex [[texture(5)]], + texture2d momentsHitTex [[texture(6)]], + texture2d historyGuideTex [[texture(7)]], instance_acceleration_structure sceneAS [[buffer(0)]], constant CameraUniforms &cam [[buffer(1)]], constant Material *materials [[buffer(2)]], @@ -1272,6 +1317,13 @@ kernel void main0(texture2d outTex [[texture(0)]], isect.set_triangle_cull_mode(triangle_cull_mode::none); float3 color = float3(0.0); + float3 primaryAlbedo = float3(0.0); + float3 primaryNormal = float3(0.0); + float3 primaryPosition = float3(0.0); + float primaryDepth = 0.0; + float primaryRoughness = 1.0; + float primaryHitDistance = 0.0; + uint primaryObjectId = 0xFFFFFFFFu; uint spp = max(sceneData.raysPerPixel, 1u); for (uint s = 0; s < spp; ++s) { @@ -1284,7 +1336,9 @@ kernel void main0(texture2d outTex [[texture(0)]], float3 sample = sampleRadiance( gid, s, w, isect, sceneAS, primaryRay, materials, meshData, vertices, indices, instanceData, dirLight, sceneData, pointLights, - spotLights, areaLights, PT_MATERIAL_TEXTURE_ARGS, skybox); + spotLights, areaLights, PT_MATERIAL_TEXTURE_ARGS, skybox, + primaryAlbedo, primaryNormal, primaryPosition, primaryDepth, + primaryRoughness, primaryHitDistance, primaryObjectId); color += clampLuminance(sample, 24.0); } @@ -1292,9 +1346,22 @@ kernel void main0(texture2d outTex [[texture(0)]], int frameIndex = int(sceneData.frameIndex); - float4 prevColor = prevTex.read(gid); + float4 prevColor = historyTex.read(gid); + float4 previousGuide = historyGuideTex.read(gid); + float objectIdValue = primaryObjectId == 0xFFFFFFFFu + ? -1.0 + : float(primaryObjectId); + float4 currentGuide = + float4(primaryNormal.xy, primaryDepth, objectIdValue); + bool historyValid = frameIndex > 0 && + abs(previousGuide.z - primaryDepth) < + max(0.05, primaryDepth * 0.02) && + distance(previousGuide.xy, primaryNormal.xy) < 0.12 && + abs(previousGuide.w - objectIdValue) < 0.5; if (frameIndex == 0) prevColor = float4(0, 0, 0, 1); + if (!historyValid) + prevColor = float4(color, 1.0); if (frameIndex > 2) { float prevL = luminance(prevColor.xyz); @@ -1305,7 +1372,12 @@ kernel void main0(texture2d outTex [[texture(0)]], } } - float3 accum = (prevColor.xyz * frameIndex + color) / (frameIndex + 1); + float historyLength = historyValid ? min(float(frameIndex), 31.0) : 0.0; + float3 lower = min(prevColor.xyz, color) - float3(0.35); + float3 upper = max(prevColor.xyz, color) + float3(0.35); + float3 clippedHistory = clamp(prevColor.xyz, lower, upper); + float3 accum = mix(color, clippedHistory, + historyLength / (historyLength + 1.0)); accum = clampLuminance(accum, 24.0); constexpr float bloomThreshold = 1.0; @@ -1325,7 +1397,19 @@ kernel void main0(texture2d outTex [[texture(0)]], max(brightness, 0.00001); float3 brightColor = accum * contribution; - + float4 previousClip = cam.prevViewProj * float4(primaryPosition, 1.0); + float2 previousUv = previousClip.xy / max(abs(previousClip.w), 0.0001); + previousUv = previousUv * 0.5 + 0.5; + float2 motion = uv - previousUv; + float moment = luminance(color); + + historyTex.write(float4(accum, 1.0), gid); + historyGuideTex.write(currentGuide, gid); + albedoRoughnessTex.write(float4(primaryAlbedo, primaryRoughness), gid); + normalDepthTex.write(float4(primaryNormal, primaryDepth), gid); + motionObjectTex.write(float4(motion, objectIdValue, 1.0), gid); + momentsHitTex.write(float4(moment, moment * moment, + primaryRoughness, primaryHitDistance), gid); outTex.write(float4(accum, 1.0), gid); brightTex.write(float4(brightColor, 1.0), gid); } diff --git a/shaders/metal/path_tracing/path_denoise.metal b/shaders/metal/path_tracing/path_denoise.metal new file mode 100644 index 00000000..05bbd952 --- /dev/null +++ b/shaders/metal/path_tracing/path_denoise.metal @@ -0,0 +1,48 @@ +#include +using namespace metal; + +struct DenoiseParameters { + int stepWidth; +}; + +kernel void main0(texture2d inputTexture [[texture(0)]], + texture2d outputTexture [[texture(1)]], + texture2d brightTexture [[texture(2)]], + texture2d guideTexture [[texture(3)]], + constant DenoiseParameters ¶meters [[buffer(0)]], + uint2 gid [[thread_position_in_grid]]) { + uint width = outputTexture.get_width(); + uint height = outputTexture.get_height(); + if (gid.x >= width || gid.y >= height) return; + + constexpr int2 offsets[9] = { + int2(0, 0), int2(1, 0), int2(-1, 0), int2(0, 1), int2(0, -1), + int2(1, 1), int2(-1, 1), int2(1, -1), int2(-1, -1)}; + constexpr float weights[9] = {0.28, 0.12, 0.12, 0.12, 0.12, + 0.06, 0.06, 0.06, 0.06}; + float3 center = inputTexture.read(gid).xyz; + float4 centerGuide = guideTexture.read(gid); + float centerLuminance = dot(center, float3(0.2126, 0.7152, 0.0722)); + float3 filtered = float3(0.0); + float totalWeight = 0.0; + for (int i = 0; i < 9; ++i) { + int2 samplePosition = clamp(int2(gid) + offsets[i] * parameters.stepWidth, + int2(0), int2(width - 1, height - 1)); + float3 sampleColor = inputTexture.read(uint2(samplePosition)).xyz; + float4 sampleGuide = guideTexture.read(uint2(samplePosition)); + float sampleLuminance = dot(sampleColor, float3(0.2126, 0.7152, 0.0722)); + float edgeWeight = exp(-abs(sampleLuminance - centerLuminance) * 6.0); + float normalWeight = + pow(max(dot(centerGuide.xyz, sampleGuide.xyz), 0.0), 24.0); + float depthWeight = exp(-abs(sampleGuide.w - centerGuide.w) / + max(0.05, centerGuide.w * 0.02)); + float weight = weights[i] * edgeWeight * normalWeight * depthWeight; + filtered += sampleColor * weight; + totalWeight += weight; + } + float3 result = filtered / max(totalWeight, 0.0001); + float brightness = dot(result, float3(0.2126, 0.7152, 0.0722)); + float contribution = smoothstep(0.5, 1.5, brightness); + outputTexture.write(float4(result, 1.0), gid); + brightTexture.write(float4(result * contribution, 1.0), gid); +} diff --git a/shaders/opengl/deferred/deferred.frag b/shaders/opengl/deferred/deferred.frag index e90a683c..83a45aa1 100644 --- a/shaders/opengl/deferred/deferred.frag +++ b/shaders/opengl/deferred/deferred.frag @@ -216,5 +216,5 @@ void main() { } gAlbedoSpec = vec4(a, ao); - gMaterial = vec4(metallic, roughness, ao, 1.0); + gMaterial = vec4(metallic, roughness, ao, clamp(material.reflectivity, 0.0, 1.0)); } diff --git a/shaders/opengl/deferred/light.frag b/shaders/opengl/deferred/light.frag index 201d05b0..26901ff5 100644 --- a/shaders/opengl/deferred/light.frag +++ b/shaders/opengl/deferred/light.frag @@ -75,6 +75,7 @@ struct ShadowParameters { float bias; int textureIndex; float farPlane; + int lightIndex; vec3 lightPos; int lightType; }; @@ -194,17 +195,16 @@ float calculateShadow(ShadowParameters shadowParam, vec3 fragPos, vec3 normal) { float desiredKernel = mix(1.0, 1.5, distFactor) * resFactor; int kernelSize = int(clamp(floor(desiredKernel + 0.5), 1.0, 2.0)); - const vec2 poissonDisk[12] = vec2[]( + const vec2 poissonDisk[8] = vec2[]( vec2(-0.326, -0.406), vec2(-0.840, -0.074), vec2(-0.696, 0.457), vec2(-0.203, 0.621), vec2(0.962, -0.195), vec2(0.473, -0.480), - vec2(0.519, 0.767), vec2(0.185, -0.893), vec2(0.507, 0.064), - vec2(0.896, 0.412), vec2(-0.322, -0.933), vec2(-0.792, -0.598) + vec2(0.519, 0.767), vec2(0.185, -0.893) ); float texelRadius = mix(1.0, 3.0, distFactor) * resFactor; vec2 filterRadius = texelSize * texelRadius; int sampleCount = 0; - for (int i = 0; i < 12; ++i) { + for (int i = 0; i < 8; ++i) { vec2 offset = poissonDisk[i] * filterRadius; vec2 uv = projCoords.xy + offset; if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) { @@ -224,12 +224,13 @@ float calculateShadow(ShadowParameters shadowParam, vec3 fragPos, vec3 normal) { float calculatePointShadow(ShadowParameters shadowParam, vec3 fragPos) { vec3 fragToLight = fragPos - shadowParam.lightPos; float currentDepth = length(fragToLight); + if (currentDepth >= shadowParam.farPlane) return 0.0; float bias = 0.05; float shadow = 0.0; float diskRadius = (1.0 + (currentDepth / shadowParam.farPlane)) * 0.05; - const int samples = 20; + const int samples = 8; const vec3 sampleOffsetDirections[] = vec3[]( vec3(0.5381, 0.1856, -0.4319), vec3(0.1379, 0.2486, 0.4430), vec3(0.3371, 0.5679, -0.0057), vec3(-0.6999, -0.0451, -0.0019), @@ -255,6 +256,18 @@ float calculatePointShadow(ShadowParameters shadowParam, vec3 fragPos) { return shadow; } +float shadowForLight(int lightType, int lightIndex, vec3 fragPos, vec3 normal) { + for (int i = 0; i < shadowParamCount; ++i) { + if (shadowParams[i].lightType != lightType || + shadowParams[i].lightIndex != lightIndex) continue; + float shadow = lightType == 3 + ? calculatePointShadow(shadowParams[i], fragPos) + : calculateShadow(shadowParams[i], fragPos, normal); + return clamp(shadow * 0.85, 0.0, 1.0); + } + return 0.0; +} + vec3 evaluateBRDF(vec3 L, vec3 radiance, vec3 N, vec3 V, vec3 F0, vec3 albedo, float metallic, float roughness) { vec3 H = normalize(V + L); @@ -372,43 +385,25 @@ void main() { float occlusion = clamp(ao * (0.2 + 0.8 * ssaoDesaturated), 0.0, 1.0); float lightingOcclusion = clamp(ssaoDesaturated, 0.2, 1.0); - float directionalShadow = 0.0; - float spotShadow = 0.0; - float areaShadow = 0.0; - float pointShadow = 0.0; - for (int i = 0; i < shadowParamCount; ++i) { - if (shadowParams[i].lightType == 3) { - pointShadow = max(pointShadow, calculatePointShadow(shadowParams[i], FragPos)); - } else if (shadowParams[i].lightType == 1) { - spotShadow = max(spotShadow, calculateShadow(shadowParams[i], FragPos, N)); - } else if (shadowParams[i].lightType == 2) { - areaShadow = max(areaShadow, calculateShadow(shadowParams[i], FragPos, N)); - } else { - directionalShadow = max(directionalShadow, calculateShadow(shadowParams[i], FragPos, N)); - } - } - directionalShadow = clamp(directionalShadow * 0.85, 0.0, 1.0); - spotShadow = clamp(spotShadow * 0.85, 0.0, 1.0); - areaShadow = clamp(areaShadow * 0.85, 0.0, 1.0); - pointShadow = clamp(pointShadow * 0.85, 0.0, 1.0); - vec3 directionalResult = vec3(0.0); for (int i = 0; i < directionalLightCount; ++i) { - directionalResult += calcDirectionalLight(directionalLights[i], N, V, F0, albedo, metallic, roughness); + float shadow = shadowForLight(0, i, FragPos, N); + directionalResult += calcDirectionalLight(directionalLights[i], N, V, F0, albedo, metallic, roughness) * (1.0 - shadow); } - directionalResult *= (1.0 - directionalShadow); vec3 pointResult = vec3(0.0); for (int i = 0; i < pointLightCount; ++i) { - pointResult += calcPointLight(pointLights[i], FragPos, N, V, F0, albedo, metallic, roughness); + if (length(pointLights[i].position - FragPos) >= pointLights[i].radius) continue; + float shadow = shadowForLight(3, i, FragPos, N); + pointResult += calcPointLight(pointLights[i], FragPos, N, V, F0, albedo, metallic, roughness) * (1.0 - shadow); } - pointResult *= (1.0 - pointShadow); vec3 spotResult = vec3(0.0); for (int i = 0; i < spotlightCount; ++i) { - spotResult += calcSpotLight(spotlights[i], FragPos, N, V, F0, albedo, metallic, roughness); + if (length(spotlights[i].position - FragPos) >= spotlights[i].range) continue; + float shadow = shadowForLight(1, i, FragPos, N); + spotResult += calcSpotLight(spotlights[i], FragPos, N, V, F0, albedo, metallic, roughness) * (1.0 - shadow); } - spotResult *= (1.0 - spotShadow); vec3 areaResult = vec3(0.0); for (int i = 0; i < areaLightCount; ++i) { @@ -437,12 +432,11 @@ void main() { float attenuation = 1.0 / (1.0 + (dist / range) + (dist * dist) / (range * range)); float fade = 1.0 - smoothstep(range * 0.9, range, dist); vec3 radiance = areaLights[i].diffuse * max(areaLights[i].intensity, 0.0) * attenuation * facing * fade; - areaResult += evaluateBRDF(L, radiance, N, V, F0, albedo, metallic, roughness); + float shadow = shadowForLight(2, i, FragPos, N); + areaResult += evaluateBRDF(L, radiance, N, V, F0, albedo, metallic, roughness) * (1.0 - shadow); } } } - areaResult *= (1.0 - areaShadow); - vec3 rimResult = getRimLight(FragPos, N, V, F0, albedo, metallic, roughness); vec3 lighting = (directionalResult + pointResult + spotResult + areaResult + rimResult) * lightingOcclusion; diff --git a/shaders/opengl/effects/ssr.frag b/shaders/opengl/effects/ssr.frag index 3a9c527b..ec4ad15c 100644 --- a/shaders/opengl/effects/ssr.frag +++ b/shaders/opengl/effects/ssr.frag @@ -9,6 +9,7 @@ uniform sampler2D gAlbedoSpec; uniform sampler2D gMaterial; uniform sampler2D sceneColor; uniform sampler2D gDepth; +uniform sampler2D historyTexture; uniform samplerCube skybox; uniform mat4 projection; @@ -22,6 +23,8 @@ uniform float resolution = 0.5; uniform int steps = 64; uniform float thickness = 2.0; uniform float maxRoughness = 0.5; +uniform float historyWeight = 0.0; +uniform int debugMode = 0; const float PI = 3.14159265359; @@ -55,17 +58,17 @@ vec3 sampleSkyReflection(vec3 normal, vec3 viewDir, vec3 albedo, float metallic) return skyColor * tint; } -vec4 SSR(vec3 worldPos, vec3 normal, vec3 viewDir, float roughness, float metallic, vec3 albedo) { +vec4 SSR(vec3 worldPos, vec3 normal, vec3 viewDir, float roughness, float metallic, float reflectivity, vec3 albedo) { vec3 viewPos = (view * vec4(worldPos, 1.0)).xyz; vec3 viewNormal = normalize((view * vec4(normal, 0.0)).xyz); - float mirrorFactor = clamp(metallic * (1.0 - roughness), 0.0, 1.0); + float mirrorFactor = clamp(max(metallic, reflectivity) * (1.0 - roughness), 0.0, 1.0); vec3 skyReflection = sampleSkyReflection(normal, viewDir, albedo, metallic); vec3 viewDirection = normalize(viewPos); vec3 viewReflect = normalize(reflect(viewDirection, viewNormal)); if (viewReflect.z > 0.0) { - return vec4(skyReflection, mirrorFactor); + return vec4(0.0); } vec3 rayOrigin = viewPos + viewNormal * 0.01; @@ -102,8 +105,11 @@ vec4 SSR(vec3 worldPos, vec3 normal, vec3 viewDir, float roughness, float metall break; } - float rawDepth = texture(gDepth, screenUV).r; + float hierarchyLevel = clamp(floor(log2(1.0 + float(i) * 0.25)), + 0.0, 5.0); + float rawDepth = textureLod(gDepth, screenUV, hierarchyLevel).r; if (rawDepth >= 0.9999) { + currentPos += rayDir * stepSize * (exp2(hierarchyLevel) - 1.0); lastPos = currentPos; continue; } @@ -175,24 +181,10 @@ vec4 SSR(vec3 worldPos, vec3 normal, vec3 viewDir, float roughness, float metall } if (!hit) { - vec3 fallbackReflection = skyReflection; - if (hasFallbackUV) { - float mipLevel = roughness * 5.0; - vec3 screenFallback = textureLod(sceneColor, fallbackUV, mipLevel).rgb; - float fallbackDepth = texture(gDepth, fallbackUV).r; - float screenValidity = 1.0 - smoothstep(0.998, 1.0, fallbackDepth); - float fallbackLuma = dot(screenFallback, vec3(0.2126, 0.7152, 0.0722)); - screenValidity *= smoothstep(0.005, 0.03, fallbackLuma); - float tintStrength = metallic * 0.35; - vec3 metalTint = mix(vec3(1.0), albedo, tintStrength); - fallbackReflection = mix( - skyReflection, screenFallback * metalTint, screenValidity); - } - return vec4(fallbackReflection, mirrorFactor); + return vec4(0.0); } - float mipLevel = roughness * 5.0; - vec3 hitColor = textureLod(sceneColor, hitUV, mipLevel).rgb; + vec3 hitColor = texture(sceneColor, hitUV).rgb; float tintStrength = metallic * 0.35; vec3 metalTint = mix(vec3(1.0), albedo, tintStrength); hitColor *= metalTint; @@ -241,20 +233,31 @@ void main() { float metallic = material.r; float roughness = material.g; + float reflectivity = material.a; if (length(normal) < 0.001) { FragColor = vec4(0.0, 0.0, 0.0, 0.0); return; } - if (roughness > maxRoughness) { + if (roughness > maxRoughness || max(metallic, reflectivity) < 0.02) { FragColor = vec4(0.0, 0.0, 0.0, 0.0); return; } vec3 viewDir = normalize(cameraPosition - worldPos); - vec4 reflection = SSR(worldPos, normal, viewDir, roughness, metallic, albedo); + vec4 reflection = SSR(worldPos, normal, viewDir, roughness, metallic, reflectivity, albedo); + if (debugMode != 0) { + FragColor = vec4(1.0 - reflection.a, reflection.a, 0.0, 1.0); + return; + } + if (reflection.a > 0.0 && historyWeight > 0.0) { + vec4 history = texture(historyTexture, TexCoord); + float validHistory = step(0.001, history.a); + reflection = mix(reflection, history, + historyWeight * validHistory * reflection.a); + } FragColor = reflection; } diff --git a/shaders/vulkan/deferred/deferred.frag b/shaders/vulkan/deferred/deferred.frag index 0be51cc7..3f882505 100644 --- a/shaders/vulkan/deferred/deferred.frag +++ b/shaders/vulkan/deferred/deferred.frag @@ -228,5 +228,5 @@ void main() { } gAlbedoSpec = vec4(a, aoValue); - gMaterial = vec4(metallicValue, roughnessValue, aoValue, 1.0); + gMaterial = vec4(metallicValue, roughnessValue, aoValue, clamp(material.reflectivity, 0.0, 1.0)); } diff --git a/shaders/vulkan/deferred/light.frag b/shaders/vulkan/deferred/light.frag index 2f4c6a3c..6d1fc011 100644 --- a/shaders/vulkan/deferred/light.frag +++ b/shaders/vulkan/deferred/light.frag @@ -87,7 +87,7 @@ struct ShadowParameters { float bias; int textureIndex; float farPlane; - float _pad1; + int lightIndex; vec3 lightPos; int lightType; }; @@ -239,17 +239,16 @@ float calculateShadow(ShadowParameters shadowParam, vec3 fragPos, vec3 normal) { float desiredKernel = mix(1.0, 1.5, distFactor) * resFactor; int kernelSize = int(clamp(floor(desiredKernel + 0.5), 1.0, 2.0)); - const vec2 poissonDisk[12] = vec2[]( + const vec2 poissonDisk[8] = vec2[]( vec2(-0.326, -0.406), vec2(-0.840, -0.074), vec2(-0.696, 0.457), vec2(-0.203, 0.621), vec2(0.962, -0.195), vec2(0.473, -0.480), - vec2(0.519, 0.767), vec2(0.185, -0.893), vec2(0.507, 0.064), - vec2(0.896, 0.412), vec2(-0.322, -0.933), vec2(-0.792, -0.598) + vec2(0.519, 0.767), vec2(0.185, -0.893) ); float texelRadius = mix(1.0, 3.0, distFactor) * resFactor; vec2 filterRadius = texelSize * texelRadius; int sampleCount = 0; - for (int i = 0; i < 12; ++i) { + for (int i = 0; i < 8; ++i) { vec2 offset = poissonDisk[i] * filterRadius; vec2 uv = projCoords.xy + offset; if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) { @@ -273,12 +272,13 @@ float calculatePointShadow(ShadowParameters shadowParam, vec3 fragPos) { } vec3 fragToLight = fragPos - shadowParam.lightPos; float currentDepth = length(fragToLight); + if (currentDepth >= shadowParam.farPlane) return 0.0; float bias = 0.05; float shadow = 0.0; float diskRadius = (1.0 + (currentDepth / shadowParam.farPlane)) * 0.05; - const int samples = 20; + const int samples = 8; const vec3 sampleOffsetDirections[] = vec3[]( vec3(0.5381, 0.1856, -0.4319), vec3(0.1379, 0.2486, 0.4430), vec3(0.3371, 0.5679, -0.0057), vec3(-0.6999, -0.0451, -0.0019), @@ -304,6 +304,18 @@ float calculatePointShadow(ShadowParameters shadowParam, vec3 fragPos) { return shadow; } +float shadowForLight(int lightType, int lightIndex, vec3 fragPos, vec3 normal) { + for (int i = 0; i < shadowParamCount; ++i) { + if (shadowParams[i].lightType != lightType || + shadowParams[i].lightIndex != lightIndex) continue; + float shadow = lightType == 3 + ? calculatePointShadow(shadowParams[i], fragPos) + : calculateShadow(shadowParams[i], fragPos, normal); + return clamp(shadow * 0.85, 0.0, 1.0); + } + return 0.0; +} + vec3 evaluateBRDF(vec3 L, vec3 radiance, vec3 N, vec3 V, vec3 F0, vec3 albedo, float metallic, float roughness) { vec3 H = normalize(V + L); @@ -421,45 +433,25 @@ void main() { float occlusion = clamp(ao * (0.2 + 0.8 * ssaoDesaturated), 0.0, 1.0); float lightingOcclusion = clamp(ssaoDesaturated, 0.25, 1.0); - float directionalShadow = 0.0; - float spotShadow = 0.0; - float areaShadow = 0.0; - float pointShadow = 0.0; - - int shadowCount = shadowParamCount; - for (int i = 0; i < shadowCount; ++i) { - if (shadowParams[i].lightType == 3) { - pointShadow = max(pointShadow, calculatePointShadow(shadowParams[i], FragPos)); - } else if (shadowParams[i].lightType == 1) { - spotShadow = max(spotShadow, calculateShadow(shadowParams[i], FragPos, N)); - } else if (shadowParams[i].lightType == 2) { - areaShadow = max(areaShadow, calculateShadow(shadowParams[i], FragPos, N)); - } else { - directionalShadow = max(directionalShadow, calculateShadow(shadowParams[i], FragPos, N)); - } - } - directionalShadow = clamp(directionalShadow * 0.85, 0.0, 1.0); - spotShadow = clamp(spotShadow * 0.85, 0.0, 1.0); - areaShadow = clamp(areaShadow * 0.85, 0.0, 1.0); - pointShadow = clamp(pointShadow * 0.85, 0.0, 1.0); - vec3 directionalResult = vec3(0.0); for (int i = 0; i < directionalLightCount; ++i) { - directionalResult += calcDirectionalLight(directionalLights[i], N, V, F0, albedo, metallic, roughness); + float shadow = shadowForLight(0, i, FragPos, N); + directionalResult += calcDirectionalLight(directionalLights[i], N, V, F0, albedo, metallic, roughness) * (1.0 - shadow); } - directionalResult *= (1.0 - directionalShadow); vec3 pointResult = vec3(0.0); for (int i = 0; i < pointLightCount; ++i) { - pointResult += calcPointLight(pointLights[i], FragPos, N, V, F0, albedo, metallic, roughness); + if (length(pointLights[i].position - FragPos) >= pointLights[i].radius) continue; + float shadow = shadowForLight(3, i, FragPos, N); + pointResult += calcPointLight(pointLights[i], FragPos, N, V, F0, albedo, metallic, roughness) * (1.0 - shadow); } - pointResult *= (1.0 - pointShadow); vec3 spotResult = vec3(0.0); for (int i = 0; i < spotlightCount; ++i) { - spotResult += calcSpotLight(spotlights[i], FragPos, N, V, F0, albedo, metallic, roughness); + if (length(spotlights[i].position - FragPos) >= spotlights[i].range) continue; + float shadow = shadowForLight(1, i, FragPos, N); + spotResult += calcSpotLight(spotlights[i], FragPos, N, V, F0, albedo, metallic, roughness) * (1.0 - shadow); } - spotResult *= (1.0 - spotShadow); vec3 areaResult = vec3(0.0); for (int i = 0; i < areaLightCount; ++i) { @@ -488,12 +480,11 @@ void main() { float attenuation = 1.0 / (1.0 + (dist / range) + (dist * dist) / (range * range)); float fade = 1.0 - smoothstep(range * 0.9, range, dist); vec3 radiance = areaLights[i].diffuse * max(areaLights[i].intensity, 0.0) * attenuation * facing * fade; - areaResult += evaluateBRDF(L, radiance, N, V, F0, albedo, metallic, roughness); + float shadow = shadowForLight(2, i, FragPos, N); + areaResult += evaluateBRDF(L, radiance, N, V, F0, albedo, metallic, roughness) * (1.0 - shadow); } } } - areaResult *= (1.0 - areaShadow); - vec3 rimResult = getRimLight(FragPos, N, V, F0, albedo, metallic, roughness); vec3 lighting = (directionalResult + pointResult + spotResult + areaResult + rimResult) * lightingOcclusion; diff --git a/shaders/vulkan/effects/ssr.frag b/shaders/vulkan/effects/ssr.frag index b76d05d9..24034f79 100644 --- a/shaders/vulkan/effects/ssr.frag +++ b/shaders/vulkan/effects/ssr.frag @@ -9,6 +9,7 @@ layout(set = 2, binding = 2) uniform sampler2D gAlbedoSpec; layout(set = 2, binding = 3) uniform sampler2D gMaterial; layout(set = 2, binding = 4) uniform sampler2D sceneColor; layout(set = 2, binding = 5) uniform sampler2D gDepth; +layout(set = 2, binding = 6) uniform sampler2D historyTexture; layout(set = 3, binding = 0) uniform samplerCube skybox; layout(set = 1, binding = 0) uniform Uniforms { @@ -25,6 +26,8 @@ layout(set = 1, binding = 1) uniform SSRParameters { int steps; float thickness; float maxRoughness; + float historyWeight; + int debugMode; }; const float PI = 3.14159265359; @@ -59,17 +62,17 @@ vec3 sampleSkyReflection(vec3 normal, vec3 viewDir, vec3 albedo, float metallic) return skyColor * tint; } -vec4 SSR(vec3 worldPos, vec3 normal, vec3 viewDir, float roughness, float metallic, vec3 albedo) { +vec4 SSR(vec3 worldPos, vec3 normal, vec3 viewDir, float roughness, float metallic, float reflectivity, vec3 albedo) { vec3 viewPos = (view * vec4(worldPos, 1.0)).xyz; vec3 viewNormal = normalize((view * vec4(normal, 0.0)).xyz); - float mirrorFactor = clamp(metallic * (1.0 - roughness), 0.0, 1.0); + float mirrorFactor = clamp(max(metallic, reflectivity) * (1.0 - roughness), 0.0, 1.0); vec3 skyReflection = sampleSkyReflection(normal, viewDir, albedo, metallic); vec3 viewDirection = normalize(viewPos); vec3 viewReflect = normalize(reflect(viewDirection, viewNormal)); if (viewReflect.z > 0.0) { - return vec4(skyReflection, mirrorFactor); + return vec4(0.0); } vec3 rayOrigin = viewPos + viewNormal * 0.01; @@ -106,8 +109,10 @@ vec4 SSR(vec3 worldPos, vec3 normal, vec3 viewDir, float roughness, float metall break; } + float hierarchyLevel = 0.0; float rawDepth = texture(gDepth, screenUV).r; if (rawDepth >= 0.9999) { + currentPos += rayDir * stepSize * (exp2(hierarchyLevel) - 1.0); lastPos = currentPos; continue; } @@ -179,24 +184,10 @@ vec4 SSR(vec3 worldPos, vec3 normal, vec3 viewDir, float roughness, float metall } if (!hit) { - vec3 fallbackReflection = skyReflection; - if (hasFallbackUV) { - float mipLevel = roughness * 5.0; - vec3 screenFallback = textureLod(sceneColor, fallbackUV, mipLevel).rgb; - float fallbackDepth = texture(gDepth, fallbackUV).r; - float screenValidity = 1.0 - smoothstep(0.998, 1.0, fallbackDepth); - float fallbackLuma = dot(screenFallback, vec3(0.2126, 0.7152, 0.0722)); - screenValidity *= smoothstep(0.005, 0.03, fallbackLuma); - float tintStrength = metallic * 0.35; - vec3 metalTint = mix(vec3(1.0), albedo, tintStrength); - fallbackReflection = mix( - skyReflection, screenFallback * metalTint, screenValidity); - } - return vec4(fallbackReflection, mirrorFactor); + return vec4(0.0); } - float mipLevel = roughness * 5.0; - vec3 hitColor = textureLod(sceneColor, hitUV, mipLevel).rgb; + vec3 hitColor = texture(sceneColor, hitUV).rgb; float tintStrength = metallic * 0.35; vec3 metalTint = mix(vec3(1.0), albedo, tintStrength); hitColor *= metalTint; @@ -245,20 +236,31 @@ void main() { float metallic = material.r; float roughness = material.g; + float reflectivity = material.a; if (length(normal) < 0.001) { FragColor = vec4(0.0, 0.0, 0.0, 0.0); return; } - if (roughness > maxRoughness) { + if (roughness > maxRoughness || max(metallic, reflectivity) < 0.02) { FragColor = vec4(0.0, 0.0, 0.0, 0.0); return; } vec3 viewDir = normalize(cameraPosition - worldPos); - vec4 reflection = SSR(worldPos, normal, viewDir, roughness, metallic, albedo); + vec4 reflection = SSR(worldPos, normal, viewDir, roughness, metallic, reflectivity, albedo); + if (debugMode != 0) { + FragColor = vec4(1.0 - reflection.a, reflection.a, 0.0, 1.0); + return; + } + if (reflection.a > 0.0 && historyWeight > 0.0) { + vec4 history = texture(historyTexture, TexCoord); + float validHistory = step(0.001, history.a); + reflection = mix(reflection, history, + historyWeight * validHistory * reflection.a); + } FragColor = reflection; }