From a1c6ad20be7d00b0b9914d3e39d3c7d11e285ab6 Mon Sep 17 00:00:00 2001 From: atdunbg Date: Thu, 24 Sep 2026 13:03:00 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=89=B9=E6=80=A7=E5=BC=80=E5=85=B3=20?= =?UTF-8?q?+=20Pass=20=E8=B0=83=E8=AF=95=E8=A7=86=E5=9B=BE=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=EF=BC=88ImGui=20=E5=8F=AF=E6=8E=A7=EF=BC=8C=E7=BB=9F?= =?UTF-8?q?=E4=B8=80=E5=B8=A7=E5=A5=91=E7=BA=A6=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 三个运行时开关(Shadows/TAA/Bloom)与 9 种调试视图(Final/ TemporalDiff/HDR/TAAColor/Bloom/Velocity/ShadowMap0-2)。 统一设计:开关状态收敛 FrameSceneData 契约,SceneRenderer 唯一 写入、各 pass 只读(零虚接口路由);调试视图经 tonemap push int 路由已绑定的中间目标(声明超集、运行时零重绑);HDR 域 资源过同一 tonemap 保证观感可比。 - Shadows:ShadowPass lambda 空转 + frag cascadeSplits.w 短路 - TAA:alpha=0 直通 + SceneRenderer 停投影抖动(同一持有者同步) - Bloom:3 子 pass 空转 + tonemap 强度归零 - Renderer 门面 Get/Set 转发,App ImGui 3 Checkbox + Combo - PostProcessParams 瘦身为纯数值参数,showTemporalDiff 被取缔 --- App/main.cpp | 27 +++++- .../Engine/Renderer/Passes/BloomPass.h | 3 + .../Engine/Renderer/Passes/FullscreenGpu.h | 19 ++++- .../include/Engine/Renderer/Passes/TAAPass.h | 6 +- .../Engine/Renderer/Passes/TonemapPass.h | 15 +++- Engine/include/Engine/Renderer/Renderer.h | 16 +++- .../include/Engine/Renderer/SceneRenderer.h | 14 +++ Engine/include/Engine/Renderer/SceneTypes.h | 10 ++- Engine/src/Renderer/Passes/BloomPass.cpp | 11 ++- Engine/src/Renderer/Passes/FullscreenGpu.cpp | 21 +++-- Engine/src/Renderer/Passes/ShadowPass.cpp | 4 + Engine/src/Renderer/Passes/TAAPass.cpp | 9 +- Engine/src/Renderer/Passes/TonemapPass.cpp | 41 +++++++-- Engine/src/Renderer/Renderer.cpp | 41 +++++++-- Engine/src/Renderer/SceneRenderer.cpp | 11 ++- shaders/simple_shader.frag | 4 + shaders/tonemap.frag | 85 ++++++++++++++----- 17 files changed, 271 insertions(+), 66 deletions(-) diff --git a/App/main.cpp b/App/main.cpp index 3d1a2c3..e9010f2 100644 --- a/App/main.cpp +++ b/App/main.cpp @@ -182,11 +182,30 @@ namespace renderer.SetBloomStrength(strength); } - // 调试视图:帧历史差异(静止=黑,运动边缘发光=链路正确) - bool temporalDiff = renderer.GetShowTemporalDiff(); - if (ImGui::Checkbox("Temporal Diff View", &temporalDiff)) + ImGui::Text("Features"); + bool shadows = renderer.GetShadowsEnabled(); + if (ImGui::Checkbox("Shadows", &shadows)) { - renderer.SetShowTemporalDiff(temporalDiff); + renderer.SetShadowsEnabled(shadows); + } + bool taa = renderer.GetTAAEnabled(); + if (ImGui::Checkbox("TAA", &taa)) + { + renderer.SetTAAEnabled(taa); + } + bool bloom = renderer.GetBloomEnabled(); + if (ImGui::Checkbox("Bloom", &bloom)) + { + renderer.SetBloomEnabled(bloom); + } + + // 调试视图:单看某个 pass 的中间输出(类 UE pass debug) + const auto& viewNames = renderer.GetDebugViewNames(); + int view = renderer.GetDebugView(); + if (ImGui::Combo("Debug View", &view, viewNames.data(), + static_cast(viewNames.size()))) + { + renderer.SetDebugView(view); } ImGui::Checkbox("Pause Animation", &m_PauseAnim); diff --git a/Engine/include/Engine/Renderer/Passes/BloomPass.h b/Engine/include/Engine/Renderer/Passes/BloomPass.h index 9cc030b..33594f9 100644 --- a/Engine/include/Engine/Renderer/Passes/BloomPass.h +++ b/Engine/include/Engine/Renderer/Passes/BloomPass.h @@ -11,6 +11,7 @@ #include #include +#include #include #include @@ -19,6 +20,7 @@ namespace Engine::Passes { class BloomPass final : public RenderPass { public: BloomPass(FullscreenGpu& gpu, PostProcessParams& params, + FrameSceneData& frame, RG::TextureHandle hdrColor, RG::TextureHandle bloomA, RG::TextureHandle bloomB); ~BloomPass() override = default; @@ -29,6 +31,7 @@ namespace Engine::Passes { private: FullscreenGpu& m_Gpu; PostProcessParams& m_Params; + FrameSceneData& m_Frame; // bloomEnabled 开关(帧契约) RG::TextureHandle m_HDR; RG::TextureHandle m_A; // 提取 + 垂直模糊结果 RG::TextureHandle m_B; // 水平模糊中间结果 diff --git a/Engine/include/Engine/Renderer/Passes/FullscreenGpu.h b/Engine/include/Engine/Renderer/Passes/FullscreenGpu.h index 1529b58..aedbb34 100644 --- a/Engine/include/Engine/Renderer/Passes/FullscreenGpu.h +++ b/Engine/include/Engine/Renderer/Passes/FullscreenGpu.h @@ -31,13 +31,28 @@ namespace Engine { f32 param2; }; - // 后处理链参数(ImGui 可调;Renderer 持有,pass 按引用读取) + // 后处理数值参数(ImGui 可调;Renderer 持有,pass 按引用读取)。 + // 运行时开关/调试视图不在这里——统一进 FrameSceneData(帧契约) struct PostProcessParams { f32 exposure = 0.6f; bool useACES = true; f32 bloomThreshold = 1.0f; // HDR 域亮度阈值 f32 bloomStrength = 0.8f; // 叠加强度 - bool showTemporalDiff = false; // 调试视图:显示帧历史差异 + }; + + // 调试视图枚举:TonemapPass 按值把对应中间目标路由上屏。 + // 与 Renderer 的 DebugViewNames 表下标一一对应 + enum DebugView : int { + kDebugFinal = 0, // 正常画面(tonemap 输出) + kDebugTemporalDiff, // 帧历史差异(TAA 链路验证) + kDebugHDR, // Forward 原始 HDR(无 bloom/TAA) + kDebugTAAColor, // TAA resolve 后 + kDebugBloom, // bloom 模糊结果 + kDebugVelocity, // 运动向量(|uv| 增益显示) + kDebugShadowMap0, // CSM 级联 0 深度(近=白) + kDebugShadowMap1, // 级联 1 + kDebugShadowMap2, // 级联 2 + kDebugViewCount }; class FullscreenGpu { diff --git a/Engine/include/Engine/Renderer/Passes/TAAPass.h b/Engine/include/Engine/Renderer/Passes/TAAPass.h index 4156ef2..a6a073b 100644 --- a/Engine/include/Engine/Renderer/Passes/TAAPass.h +++ b/Engine/include/Engine/Renderer/Passes/TAAPass.h @@ -12,6 +12,7 @@ #include #include +#include #include #include @@ -19,7 +20,7 @@ namespace Engine::Passes { class TAAPass final : public RenderPass { public: - TAAPass(FullscreenGpu& gpu, f32 historyAlpha, + TAAPass(FullscreenGpu& gpu, FrameSceneData& frame, f32 historyAlpha, RG::TextureHandle hdrColor, RG::TextureHandle velocity, RG::TextureHandle taaColor); ~TAAPass() override = default; @@ -29,7 +30,8 @@ namespace Engine::Passes { private: FullscreenGpu& m_Gpu; - f32 m_HistoryAlpha; + FrameSceneData& m_Frame; // taaEnabled 开关(帧契约) + f32 m_HistoryAlpha; // 开启时的历史权重 RG::TextureHandle m_HDR; RG::TextureHandle m_Velocity; RG::TextureHandle m_TAAColor; // 输出 = 下一帧的历史 diff --git a/Engine/include/Engine/Renderer/Passes/TonemapPass.h b/Engine/include/Engine/Renderer/Passes/TonemapPass.h index 9a72d0d..7c046dc 100644 --- a/Engine/include/Engine/Renderer/Passes/TonemapPass.h +++ b/Engine/include/Engine/Renderer/Passes/TonemapPass.h @@ -11,6 +11,7 @@ #include #include +#include #include #include @@ -19,8 +20,12 @@ namespace Engine::Passes { class TonemapPass final : public RenderPass { public: TonemapPass(FullscreenGpu& gpu, PostProcessParams& params, + FrameSceneData& frame, RG::TextureHandle hdrColor, RG::TextureHandle bloomA, - RG::TextureHandle temporalDiff, RG::TextureHandle backbuffer); + RG::TextureHandle temporalDiff, RG::TextureHandle backbuffer, + RG::TextureHandle rawHdr, RG::TextureHandle velocity, + RG::TextureHandle shadowMap0, RG::TextureHandle shadowMap1, + RG::TextureHandle shadowMap2); ~TonemapPass() override = default; void Register(RG::RenderGraph& rg) override; @@ -29,10 +34,16 @@ namespace Engine::Passes { private: FullscreenGpu& m_Gpu; PostProcessParams& m_Params; + FrameSceneData& m_Frame; // debugView / bloomEnabled(帧契约) RG::TextureHandle m_HDR; RG::TextureHandle m_A; - RG::TextureHandle m_TemporalDiff; // 调试视图输入(当前帧槽位) + RG::TextureHandle m_TemporalDiff; RG::TextureHandle m_Backbuffer; + // ---- 调试视图源(binding 3-7,Register 时声明读防止剔除)---- + // 注:TAAColor 视图直接用 binding0(主输入即 TAA 输出),无需重复绑 + RG::TextureHandle m_RawHDR; // Forward 直出(TAA 前) + RG::TextureHandle m_Velocity; + RG::TextureHandle m_ShadowMaps[3]; std::vector m_Sets; // per-slot }; diff --git a/Engine/include/Engine/Renderer/Renderer.h b/Engine/include/Engine/Renderer/Renderer.h index 3ef25c0..07876b0 100644 --- a/Engine/include/Engine/Renderer/Renderer.h +++ b/Engine/include/Engine/Renderer/Renderer.h @@ -81,9 +81,19 @@ namespace Engine { void SetBloomThreshold(f32 v); f32 GetBloomStrength() const; void SetBloomStrength(f32 v); - // 调试视图:显示帧历史差异(TemporalDiff pass 产出) - bool GetShowTemporalDiff() const; - void SetShowTemporalDiff(bool b); + + // ---- 特性开关 / 调试视图(转发到 SceneRenderer,每帧落 + // FrameSceneData 契约,pass 只读;ImGui 直接对接)---- + bool GetShadowsEnabled() const; + void SetShadowsEnabled(bool b); + bool GetTAAEnabled() const; + void SetTAAEnabled(bool b); + bool GetBloomEnabled() const; + void SetBloomEnabled(bool b); + int GetDebugView() const; + void SetDebugView(int v); // Passes::DebugView 枚举值 + // 调试视图名表(下标 = DebugView 枚举值,ImGui combo 直接枚举) + const std::vector& GetDebugViewNames() const; // pass 级 GPU 耗时(最近完成帧,毫秒;与 GetPassGpuMs 下标对应) const std::vector& GetPassNames() const { return m_PassNames; } diff --git a/Engine/include/Engine/Renderer/SceneRenderer.h b/Engine/include/Engine/Renderer/SceneRenderer.h index 9e346bc..8b78546 100644 --- a/Engine/include/Engine/Renderer/SceneRenderer.h +++ b/Engine/include/Engine/Renderer/SceneRenderer.h @@ -49,6 +49,14 @@ namespace Engine { } f32 GetCollectMs() const { return m_Frame.collectMs; } + // ---- 运行时开关/调试视图(ImGui 经 Renderer 门面调用)。 + // 状态唯一持有者在此,BuildFrame 每帧落入 FrameSceneData; + // TAA 开关同时控制投影抖动,同一持有者天然同步 ---- + void SetShadowsEnabled(bool b) { m_ShadowsEnabled = b; } + void SetTAAEnabled(bool b) { m_TAAEnabled = b; } + void SetBloomEnabled(bool b) { m_BloomEnabled = b; } + void SetDebugView(int v) { m_DebugView = v; } + private: // CSM:把相机视锥按实用分割切 N 段,每段独立拟合光源正交投影 //(含纹素对齐防阴影边缘闪烁)。写入(Shadow pass push)与采样 @@ -86,6 +94,12 @@ namespace Engine { std::unordered_map m_CurWorlds; bool m_TruncationWarned = false; // 实例缓冲打满只告警一次 + + // ---- 运行时开关/调试视图状态(本类是唯一写入者)---- + bool m_ShadowsEnabled = true; + bool m_TAAEnabled = true; + bool m_BloomEnabled = true; + int m_DebugView = 0; }; } // namespace Engine diff --git a/Engine/include/Engine/Renderer/SceneTypes.h b/Engine/include/Engine/Renderer/SceneTypes.h index 6c31ccf..09adfc3 100644 --- a/Engine/include/Engine/Renderer/SceneTypes.h +++ b/Engine/include/Engine/Renderer/SceneTypes.h @@ -52,7 +52,8 @@ namespace Engine { glm::mat4 view; glm::mat4 proj; // 抖动后的投影(光栅化用) glm::mat4 lightSpace[kMaxCascades]; // CSM 各级联光空间 VP([0,1] 重映射) - glm::vec4 cascadeSplits; // xyz = 级联 0..2 远边界(视图深度,正值),w 未用 + glm::vec4 cascadeSplits; // xyz = 级联 0..2 远边界(视图深度,正值) + // w = shadowsEnabled(1.0/0.0,frag 阴影短路用) glm::vec4 ambientColor; LightData lights[kMaxLights]; @@ -82,6 +83,13 @@ namespace Engine { // 级联盒沿光方向加了 caster pad,影子不会被剔除裁掉) CullResult shadowCascades[kMaxCascades]; + // ---- 运行时开关/调试视图(ImGui 经 Renderer 门面写入, + // SceneRenderer 唯一落帧,各 pass 只读;每帧确定性)---- + bool shadowsEnabled = true; // ShadowPass 空转 + frag ShadowFactor 短路 + bool taaEnabled = true; // TAAPass 直通(alpha=0) + 停投影抖动 + bool bloomEnabled = true; // BloomPass 子 pass 空转 + tonemap 强度归零 + int debugView = 0; // Passes::DebugView 枚举(tonemap 路由上屏) + f32 collectMs = 0.0f; // 收集+剔除 CPU 耗时(遥测) }; diff --git a/Engine/src/Renderer/Passes/BloomPass.cpp b/Engine/src/Renderer/Passes/BloomPass.cpp index 93532fa..8e9aa58 100644 --- a/Engine/src/Renderer/Passes/BloomPass.cpp +++ b/Engine/src/Renderer/Passes/BloomPass.cpp @@ -8,9 +8,10 @@ namespace Engine::Passes { BloomPass::BloomPass(FullscreenGpu& gpu, PostProcessParams& params, + FrameSceneData& frame, RG::TextureHandle hdrColor, RG::TextureHandle bloomA, RG::TextureHandle bloomB) - : m_Gpu(gpu), m_Params(params), + : m_Gpu(gpu), m_Params(params), m_Frame(frame), m_HDR(hdrColor), m_A(bloomA), m_B(bloomB) { m_ExtractSets.resize(kMaxFramesInFlight); @@ -25,9 +26,13 @@ namespace Engine::Passes void BloomPass::Register(RG::RenderGraph& rg) { - // Extract:HDR → 半分辨率 A(软阈值由 frag 的 param0 传入) + // bloom 关闭:三个子 pass 全部空转(省两次模糊 + 一次提取), + // tonemap 侧强度同步归零不采样叠加。 + // 注意开关判断必须在录制 lambda 内(每帧评估),Register 只在 + // RG 编译时跑一次 rg.AddPass("BloomExtract", [this](RG::RenderGraphContext& ctx) { + if (!m_Frame.bloomEnabled) return; m_Gpu.RecordFullscreen(ctx, ctx.View(m_A), m_Gpu.extractFrag, m_ExtractSets[ctx.frame.frameSlot], FullscreenPush{0.0f, m_Params.bloomThreshold, 0.0f, 0.0f}); @@ -39,6 +44,7 @@ namespace Engine::Passes // BlurH:A → B(水平步长经 push 传入) rg.AddPass("BloomBlurH", [this](RG::RenderGraphContext& ctx) { + if (!m_Frame.bloomEnabled) return; m_Gpu.RecordFullscreen(ctx, ctx.View(m_B), m_Gpu.blurFrag, m_BlurHSets[ctx.frame.frameSlot], FullscreenPush{0.0f, @@ -52,6 +58,7 @@ namespace Engine::Passes // BlurV:B → A(垂直步长) rg.AddPass("BloomBlurV", [this](RG::RenderGraphContext& ctx) { + if (!m_Frame.bloomEnabled) return; m_Gpu.RecordFullscreen(ctx, ctx.View(m_A), m_Gpu.blurFrag, m_BlurVSets[ctx.frame.frameSlot], FullscreenPush{0.0f, 0.0f, diff --git a/Engine/src/Renderer/Passes/FullscreenGpu.cpp b/Engine/src/Renderer/Passes/FullscreenGpu.cpp index 32b36e2..8e3386c 100644 --- a/Engine/src/Renderer/Passes/FullscreenGpu.cpp +++ b/Engine/src/Renderer/Passes/FullscreenGpu.cpp @@ -16,17 +16,16 @@ namespace Engine::Passes ShaderManager& shaders) : m_Device(device), m_Descriptors(descriptors) { - // Set0 = {HDR 颜色图, Bloom 图, 调试输入},push = 16B 通用结构 - //(binding2 只被 tonemap 的调试视图使用;bloom 系 shader 静态 - // 使用的 binding 是布局的子集,合法) - std::vector bindings = { - {0, vk::DescriptorType::eCombinedImageSampler, - vk::ShaderStageFlagBits::eFragment}, - {1, vk::DescriptorType::eCombinedImageSampler, - vk::ShaderStageFlagBits::eFragment}, - {2, vk::DescriptorType::eCombinedImageSampler, - vk::ShaderStageFlagBits::eFragment}, - }; + // Set0 = {HDR 颜色图, Bloom 图, 调试输入, TAA, Velocity, + // ShadowMap0/1/2},push = 16B 通用结构(VUID-08878)。 + // 后 6 个 binding 供 Tonemap 调试视图使用;其余全屏 shader + // 静态使用的 binding 是布局的子集,合法 + std::vector bindings; + for (u32 b = 0; b < 8; ++b) { + bindings.push_back( + {b, vk::DescriptorType::eCombinedImageSampler, + vk::ShaderStageFlagBits::eFragment}); + } setLayout = descriptors.CreateLayout(bindings); layout = descriptors.CreatePipelineLayout( diff --git a/Engine/src/Renderer/Passes/ShadowPass.cpp b/Engine/src/Renderer/Passes/ShadowPass.cpp index 75d5758..7e2ecbc 100644 --- a/Engine/src/Renderer/Passes/ShadowPass.cpp +++ b/Engine/src/Renderer/Passes/ShadowPass.cpp @@ -16,6 +16,10 @@ namespace Engine::Passes // 材质/实体数据驱动剔除,而非 pass 逻辑特判。 // (每级联独立剔除是后续优化点:shadowView 细分为 per-cascade 列表) rg.AddPass("Shadow", [this](RG::RenderGraphContext& ctx) { + // 阴影关闭:整 pass 空转(省一整趟场景绘制)。图仍是 + // Clear=1.0,frag 侧 cascadeSplits.w 短路根本不采样 + if (!m_Frame.shadowsEnabled) return; + const vk::CommandBuffer cmd = ctx.cmd; for (u32 c = 0; c < kMaxCascades; ++c) { diff --git a/Engine/src/Renderer/Passes/TAAPass.cpp b/Engine/src/Renderer/Passes/TAAPass.cpp index 8cd8bf6..aa0e9f3 100644 --- a/Engine/src/Renderer/Passes/TAAPass.cpp +++ b/Engine/src/Renderer/Passes/TAAPass.cpp @@ -7,10 +7,10 @@ namespace Engine::Passes { - TAAPass::TAAPass(FullscreenGpu& gpu, f32 historyAlpha, + TAAPass::TAAPass(FullscreenGpu& gpu, FrameSceneData& frame, f32 historyAlpha, RG::TextureHandle hdrColor, RG::TextureHandle velocity, RG::TextureHandle taaColor) - : m_Gpu(gpu), m_HistoryAlpha(historyAlpha), + : m_Gpu(gpu), m_Frame(frame), m_HistoryAlpha(historyAlpha), m_HDR(hdrColor), m_Velocity(velocity), m_TAAColor(taaColor) { m_Sets.resize(kMaxFramesInFlight); @@ -23,9 +23,12 @@ namespace Engine::Passes { rg.AddPass("TAA", [this](RG::RenderGraphContext& ctx) { + // TAA 关闭:alpha=0 → mix(cur, hist, 0) 直通当前帧 + //(抖动已由 SceneRenderer 同步停掉,画面静止无锯齿晃动) + const f32 alpha = m_Frame.taaEnabled ? m_HistoryAlpha : 0.0f; m_Gpu.RecordFullscreen(ctx, ctx.View(m_TAAColor), m_Gpu.taaFrag, m_Sets[ctx.frame.frameSlot], - FullscreenPush{0.0f, m_HistoryAlpha, 0.0f, 0.0f}); + FullscreenPush{0.0f, alpha, 0.0f, 0.0f}); }) .Read(m_HDR, RG::ResourceUse::ShaderRead, vk::ShaderStageFlagBits::eFragment) diff --git a/Engine/src/Renderer/Passes/TonemapPass.cpp b/Engine/src/Renderer/Passes/TonemapPass.cpp index 8356064..ee633e3 100644 --- a/Engine/src/Renderer/Passes/TonemapPass.cpp +++ b/Engine/src/Renderer/Passes/TonemapPass.cpp @@ -8,12 +8,20 @@ namespace Engine::Passes { TonemapPass::TonemapPass(FullscreenGpu& gpu, PostProcessParams& params, + FrameSceneData& frame, RG::TextureHandle hdrColor, RG::TextureHandle bloomA, RG::TextureHandle temporalDiff, - RG::TextureHandle backbuffer) - : m_Gpu(gpu), m_Params(params), + RG::TextureHandle backbuffer, + RG::TextureHandle rawHdr, + RG::TextureHandle velocity, + RG::TextureHandle shadowMap0, + RG::TextureHandle shadowMap1, + RG::TextureHandle shadowMap2) + : m_Gpu(gpu), m_Params(params), m_Frame(frame), m_HDR(hdrColor), m_A(bloomA), m_TemporalDiff(temporalDiff), - m_Backbuffer(backbuffer) + m_Backbuffer(backbuffer), + m_RawHDR(rawHdr), m_Velocity(velocity), + m_ShadowMaps{shadowMap0, shadowMap1, shadowMap2} { m_Sets.resize(kMaxFramesInFlight); for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) { @@ -30,17 +38,31 @@ namespace Engine::Passes m_Sets[ctx.frame.frameSlot], FullscreenPush{m_Params.exposure, m_Params.useACES ? 1.0f : 0.0f, - m_Params.bloomStrength, - m_Params.showTemporalDiff ? 1.0f : 0.0f}); + m_Frame.bloomEnabled + ? m_Params.bloomStrength + : 0.0f, + static_cast( + m_Frame.debugView)}); }) .Write(m_Backbuffer, RG::ResourceUse::ColorAttachment) .Read(m_HDR, RG::ResourceUse::ShaderRead, vk::ShaderStageFlagBits::eFragment) .Read(m_A, RG::ResourceUse::ShaderRead, vk::ShaderStageFlagBits::eFragment) - // shader 静态采样 binding2(调试视图开关在 push constant 里), - // 声明读否则本 pass 的产出无人消费会被剔除、镜像也不会分配 + // 调试视图源全部声明读:路由开关在 push constant(每帧变), + // 绑定集与 barrier 必须静态成立——这是"声明超集、运行时路由" + // 模式,避免切换视图时重绑描述符 .Read(m_TemporalDiff, RG::ResourceUse::ShaderRead, + vk::ShaderStageFlagBits::eFragment) + .Read(m_RawHDR, RG::ResourceUse::ShaderRead, + vk::ShaderStageFlagBits::eFragment) + .Read(m_Velocity, RG::ResourceUse::ShaderRead, + vk::ShaderStageFlagBits::eFragment) + .Read(m_ShadowMaps[0], RG::ResourceUse::ShaderRead, + vk::ShaderStageFlagBits::eFragment) + .Read(m_ShadowMaps[1], RG::ResourceUse::ShaderRead, + vk::ShaderStageFlagBits::eFragment) + .Read(m_ShadowMaps[2], RG::ResourceUse::ShaderRead, vk::ShaderStageFlagBits::eFragment); } @@ -50,6 +72,11 @@ namespace Engine::Passes m_Gpu.UpdateSetInput(m_Sets[slot], 0, rg, m_HDR, slot); m_Gpu.UpdateSetInput(m_Sets[slot], 1, rg, m_A, slot); m_Gpu.UpdateSetInput(m_Sets[slot], 2, rg, m_TemporalDiff, slot); + m_Gpu.UpdateSetInput(m_Sets[slot], 3, rg, m_RawHDR, slot); + m_Gpu.UpdateSetInput(m_Sets[slot], 4, rg, m_Velocity, slot); + m_Gpu.UpdateSetInput(m_Sets[slot], 5, rg, m_ShadowMaps[0], slot); + m_Gpu.UpdateSetInput(m_Sets[slot], 6, rg, m_ShadowMaps[1], slot); + m_Gpu.UpdateSetInput(m_Sets[slot], 7, rg, m_ShadowMaps[2], slot); } } diff --git a/Engine/src/Renderer/Renderer.cpp b/Engine/src/Renderer/Renderer.cpp index 5b57e11..1005628 100644 --- a/Engine/src/Renderer/Renderer.cpp +++ b/Engine/src/Renderer/Renderer.cpp @@ -168,8 +168,33 @@ namespace Engine void Renderer::SetBloomThreshold(f32 v) { m_PostParams->bloomThreshold = v; } f32 Renderer::GetBloomStrength() const { return m_PostParams->bloomStrength; } void Renderer::SetBloomStrength(f32 v) { m_PostParams->bloomStrength = v; } - bool Renderer::GetShowTemporalDiff() const { return m_PostParams->showTemporalDiff; } - void Renderer::SetShowTemporalDiff(bool b) { m_PostParams->showTemporalDiff = b; } + + // ---- 特性开关 / 调试视图:转发 SceneRenderer(状态唯一持有者)---- + bool Renderer::GetShadowsEnabled() const + { + return m_SceneRenderer->Frame().shadowsEnabled; + } + void Renderer::SetShadowsEnabled(bool b) { m_SceneRenderer->SetShadowsEnabled(b); } + bool Renderer::GetTAAEnabled() const { return m_SceneRenderer->Frame().taaEnabled; } + void Renderer::SetTAAEnabled(bool b) { m_SceneRenderer->SetTAAEnabled(b); } + bool Renderer::GetBloomEnabled() const + { + return m_SceneRenderer->Frame().bloomEnabled; + } + void Renderer::SetBloomEnabled(bool b) { m_SceneRenderer->SetBloomEnabled(b); } + int Renderer::GetDebugView() const { return m_SceneRenderer->Frame().debugView; } + void Renderer::SetDebugView(int v) { m_SceneRenderer->SetDebugView(v); } + + const std::vector& Renderer::GetDebugViewNames() const + { + static const std::vector kNames = { + "Final", "TemporalDiff", "HDR", "TAAColor", "Bloom", + "Velocity", "ShadowMap0", "ShadowMap1", "ShadowMap2", + }; + static_assert(int(Passes::kDebugViewCount) == 9, + "DebugView 枚举与名表同步"); + return kNames; + } u32 Renderer::GetMainInstanceCount() const { @@ -252,15 +277,17 @@ namespace Engine m_Passes.push_back(CreateScope( *m_SceneGpu, frame, m_HDRColor, m_Velocity, m_Depth, m_ShadowMaps)); m_Passes.push_back(CreateScope( - *m_FullscreenGpu, /*historyAlpha*/ 0.9f, m_HDRColor, m_Velocity, - m_TAAColor)); + *m_FullscreenGpu, frame, /*historyAlpha*/ 0.9f, m_HDRColor, + m_Velocity, m_TAAColor)); m_Passes.push_back(CreateScope( *m_FullscreenGpu, /*gain*/ 8.0f, m_HDRColor, m_TemporalDiff)); m_Passes.push_back(CreateScope( - *m_FullscreenGpu, *m_PostParams, m_TAAColor, m_BloomA, m_BloomB)); + *m_FullscreenGpu, *m_PostParams, frame, m_TAAColor, m_BloomA, + m_BloomB)); m_Passes.push_back(CreateScope( - *m_FullscreenGpu, *m_PostParams, m_TAAColor, m_BloomA, - m_TemporalDiff, m_Backbuffer)); + *m_FullscreenGpu, *m_PostParams, frame, m_TAAColor, m_BloomA, + m_TemporalDiff, m_Backbuffer, m_HDRColor, m_Velocity, + m_ShadowMaps[0], m_ShadowMaps[1], m_ShadowMaps[2])); m_Passes.push_back(CreateScope( &m_ImGuiLayer, m_Backbuffer)); m_Passes.push_back(CreateScope(m_Backbuffer)); diff --git a/Engine/src/Renderer/SceneRenderer.cpp b/Engine/src/Renderer/SceneRenderer.cpp index f116d12..8422b4e 100644 --- a/Engine/src/Renderer/SceneRenderer.cpp +++ b/Engine/src/Renderer/SceneRenderer.cpp @@ -157,8 +157,8 @@ namespace Engine ubo.projNoJitter = ubo.proj; // 子像素抖动(Halton×8):光栅化采样位置逐帧偏移,TAA 累积去锯齿。 - // NDC 偏移 = 像素偏移 × 2 / 尺寸 - { + // NDC 偏移 = 像素偏移 × 2 / 尺寸。TAA 关闭时停抖(直通画面静止) + if (m_TAAEnabled) { const u32 idx = static_cast(m_FrameIndex % 8); const f32 jx = Halton(idx + 1, 2) - 0.5f; const f32 jy = Halton(idx + 1, 3) - 0.5f; @@ -168,6 +168,13 @@ namespace Engine ubo.ambientColor = scene.GetSettings().ambientColor; + // ---- 运行时开关/调试视图落帧(各 pass 与 frag 只读契约字段)---- + m_Frame.shadowsEnabled = m_ShadowsEnabled; + m_Frame.taaEnabled = m_TAAEnabled; + m_Frame.bloomEnabled = m_BloomEnabled; + m_Frame.debugView = m_DebugView; + ubo.cascadeSplits.w = m_ShadowsEnabled ? 1.0f : 0.0f; + // 天空盒:视图去平移(只留旋转)× 投影 → 逆矩阵,push 给 Skybox pass; // 反投影远平面点 = 相机相对世界方向,frag normalize 后采样 cube m_Frame.skyInvVP = diff --git a/shaders/simple_shader.frag b/shaders/simple_shader.frag index 4dd8c99..0553fff 100644 --- a/shaders/simple_shader.frag +++ b/shaders/simple_shader.frag @@ -57,6 +57,10 @@ layout(set = 2, binding = 5) uniform sampler2D brdfLut; // BRDF 积分 // 等效 36 次比较)→ 软阴影边缘;采样器 clamp-to-border 白色: // 光视锥外自动 1.0(无阴影),省出界分支 float ShadowFactor(vec3 N, float viewDepth) { + // 阴影总开关(ubo.cascadeSplits.w 由 CPU 每帧写入):关闭即全亮, + // 配合 ShadowPass 空转(深度图无内容也读不到这里) + if (ubo.cascadeSplits.w < 0.5) return 1.0; + int cascade = 2; for (int i = 0; i < 3; ++i) { if (viewDepth < ubo.cascadeSplits[i]) { cascade = i; break; } diff --git a/shaders/tonemap.frag b/shaders/tonemap.frag index 0b95f22..3b23f2e 100644 --- a/shaders/tonemap.frag +++ b/shaders/tonemap.frag @@ -1,47 +1,92 @@ #version 450 -// Tonemap pass:HDR 线性颜色 → 显示空间。 +// Tonemap pass:HDR(+bloom)→ 显示空间,兼调试视图路由上屏。 // 这是显示变换的显式建模(对齐 UE/Unity:HDR SceneColor → tonemapper → UI)。 // ACES = Narkowicz 拟合(线性域映射到 [0,1]),可经 push constant 关闭 -// 退回纯 gamma(旧观感);bloom 等后处理在 HDR 域(Tonemap 之前)插 pass。 +// 退回纯 gamma(旧观感)。 +// +// 调试视图:pc.debugView 整型路由到已绑定的中间目标(绑定超集静态成立, +// 运行时零重绑)。HDR 域资源过同一套 exposure/tonemap 保证观感可比; +// LDR 类(velocity/深度)直接可视化。 layout(location = 0) in vec2 fragUV; layout(location = 0) out vec4 outColor; -layout(set = 0, binding = 0) uniform sampler2D hdrImage; +layout(set = 0, binding = 0) uniform sampler2D taaImage; // TAA 后 HDR(主输入) layout(set = 0, binding = 1) uniform sampler2D bloomImage; -layout(set = 0, binding = 2) uniform sampler2D temporalDiffImage; // 帧历史差异(调试视图) +layout(set = 0, binding = 2) uniform sampler2D temporalDiffImage; +layout(set = 0, binding = 3) uniform sampler2D rawHdrImage; // Forward 直出(TAA 前) +layout(set = 0, binding = 4) uniform sampler2D velocityImage; +layout(set = 0, binding = 5) uniform sampler2D shadowMap0Image; // D32 深度(r 分量) +layout(set = 0, binding = 6) uniform sampler2D shadowMap1Image; +layout(set = 0, binding = 7) uniform sampler2D shadowMap2Image; + +// 与 Passes::DebugView 枚举同步(FullscreenGpu.h) +const int VIEW_FINAL = 0; +const int VIEW_TEMPORALDIFF = 1; +const int VIEW_RAW_HDR = 2; +const int VIEW_TAA = 3; +const int VIEW_BLOOM = 4; +const int VIEW_VELOCITY = 5; +const int VIEW_SHADOW0 = 6; +const int VIEW_SHADOW1 = 7; +const int VIEW_SHADOW2 = 8; // 全屏 pass 通用 push(16B,程序级统一声明,VUID-08878) layout(push_constant) uniform PushConstants { float exposure; // 曝光系数 float aces; // >0.5 启用 ACES 曲线,否则纯 gamma float bloomStrength; // bloom 叠加强度(0 = 关闭) - float debugView; // >0.5 显示帧历史差异视图(TemporalDiff) + float debugView; // 调试视图枚举(整型经 float 传) } pc; vec3 acesFilm(vec3 x) { return clamp((x * (2.51 * x + 0.03)) / (x * (2.43 * x + 0.59) + 0.14), 0.0, 1.0); } +vec3 tonemap(vec3 hdr) { + hdr *= pc.exposure; + vec3 mapped = (pc.aces > 0.5) ? acesFilm(hdr) : clamp(hdr, 0.0, 1.0); + return pow(mapped, vec3(1.0 / 2.2)); +} + +// 深度可视化:反相(近=白,与调试截图风格一致) +vec3 depthView(sampler2D depthImage) { + float d = texture(depthImage, fragUV).r; + return vec3(pow(1.0 - d, 2.2)); +} + void main() { - // 调试视图:帧历史差异(静止=黑,运动边缘发光) - if (pc.debugView > 0.5) { - vec3 d = clamp(texture(temporalDiffImage, fragUV).rgb, 0.0, 1.0); - outColor = vec4(pow(d, vec3(1.0 / 2.2)), 1.0); - return; + int view = int(pc.debugView + 0.5); + + // ---- 调试视图路由 ---- + if (view != VIEW_FINAL) { + switch (view) { + case VIEW_TEMPORALDIFF: { + // 帧历史差异:静止=黑,运动边缘发光 + vec3 d = clamp(texture(temporalDiffImage, fragUV).rgb, 0.0, 1.0); + outColor = vec4(pow(d, vec3(1.0 / 2.2)), 1.0); + return; + } + case VIEW_RAW_HDR: outColor = vec4(tonemap(texture(rawHdrImage, fragUV).rgb), 1.0); return; + case VIEW_TAA: outColor = vec4(tonemap(texture(taaImage, fragUV).rgb), 1.0); return; + case VIEW_BLOOM: outColor = vec4(tonemap(texture(bloomImage, fragUV).rgb * 4.0), 1.0); return; + case VIEW_VELOCITY: { + // |velocity| × 增益:静止=黑,运动越快越亮 + vec2 vel = texture(velocityImage, fragUV).xy; + float mag = clamp(length(vel) * 8.0, 0.0, 1.0); + outColor = vec4(vec3(mag), 1.0); + return; + } + case VIEW_SHADOW0: outColor = vec4(depthView(shadowMap0Image), 1.0); return; + case VIEW_SHADOW1: outColor = vec4(depthView(shadowMap1Image), 1.0); return; + case VIEW_SHADOW2: outColor = vec4(depthView(shadowMap2Image), 1.0); return; + } } - // bloom 在 HDR 域叠加(半分辨率模糊图线性采样上采样) - vec4 hdrTex = texture(hdrImage, fragUV); + // ---- 正常路径:TAA 后 HDR + bloom 叠加 → tonemap ---- + vec4 hdrTex = texture(taaImage, fragUV); vec3 hdr = hdrTex.rgb + texture(bloomImage, fragUV).rgb * pc.bloomStrength; - float alpha = hdrTex.a; - hdr *= pc.exposure; - - vec3 mapped = (pc.aces > 0.5) - ? acesFilm(hdr) - : clamp(hdr, 0.0, 1.0); - - outColor = vec4(pow(mapped, vec3(1.0 / 2.2)) * alpha, alpha); + outColor = vec4(tonemap(hdr) * hdrTex.a, hdrTex.a); }