feat: 特性开关 + Pass 调试视图接口(ImGui 可控,统一帧契约)

三个运行时开关(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 被取缔
This commit is contained in:
2026-09-24 13:03:00 +08:00
parent 708d714459
commit a1c6ad20be
17 changed files with 271 additions and 66 deletions
+23 -4
View File
@@ -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<int>(viewNames.size())))
{
renderer.SetDebugView(view);
}
ImGui::Checkbox("Pause Animation", &m_PauseAnim);
@@ -11,6 +11,7 @@
#include <Engine/Renderer/Passes/RenderPass.h>
#include <Engine/Renderer/Passes/FullscreenGpu.h>
#include <Engine/Renderer/SceneTypes.h>
#include <vulkan/vulkan.hpp>
#include <vector>
@@ -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; // 水平模糊中间结果
@@ -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 {
@@ -12,6 +12,7 @@
#include <Engine/Renderer/Passes/RenderPass.h>
#include <Engine/Renderer/Passes/FullscreenGpu.h>
#include <Engine/Renderer/SceneTypes.h>
#include <vulkan/vulkan.hpp>
#include <vector>
@@ -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; // 输出 = 下一帧的历史
@@ -11,6 +11,7 @@
#include <Engine/Renderer/Passes/RenderPass.h>
#include <Engine/Renderer/Passes/FullscreenGpu.h>
#include <Engine/Renderer/SceneTypes.h>
#include <vulkan/vulkan.hpp>
#include <vector>
@@ -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<vk::DescriptorSet> m_Sets; // per-slot
};
+13 -3
View File
@@ -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<const char*>& GetDebugViewNames() const;
// pass 级 GPU 耗时(最近完成帧,毫秒;与 GetPassGpuMs 下标对应)
const std::vector<std::string>& GetPassNames() const { return m_PassNames; }
@@ -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<u32, glm::mat4> m_CurWorlds;
bool m_TruncationWarned = false; // 实例缓冲打满只告警一次
// ---- 运行时开关/调试视图状态(本类是唯一写入者)----
bool m_ShadowsEnabled = true;
bool m_TAAEnabled = true;
bool m_BloomEnabled = true;
int m_DebugView = 0;
};
} // namespace Engine
+9 -1
View File
@@ -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 耗时(遥测)
};
+9 -2
View File
@@ -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,
+10 -11
View File
@@ -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<DescriptorBinding> 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<DescriptorBinding> 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(
@@ -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) {
+6 -3
View File
@@ -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)
+34 -7
View File
@@ -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<f32>(
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);
}
}
+34 -7
View File
@@ -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<const char*>& Renderer::GetDebugViewNames() const
{
static const std::vector<const char*> 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<Passes::ForwardPass>(
*m_SceneGpu, frame, m_HDRColor, m_Velocity, m_Depth, m_ShadowMaps));
m_Passes.push_back(CreateScope<Passes::TAAPass>(
*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<Passes::TemporalDiffPass>(
*m_FullscreenGpu, /*gain*/ 8.0f, m_HDRColor, m_TemporalDiff));
m_Passes.push_back(CreateScope<Passes::BloomPass>(
*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<Passes::TonemapPass>(
*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<Passes::ImGuiPass>(
&m_ImGuiLayer, m_Backbuffer));
m_Passes.push_back(CreateScope<Passes::PresentPass>(m_Backbuffer));
+9 -2
View File
@@ -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<u32>(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 =
+4
View File
@@ -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; }
+62 -17
View File
@@ -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) {
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);
}