diff --git a/assets/shaders/fxaa.frag.glsl b/assets/shaders/fxaa.frag.glsl index 158f2f98..3da10854 100644 --- a/assets/shaders/fxaa.frag.glsl +++ b/assets/shaders/fxaa.frag.glsl @@ -2,7 +2,7 @@ // FXAA 3.11 — Fast Approximate Anti-Aliasing post-process pass. // Reads the resolved scene color and outputs a smoothed result. -// Push constant: rcpFrame = vec2(1/width, 1/height), sharpness (0=off, 2=max), unused. +// Push constant: rcpFrame = vec2(1/width, 1/height), sharpness (0=off, 2=max), desaturate (1=ghost grayscale). layout(set = 0, binding = 0) uniform sampler2D uScene; @@ -11,8 +11,8 @@ layout(location = 0) out vec4 outColor; layout(push_constant) uniform PC { vec2 rcpFrame; - float sharpness; // 0 = no sharpen, 2 = max (matches FSR2 RCAS range) - float _pad; + float sharpness; // 0 = no sharpen, 2 = max (matches FSR2 RCAS range) + float desaturate; // 1 = full grayscale (ghost mode), 0 = normal color } pc; // Quality tuning @@ -145,5 +145,11 @@ void main() { fxaaResult = clamp(fxaaResult + s * (fxaaResult - blur), 0.0, 1.0); } + // Ghost mode: desaturate to grayscale (with a slight cool blue tint). + if (pc.desaturate > 0.5) { + float gray = dot(fxaaResult, vec3(0.299, 0.587, 0.114)); + fxaaResult = mix(fxaaResult, vec3(gray, gray, gray * 1.05), pc.desaturate); + } + outColor = vec4(fxaaResult, 1.0); } diff --git a/assets/shaders/fxaa.frag.spv b/assets/shaders/fxaa.frag.spv index 7803f3e2..b87b3dee 100644 Binary files a/assets/shaders/fxaa.frag.spv and b/assets/shaders/fxaa.frag.spv differ diff --git a/include/rendering/renderer.hpp b/include/rendering/renderer.hpp index 07d8091f..a198b0c7 100644 --- a/include/rendering/renderer.hpp +++ b/include/rendering/renderer.hpp @@ -638,6 +638,8 @@ private: bool terrainEnabled = true; bool terrainLoaded = false; + bool ghostMode_ = false; // set each frame from gameHandler->isPlayerGhost() + // CPU timing stats (last frame/update). double lastUpdateMs = 0.0; double lastRenderMs = 0.0; diff --git a/src/rendering/renderer.cpp b/src/rendering/renderer.cpp index 8dcf724c..67426ff3 100644 --- a/src/rendering/renderer.cpp +++ b/src/rendering/renderer.cpp @@ -5044,7 +5044,7 @@ void Renderer::renderFXAAPass() { vkCmdBindDescriptorSets(currentCmd, VK_PIPELINE_BIND_POINT_GRAPHICS, fxaa_.pipelineLayout, 0, 1, &fxaa_.descSet, 0, nullptr); - // Pass rcpFrame + sharpness (vec4, 16 bytes). + // Pass rcpFrame + sharpness + desaturate (vec4, 16 bytes). // When FSR2/FSR3 is active alongside FXAA, forward FSR2's sharpness so the // post-FXAA unsharp-mask step restores the crispness that FXAA's blur removes. float sharpness = fsr2_.enabled ? fsr2_.sharpness : 0.0f; @@ -5052,7 +5052,7 @@ void Renderer::renderFXAAPass() { 1.0f / static_cast(ext.width), 1.0f / static_cast(ext.height), sharpness, - 0.0f + ghostMode_ ? 1.0f : 0.0f // desaturate: 1=ghost grayscale, 0=normal }; vkCmdPushConstants(currentCmd, fxaa_.pipelineLayout, VK_SHADER_STAGE_FRAGMENT_BIT, 0, 16, pc); @@ -5092,6 +5092,9 @@ void Renderer::renderWorld(game::World* world, game::GameHandler* gameHandler) { lastWMORenderMs = 0.0; lastM2RenderMs = 0.0; + // Cache ghost state for use in overlay and FXAA passes this frame. + ghostMode_ = (gameHandler && gameHandler->isPlayerGhost()); + uint32_t frameIdx = vkCtx->getCurrentFrame(); VkDescriptorSet perFrameSet = perFrameDescSets[frameIdx]; const glm::mat4& view = camera ? camera->getViewMatrix() : glm::mat4(1.0f); @@ -5237,6 +5240,12 @@ void Renderer::renderWorld(game::World* world, game::GameHandler* gameHandler) { renderOverlay(tint, cmd); } } + // Ghost mode desaturation overlay (non-FXAA path approximation). + // When FXAA is active the FXAA shader applies true per-pixel desaturation; + // otherwise a high-opacity gray overlay gives a similar washed-out effect. + if (ghostMode_ && overlayPipeline && !fxaa_.enabled) { + renderOverlay(glm::vec4(0.5f, 0.5f, 0.55f, 0.82f), cmd); + } if (minimap && minimap->isEnabled() && camera && window) { glm::vec3 minimapCenter = camera->getPosition(); if (cameraController && cameraController->isThirdPerson()) @@ -5369,6 +5378,10 @@ void Renderer::renderWorld(game::World* world, game::GameHandler* gameHandler) { renderOverlay(tint); } } + // Ghost mode desaturation overlay (non-FXAA path approximation). + if (ghostMode_ && overlayPipeline && !fxaa_.enabled) { + renderOverlay(glm::vec4(0.5f, 0.5f, 0.55f, 0.82f)); + } if (minimap && minimap->isEnabled() && camera && window) { glm::vec3 minimapCenter = camera->getPosition(); if (cameraController && cameraController->isThirdPerson())