feat: RenderGraph 帧历史资源(ReadHistory)+ TemporalDiff 验证 pass
- PassBuilder::ReadHistory:读上一帧槽位 ((slot-1+F) mod F) 的物理镜像, TAA/运动模糊等时域效果的前置能力,RG 最后一个已知边界补齐 - 历史读不建拓扑边(跨帧依赖由同队列提交序保证)、不入本帧状态链、 不改生命周期;剔除照常参与(生产者存活判定) - 历史 barrier:首帧走 first(old=Undefined),之后走 steady 且 src=ALL_COMMANDS|MEMORY_WRITE 显式闭合跨帧可用性+可见性; 有历史读的资源,常规 entry steady 的 src 同样升级(历史读打破了 同槽位 fence 全等保前提) - TemporalDiff 调试 pass:|上一帧−当前帧|×增益,静止=黑/运动边缘发光 即链路正确;Tonemap binding2 经 push 开关切调试视图 - 编译零错误、validation 零警告、8 秒冒烟通过(9 pass 全活跃)
This commit is contained in:
@@ -166,6 +166,13 @@ protected:
|
|||||||
renderer.SetBloomStrength(strength);
|
renderer.SetBloomStrength(strength);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 调试视图:帧历史差异(静止=黑,运动边缘发光=链路正确)
|
||||||
|
bool temporalDiff = renderer.GetShowTemporalDiff();
|
||||||
|
if (ImGui::Checkbox("Temporal Diff View", &temporalDiff))
|
||||||
|
{
|
||||||
|
renderer.SetShowTemporalDiff(temporalDiff);
|
||||||
|
}
|
||||||
|
|
||||||
// Pass 级 GPU 耗时(RG timestamp)
|
// Pass 级 GPU 耗时(RG timestamp)
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
ImGui::Text("GPU Timings");
|
ImGui::Text("GPU Timings");
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ namespace Engine {
|
|||||||
u64 m_FrameCount = 0;
|
u64 m_FrameCount = 0;
|
||||||
|
|
||||||
// 帧率控制
|
// 帧率控制
|
||||||
bool m_VSync = false; // 垂直同步(present 自带节奏)
|
bool m_VSync = true; // 垂直同步(present 自带节奏)
|
||||||
f32 m_TargetFps = 60.0f; // 限帧目标(≥24)
|
f32 m_TargetFps = 60.0f; // 限帧目标(≥24)
|
||||||
|
|
||||||
std::chrono::steady_clock::time_point m_LastFrameTime;
|
std::chrono::steady_clock::time_point m_LastFrameTime;
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
//
|
//
|
||||||
// 已知边界(后续演进点):
|
// 已知边界(后续演进点):
|
||||||
// - 暂不支持异步 compute queue / 跨队列信号量
|
// - 暂不支持异步 compute queue / 跨队列信号量
|
||||||
// - 暂不支持内存别名(需 VMA)与帧历史资源(TAA 等)
|
|
||||||
// - 同一 pass 内对同一资源"读+写"(input attachment / RWM)未建模
|
// - 同一 pass 内对同一资源"读+写"(input attachment / RWM)未建模
|
||||||
//
|
//
|
||||||
|
|
||||||
@@ -98,6 +97,10 @@ namespace Engine::RG {
|
|||||||
vk::ImageView View(TextureHandle h) const;
|
vk::ImageView View(TextureHandle h) const;
|
||||||
vk::Image Image(TextureHandle h) const;
|
vk::Image Image(TextureHandle h) const;
|
||||||
|
|
||||||
|
// 帧历史资源:上一帧槽位的物理镜像((slot + framesInFlight - 1) % F)。
|
||||||
|
// 采样描述符经 GetPhysicalView(h, 历史槽位) 预绑(同阴影图模式)。
|
||||||
|
u32 HistorySlot(u32 frameSlot) const;
|
||||||
|
|
||||||
class RenderGraph* graph = nullptr;
|
class RenderGraph* graph = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -131,6 +134,10 @@ namespace Engine::RG {
|
|||||||
};
|
};
|
||||||
std::vector<SlotState> slots;
|
std::vector<SlotState> slots;
|
||||||
|
|
||||||
|
// 是否存在帧历史读(Compile 期校验:历史资源必须有同帧正常使用,
|
||||||
|
// 否则没有物理镜像可读)
|
||||||
|
bool hasHistoryUse = false;
|
||||||
|
|
||||||
// Compile 期状态链解算用(执行序上的"当前"状态)
|
// Compile 期状态链解算用(执行序上的"当前"状态)
|
||||||
vk::ImageLayout chainLayout = vk::ImageLayout::eUndefined;
|
vk::ImageLayout chainLayout = vk::ImageLayout::eUndefined;
|
||||||
vk::PipelineStageFlags2 chainStage{};
|
vk::PipelineStageFlags2 chainStage{};
|
||||||
@@ -150,6 +157,7 @@ namespace Engine::RG {
|
|||||||
u32 resIndex;
|
u32 resIndex;
|
||||||
ResourceUse use;
|
ResourceUse use;
|
||||||
vk::ShaderStageFlags readStage; // ShaderRead 时的消费 stage
|
vk::ShaderStageFlags readStage; // ShaderRead 时的消费 stage
|
||||||
|
bool history = false; // 帧历史读:读上一帧槽位的物理镜像
|
||||||
};
|
};
|
||||||
std::vector<Use> uses;
|
std::vector<Use> uses;
|
||||||
ExecuteFn exec;
|
ExecuteFn exec;
|
||||||
@@ -161,10 +169,12 @@ namespace Engine::RG {
|
|||||||
// steady = 该槽位已被使用过(从 resting 状态出发)
|
// steady = 该槽位已被使用过(从 resting 状态出发)
|
||||||
// first = 该槽位首次使用(从 Undefined 出发)
|
// first = 该槽位首次使用(从 Undefined 出发)
|
||||||
// imported 资源恒用 first(acquire 后 layout 语义上为 Undefined)
|
// imported 资源恒用 first(acquire 后 layout 语义上为 Undefined)
|
||||||
|
// history = 帧历史读(操作上一帧槽位的镜像,steady 变体带跨帧可见性同步)
|
||||||
struct BarrierPair {
|
struct BarrierPair {
|
||||||
u32 resIndex;
|
u32 resIndex;
|
||||||
vk::ImageMemoryBarrier2 steady;
|
vk::ImageMemoryBarrier2 steady;
|
||||||
vk::ImageMemoryBarrier2 first;
|
vk::ImageMemoryBarrier2 first;
|
||||||
|
bool history = false;
|
||||||
};
|
};
|
||||||
std::vector<BarrierPair> barriers;
|
std::vector<BarrierPair> barriers;
|
||||||
};
|
};
|
||||||
@@ -195,6 +205,11 @@ namespace Engine::RG {
|
|||||||
// 注意:物理镜像按槽位各一份,槽位变化/重编译后必须重新获取。
|
// 注意:物理镜像按槽位各一份,槽位变化/重编译后必须重新获取。
|
||||||
vk::ImageView GetPhysicalView(TextureHandle h, u32 frameSlot) const;
|
vk::ImageView GetPhysicalView(TextureHandle h, u32 frameSlot) const;
|
||||||
|
|
||||||
|
// 帧历史槽位 = 上一帧使用的槽位:(slot - 1 + F) mod F
|
||||||
|
u32 HistorySlot(u32 frameSlot) const {
|
||||||
|
return (frameSlot + m_FramesInFlight - 1) % m_FramesInFlight;
|
||||||
|
}
|
||||||
|
|
||||||
// ---- Pass 声明 ----
|
// ---- Pass 声明 ----
|
||||||
|
|
||||||
class PassBuilder {
|
class PassBuilder {
|
||||||
@@ -202,6 +217,12 @@ namespace Engine::RG {
|
|||||||
PassBuilder& Write(TextureHandle h, ResourceUse use);
|
PassBuilder& Write(TextureHandle h, ResourceUse use);
|
||||||
PassBuilder& Read(TextureHandle h, ResourceUse use,
|
PassBuilder& Read(TextureHandle h, ResourceUse use,
|
||||||
vk::ShaderStageFlags stage = vk::ShaderStageFlagBits::eFragment);
|
vk::ShaderStageFlags stage = vk::ShaderStageFlagBits::eFragment);
|
||||||
|
// 帧历史读:读上一帧槽位的物理镜像(TAA/运动模糊/时域滤波用)。
|
||||||
|
// - 不产生图内边:跨帧依赖由同队列提交序 + 历史 barrier 保证
|
||||||
|
// - 资源必须有同帧正常使用(否则无物理镜像),Compile 期校验
|
||||||
|
// - 采样描述符在 pass 内用 GetPhysicalView(h, HistorySlot(slot)) 预绑
|
||||||
|
PassBuilder& ReadHistory(TextureHandle h, ResourceUse use,
|
||||||
|
vk::ShaderStageFlags stage = vk::ShaderStageFlagBits::eFragment);
|
||||||
PassBuilder& SideEffect();
|
PassBuilder& SideEffect();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ namespace Engine {
|
|||||||
bool useACES = true;
|
bool useACES = true;
|
||||||
f32 bloomThreshold = 1.0f; // HDR 域亮度阈值
|
f32 bloomThreshold = 1.0f; // HDR 域亮度阈值
|
||||||
f32 bloomStrength = 0.8f; // 叠加强度
|
f32 bloomStrength = 0.8f; // 叠加强度
|
||||||
|
bool showTemporalDiff = false; // 调试视图:显示帧历史差异
|
||||||
};
|
};
|
||||||
|
|
||||||
class FullscreenGpu {
|
class FullscreenGpu {
|
||||||
@@ -48,10 +49,11 @@ namespace Engine {
|
|||||||
FullscreenGpu(const FullscreenGpu&) = delete;
|
FullscreenGpu(const FullscreenGpu&) = delete;
|
||||||
FullscreenGpu& operator=(const FullscreenGpu&) = delete;
|
FullscreenGpu& operator=(const FullscreenGpu&) = delete;
|
||||||
|
|
||||||
vk::DescriptorSetLayout setLayout; // Set0 = {输入0, 输入1}
|
vk::DescriptorSetLayout setLayout; // Set0 = {输入0, 输入1, 调试输入}
|
||||||
vk::PipelineLayout layout; // push 16B(FullscreenPush)
|
vk::PipelineLayout layout; // push 16B(FullscreenPush)
|
||||||
vk::Sampler sampler;
|
vk::Sampler sampler;
|
||||||
vk::ShaderEXT vert{}, extractFrag{}, blurFrag{}, tonemapFrag{};
|
vk::ShaderEXT vert{}, extractFrag{}, blurFrag{}, tonemapFrag{};
|
||||||
|
vk::ShaderEXT diffFrag{}; // temporal_diff(帧历史调试)
|
||||||
|
|
||||||
// 各 pass 自己持有 per-slot set;分配统一走这里
|
// 各 pass 自己持有 per-slot set;分配统一走这里
|
||||||
vk::DescriptorSet AllocateSet();
|
vk::DescriptorSet AllocateSet();
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/23.
|
||||||
|
//
|
||||||
|
// TemporalDiffPass:帧历史链路调试视图。
|
||||||
|
// 读 HDRColor 的帧历史镜像(ReadHistory)与当前帧,输出 |prev − cur| × 增益。
|
||||||
|
// 静止画面纯黑、运动边缘发光 = 帧历史机制正确;噪声/残影错位 = 有 bug。
|
||||||
|
// 也是 RenderGraph::ReadHistory 的参考实现(TAA 照此模式接入)。
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef HELLOVULKAN_TEMPORALDIFFPASS_H
|
||||||
|
#define HELLOVULKAN_TEMPORALDIFFPASS_H
|
||||||
|
|
||||||
|
#include <Engine/Renderer/Passes/RenderPass.h>
|
||||||
|
#include <Engine/Renderer/Passes/FullscreenGpu.h>
|
||||||
|
#include <vulkan/vulkan.hpp>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Engine::Passes {
|
||||||
|
|
||||||
|
class TemporalDiffPass final : public RenderPass {
|
||||||
|
public:
|
||||||
|
TemporalDiffPass(FullscreenGpu& gpu, f32 gain,
|
||||||
|
RG::TextureHandle hdrColor, RG::TextureHandle output);
|
||||||
|
~TemporalDiffPass() override = default;
|
||||||
|
|
||||||
|
void Register(RG::RenderGraph& rg) override;
|
||||||
|
void OnTargetsRebuilt(RG::RenderGraph& rg) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
FullscreenGpu& m_Gpu;
|
||||||
|
f32 m_Gain;
|
||||||
|
RG::TextureHandle m_HDR;
|
||||||
|
RG::TextureHandle m_Output;
|
||||||
|
|
||||||
|
std::vector<vk::DescriptorSet> m_Sets; // per-slot
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Engine::Passes
|
||||||
|
|
||||||
|
#endif //HELLOVULKAN_TEMPORALDIFFPASS_H
|
||||||
@@ -20,7 +20,7 @@ namespace Engine::Passes {
|
|||||||
public:
|
public:
|
||||||
TonemapPass(FullscreenGpu& gpu, PostProcessParams& params,
|
TonemapPass(FullscreenGpu& gpu, PostProcessParams& params,
|
||||||
RG::TextureHandle hdrColor, RG::TextureHandle bloomA,
|
RG::TextureHandle hdrColor, RG::TextureHandle bloomA,
|
||||||
RG::TextureHandle backbuffer);
|
RG::TextureHandle temporalDiff, RG::TextureHandle backbuffer);
|
||||||
~TonemapPass() override = default;
|
~TonemapPass() override = default;
|
||||||
|
|
||||||
void Register(RG::RenderGraph& rg) override;
|
void Register(RG::RenderGraph& rg) override;
|
||||||
@@ -31,6 +31,7 @@ namespace Engine::Passes {
|
|||||||
PostProcessParams& m_Params;
|
PostProcessParams& m_Params;
|
||||||
RG::TextureHandle m_HDR;
|
RG::TextureHandle m_HDR;
|
||||||
RG::TextureHandle m_A;
|
RG::TextureHandle m_A;
|
||||||
|
RG::TextureHandle m_TemporalDiff; // 调试视图输入(当前帧槽位)
|
||||||
RG::TextureHandle m_Backbuffer;
|
RG::TextureHandle m_Backbuffer;
|
||||||
|
|
||||||
std::vector<vk::DescriptorSet> m_Sets; // per-slot
|
std::vector<vk::DescriptorSet> m_Sets; // per-slot
|
||||||
|
|||||||
@@ -79,6 +79,9 @@ namespace Engine {
|
|||||||
void SetBloomThreshold(f32 v);
|
void SetBloomThreshold(f32 v);
|
||||||
f32 GetBloomStrength() const;
|
f32 GetBloomStrength() const;
|
||||||
void SetBloomStrength(f32 v);
|
void SetBloomStrength(f32 v);
|
||||||
|
// 调试视图:显示帧历史差异(TemporalDiff pass 产出)
|
||||||
|
bool GetShowTemporalDiff() const;
|
||||||
|
void SetShowTemporalDiff(bool b);
|
||||||
|
|
||||||
// pass 级 GPU 耗时(最近完成帧,毫秒;与 GetPassGpuMs 下标对应)
|
// pass 级 GPU 耗时(最近完成帧,毫秒;与 GetPassGpuMs 下标对应)
|
||||||
const std::vector<std::string>& GetPassNames() const { return m_PassNames; }
|
const std::vector<std::string>& GetPassNames() const { return m_PassNames; }
|
||||||
@@ -125,6 +128,7 @@ namespace Engine {
|
|||||||
RG::TextureHandle m_HDRColor{}; // Forward 输出(线性 HDR),Tonemap 归一
|
RG::TextureHandle m_HDRColor{}; // Forward 输出(线性 HDR),Tonemap 归一
|
||||||
RG::TextureHandle m_BloomA{}; // 半分辨率:提取 + 垂直模糊结果
|
RG::TextureHandle m_BloomA{}; // 半分辨率:提取 + 垂直模糊结果
|
||||||
RG::TextureHandle m_BloomB{}; // 半分辨率:水平模糊中间结果
|
RG::TextureHandle m_BloomB{}; // 半分辨率:水平模糊中间结果
|
||||||
|
RG::TextureHandle m_TemporalDiff{}; // 半分辨率:帧历史差异(调试)
|
||||||
|
|
||||||
// pass 共享的可调参数(Renderer 持有,App 层访问器转发)
|
// pass 共享的可调参数(Renderer 持有,App 层访问器转发)
|
||||||
Scope<Passes::PostProcessParams> m_PostParams;
|
Scope<Passes::PostProcessParams> m_PostParams;
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ namespace Engine
|
|||||||
|
|
||||||
vk::Format m_Format;
|
vk::Format m_Format;
|
||||||
vk::Extent2D m_Extent;
|
vk::Extent2D m_Extent;
|
||||||
bool m_VSync = false;
|
bool m_VSync = true;
|
||||||
};
|
};
|
||||||
} // namespace Engine
|
} // namespace Engine
|
||||||
|
|
||||||
|
|||||||
@@ -130,6 +130,23 @@ namespace Engine::RG {
|
|||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RenderGraph::PassBuilder& RenderGraph::PassBuilder::ReadHistory(
|
||||||
|
TextureHandle h, ResourceUse use, vk::ShaderStageFlags stage)
|
||||||
|
{
|
||||||
|
m_Graph.CheckMutable();
|
||||||
|
m_Graph.CheckHandle(h);
|
||||||
|
auto& entry = m_Graph.m_Textures[h.index];
|
||||||
|
if (entry.imported) {
|
||||||
|
// swapchain image 不按槽位持久存在,历史读无意义
|
||||||
|
throw std::runtime_error(
|
||||||
|
"RenderGraph: history read of imported resource '" + entry.name +
|
||||||
|
"' is not supported");
|
||||||
|
}
|
||||||
|
entry.hasHistoryUse = true;
|
||||||
|
m_Graph.m_Passes[m_PassIndex].uses.push_back({h.index, use, stage, true});
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
RenderGraph::PassBuilder& RenderGraph::PassBuilder::SideEffect()
|
RenderGraph::PassBuilder& RenderGraph::PassBuilder::SideEffect()
|
||||||
{
|
{
|
||||||
m_Graph.CheckMutable();
|
m_Graph.CheckMutable();
|
||||||
@@ -265,6 +282,7 @@ namespace Engine::RG {
|
|||||||
|
|
||||||
bool keep = pass.sideEffect;
|
bool keep = pass.sideEffect;
|
||||||
for (const auto& use : pass.uses) {
|
for (const auto& use : pass.uses) {
|
||||||
|
// 帧历史读照常参与:它消费了上一帧的产出,生产者必须存活
|
||||||
if (IsWriteUse(use.use) && readByKept[use.resIndex]) {
|
if (IsWriteUse(use.use) && readByKept[use.resIndex]) {
|
||||||
keep = true;
|
keep = true;
|
||||||
} else if (m_Textures[use.resIndex].imported) {
|
} else if (m_Textures[use.resIndex].imported) {
|
||||||
@@ -317,6 +335,11 @@ namespace Engine::RG {
|
|||||||
auto& lw = lastWriter[use.resIndex];
|
auto& lw = lastWriter[use.resIndex];
|
||||||
auto& readers = pendingReaders[use.resIndex];
|
auto& readers = pendingReaders[use.resIndex];
|
||||||
|
|
||||||
|
// 帧历史读不建边:它消费的是上一帧槽位的物理镜像(与本帧
|
||||||
|
// 任何写操作无资源竞争),跨帧依赖由同队列提交序 + 历史
|
||||||
|
// barrier(ALL_COMMANDS 可见性同步)保证
|
||||||
|
if (use.history) continue;
|
||||||
|
|
||||||
if (IsWriteUse(use.use)) {
|
if (IsWriteUse(use.use)) {
|
||||||
for (u32 r : readers) addEdge(r, i); // WAR
|
for (u32 r : readers) addEdge(r, i); // WAR
|
||||||
readers.clear();
|
readers.clear();
|
||||||
@@ -363,6 +386,7 @@ namespace Engine::RG {
|
|||||||
for (u32 pos = 0; pos < m_ExecutionOrder.size(); ++pos) {
|
for (u32 pos = 0; pos < m_ExecutionOrder.size(); ++pos) {
|
||||||
const u32 passIdx = m_ExecutionOrder[pos];
|
const u32 passIdx = m_ExecutionOrder[pos];
|
||||||
for (const auto& use : m_Passes[passIdx].uses) {
|
for (const auto& use : m_Passes[passIdx].uses) {
|
||||||
|
if (use.history) continue; // 帧历史读不改变本帧状态链与生命周期
|
||||||
auto& entry = m_Textures[use.resIndex];
|
auto& entry = m_Textures[use.resIndex];
|
||||||
if (entry.firstPass < 0) entry.firstPass = static_cast<i32>(pos);
|
if (entry.firstPass < 0) entry.firstPass = static_cast<i32>(pos);
|
||||||
entry.lastPass = static_cast<i32>(pos);
|
entry.lastPass = static_cast<i32>(pos);
|
||||||
@@ -387,6 +411,7 @@ namespace Engine::RG {
|
|||||||
// ---- Pass A:状态链推演 ----
|
// ---- Pass A:状态链推演 ----
|
||||||
for (const u32 passIdx : m_ExecutionOrder) {
|
for (const u32 passIdx : m_ExecutionOrder) {
|
||||||
for (const auto& use : m_Passes[passIdx].uses) {
|
for (const auto& use : m_Passes[passIdx].uses) {
|
||||||
|
if (use.history) continue; // 历史读操作另一槽位的镜像,不入本帧链
|
||||||
auto& entry = m_Textures[use.resIndex];
|
auto& entry = m_Textures[use.resIndex];
|
||||||
entry.chainLayout = UseLayout(use.use);
|
entry.chainLayout = UseLayout(use.use);
|
||||||
entry.chainStage = UseDstStage(use);
|
entry.chainStage = UseDstStage(use);
|
||||||
@@ -416,6 +441,36 @@ namespace Engine::RG {
|
|||||||
for (const auto& use : pass.uses) {
|
for (const auto& use : pass.uses) {
|
||||||
auto& entry = m_Textures[use.resIndex];
|
auto& entry = m_Textures[use.resIndex];
|
||||||
|
|
||||||
|
// ---- 帧历史读:操作上一帧槽位的物理镜像,不进入本帧状态链。
|
||||||
|
// first = 该槽位镜像从未用过(图刚编译/重编译),old=Undefined
|
||||||
|
// steady = 跨帧可见性同步:src=ALL_COMMANDS|MEMORY_WRITE
|
||||||
|
// 覆盖上一帧提交对该镜像的全部写入(同队列提交序
|
||||||
|
// 保证 barrier 执行时上一帧命令已全部完成,
|
||||||
|
// ALL_COMMANDS 把可用性+可见性显式闭合),
|
||||||
|
// old/new = resting layout(历史镜像静止在读布局)
|
||||||
|
if (use.history) {
|
||||||
|
const vk::ImageLayout desiredLayout = UseLayout(use.use);
|
||||||
|
const vk::PipelineStageFlags2 dstStage = UseDstStage(use);
|
||||||
|
const vk::AccessFlags2 dstAccess = UseDstAccess(use);
|
||||||
|
|
||||||
|
vk::ImageMemoryBarrier2 first;
|
||||||
|
first.setSrcStageMask(vk::PipelineStageFlagBits2::eNone)
|
||||||
|
.setSrcAccessMask(vk::AccessFlagBits2::eNone)
|
||||||
|
.setDstStageMask(dstStage)
|
||||||
|
.setDstAccessMask(dstAccess)
|
||||||
|
.setOldLayout(vk::ImageLayout::eUndefined)
|
||||||
|
.setNewLayout(desiredLayout)
|
||||||
|
.setSubresourceRange({entry.aspect, 0, 1, 0, 1});
|
||||||
|
|
||||||
|
vk::ImageMemoryBarrier2 steady = first;
|
||||||
|
steady.setSrcStageMask(vk::PipelineStageFlagBits2::eAllCommands)
|
||||||
|
.setSrcAccessMask(vk::AccessFlagBits2::eMemoryWrite)
|
||||||
|
.setOldLayout(entry.restingLayout);
|
||||||
|
|
||||||
|
pass.barriers.push_back({use.resIndex, steady, first, true});
|
||||||
|
continue; // 不推进 chain:本帧链只描述当前槽位镜像
|
||||||
|
}
|
||||||
|
|
||||||
const vk::ImageLayout desiredLayout = UseLayout(use.use);
|
const vk::ImageLayout desiredLayout = UseLayout(use.use);
|
||||||
const vk::PipelineStageFlags2 dstStage = UseDstStage(use);
|
const vk::PipelineStageFlags2 dstStage = UseDstStage(use);
|
||||||
const vk::AccessFlags2 dstAccess = UseDstAccess(use);
|
const vk::AccessFlags2 dstAccess = UseDstAccess(use);
|
||||||
@@ -451,6 +506,17 @@ namespace Engine::RG {
|
|||||||
steady = makeBarrier(entry.restingLayout,
|
steady = makeBarrier(entry.restingLayout,
|
||||||
entry.restingStage,
|
entry.restingStage,
|
||||||
entry.restingAccess);
|
entry.restingAccess);
|
||||||
|
if (entry.hasHistoryUse) {
|
||||||
|
// 历史读打破了"同槽位两次进入之间有 fence 全等保"
|
||||||
|
// 的前提(上一帧对该槽位镜像的历史读不在本槽位
|
||||||
|
// fence 的保护范围内),src 升级为 ALL_COMMANDS
|
||||||
|
// 覆盖跨帧的一切触碰
|
||||||
|
steady.setSrcStageMask(
|
||||||
|
vk::PipelineStageFlagBits2::eAllCommands)
|
||||||
|
.setSrcAccessMask(
|
||||||
|
vk::AccessFlagBits2::eMemoryWrite |
|
||||||
|
vk::AccessFlagBits2::eMemoryRead);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pass.barriers.push_back({use.resIndex, steady, first});
|
pass.barriers.push_back({use.resIndex, steady, first});
|
||||||
@@ -481,7 +547,16 @@ namespace Engine::RG {
|
|||||||
void RenderGraph::AllocatePhysical()
|
void RenderGraph::AllocatePhysical()
|
||||||
{
|
{
|
||||||
for (auto& entry : m_Textures) {
|
for (auto& entry : m_Textures) {
|
||||||
if (entry.imported || entry.firstPass < 0) continue;
|
if (entry.imported) continue;
|
||||||
|
|
||||||
|
// 帧历史校验:历史资源必须有同帧正常使用(否则既无产出也无镜像)
|
||||||
|
if (entry.hasHistoryUse && entry.firstPass < 0) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"RenderGraph: history-read resource '" + entry.name +
|
||||||
|
"' has no same-frame use (nothing to read history of)");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.firstPass < 0) continue;
|
||||||
|
|
||||||
// 显式尺寸优先;否则 = 目标尺寸 × sizeScale(后处理降采样)
|
// 显式尺寸优先;否则 = 目标尺寸 × sizeScale(后处理降采样)
|
||||||
vk::Extent2D extent{
|
vk::Extent2D extent{
|
||||||
@@ -649,21 +724,27 @@ namespace Engine::RG {
|
|||||||
for (const auto& bp : pass.barriers) {
|
for (const auto& bp : pass.barriers) {
|
||||||
auto& entry = m_Textures[bp.resIndex];
|
auto& entry = m_Textures[bp.resIndex];
|
||||||
|
|
||||||
|
u32 imageSlot = frame.frameSlot;
|
||||||
bool useFirst;
|
bool useFirst;
|
||||||
if (entry.imported) {
|
if (entry.imported) {
|
||||||
useFirst = true; // 恒为 Undefined 起点
|
useFirst = true; // 恒为 Undefined 起点
|
||||||
|
} else if (bp.history) {
|
||||||
|
// 帧历史:操作上一帧槽位的物理镜像。
|
||||||
|
// used 标记持久(跨帧),首帧走 first,之后走 steady
|
||||||
|
imageSlot = HistorySlot(frame.frameSlot);
|
||||||
|
useFirst = !entry.slots[imageSlot].used;
|
||||||
|
entry.slots[imageSlot].used = true;
|
||||||
} else {
|
} else {
|
||||||
auto& slot = entry.slots[frame.frameSlot];
|
useFirst = !entry.slots[imageSlot].used;
|
||||||
useFirst = !slot.used;
|
entry.slots[imageSlot].used = true;
|
||||||
slot.used = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
vk::ImageMemoryBarrier2 b = useFirst ? bp.first : bp.steady;
|
vk::ImageMemoryBarrier2 b = useFirst ? bp.first : bp.steady;
|
||||||
// image 句柄在执行期绑定:imported 用当帧 acquire 的 image,
|
// image 句柄在执行期绑定:imported 用当帧 acquire 的 image,
|
||||||
// created 用当前帧槽位的物理镜像(每个槽位各有一份)
|
// created 用当前帧槽位(历史 barrier 用上一帧槽位)的物理镜像
|
||||||
b.setImage(entry.imported
|
b.setImage(entry.imported
|
||||||
? entry.importedImage
|
? entry.importedImage
|
||||||
: entry.physical[frame.frameSlot]->image);
|
: entry.physical[imageSlot]->image);
|
||||||
m_ScratchBarriers.push_back(b);
|
m_ScratchBarriers.push_back(b);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -707,6 +788,11 @@ namespace Engine::RG {
|
|||||||
return entry.physical[frame.frameSlot]->image;
|
return entry.physical[frame.frameSlot]->image;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
u32 RenderGraphContext::HistorySlot(u32 frameSlot) const
|
||||||
|
{
|
||||||
|
return graph->HistorySlot(frameSlot);
|
||||||
|
}
|
||||||
|
|
||||||
// ---------- 工具 / 统计 ----------
|
// ---------- 工具 / 统计 ----------
|
||||||
|
|
||||||
TextureEntry& RenderGraph::GetEntry(TextureHandle h)
|
TextureEntry& RenderGraph::GetEntry(TextureHandle h)
|
||||||
|
|||||||
@@ -16,12 +16,16 @@ namespace Engine::Passes
|
|||||||
ShaderManager& shaders)
|
ShaderManager& shaders)
|
||||||
: m_Device(device), m_Descriptors(descriptors)
|
: m_Device(device), m_Descriptors(descriptors)
|
||||||
{
|
{
|
||||||
// Set0 = {HDR 颜色图, Bloom 图},push = 16B 通用结构
|
// Set0 = {HDR 颜色图, Bloom 图, 调试输入},push = 16B 通用结构
|
||||||
|
//(binding2 只被 tonemap 的调试视图使用;bloom 系 shader 静态
|
||||||
|
// 使用的 binding 是布局的子集,合法)
|
||||||
std::vector<DescriptorBinding> bindings = {
|
std::vector<DescriptorBinding> bindings = {
|
||||||
{0, vk::DescriptorType::eCombinedImageSampler,
|
{0, vk::DescriptorType::eCombinedImageSampler,
|
||||||
vk::ShaderStageFlagBits::eFragment},
|
vk::ShaderStageFlagBits::eFragment},
|
||||||
{1, vk::DescriptorType::eCombinedImageSampler,
|
{1, vk::DescriptorType::eCombinedImageSampler,
|
||||||
vk::ShaderStageFlagBits::eFragment},
|
vk::ShaderStageFlagBits::eFragment},
|
||||||
|
{2, vk::DescriptorType::eCombinedImageSampler,
|
||||||
|
vk::ShaderStageFlagBits::eFragment},
|
||||||
};
|
};
|
||||||
setLayout = descriptors.CreateLayout(bindings);
|
setLayout = descriptors.CreateLayout(bindings);
|
||||||
|
|
||||||
@@ -59,13 +63,19 @@ namespace Engine::Passes
|
|||||||
vk::ShaderStageFlagBits::eFragment,
|
vk::ShaderStageFlagBits::eFragment,
|
||||||
std::vector<vk::DescriptorSetLayout>{setLayout},
|
std::vector<vk::DescriptorSetLayout>{setLayout},
|
||||||
static_cast<u32>(sizeof(FullscreenPush)));
|
static_cast<u32>(sizeof(FullscreenPush)));
|
||||||
|
auto diffF = shaders.CreateFromFile(
|
||||||
|
shaderDir + "temporal_diff.frag.spv",
|
||||||
|
vk::ShaderStageFlagBits::eFragment,
|
||||||
|
std::vector<vk::DescriptorSetLayout>{setLayout},
|
||||||
|
static_cast<u32>(sizeof(FullscreenPush)));
|
||||||
|
|
||||||
vert = vertShader.Handle;
|
vert = vertShader.Handle;
|
||||||
tonemapFrag = tonemapF.Handle;
|
tonemapFrag = tonemapF.Handle;
|
||||||
extractFrag = extractF.Handle;
|
extractFrag = extractF.Handle;
|
||||||
blurFrag = blurF.Handle;
|
blurFrag = blurF.Handle;
|
||||||
|
diffFrag = diffF.Handle;
|
||||||
|
|
||||||
if (!vert || !tonemapFrag || !extractFrag || !blurFrag) {
|
if (!vert || !tonemapFrag || !extractFrag || !blurFrag || !diffFrag) {
|
||||||
ENGINE_FATAL("FullscreenGpu: failed to create shader objects");
|
ENGINE_FATAL("FullscreenGpu: failed to create shader objects");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -78,6 +88,7 @@ namespace Engine::Passes
|
|||||||
if (tonemapFrag) vkDevice.destroyShaderEXT(tonemapFrag);
|
if (tonemapFrag) vkDevice.destroyShaderEXT(tonemapFrag);
|
||||||
if (extractFrag) vkDevice.destroyShaderEXT(extractFrag);
|
if (extractFrag) vkDevice.destroyShaderEXT(extractFrag);
|
||||||
if (blurFrag) vkDevice.destroyShaderEXT(blurFrag);
|
if (blurFrag) vkDevice.destroyShaderEXT(blurFrag);
|
||||||
|
if (diffFrag) vkDevice.destroyShaderEXT(diffFrag);
|
||||||
|
|
||||||
vkDevice.destroySampler(sampler);
|
vkDevice.destroySampler(sampler);
|
||||||
vkDevice.destroyPipelineLayout(layout);
|
vkDevice.destroyPipelineLayout(layout);
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <Engine/Renderer/Passes/TemporalDiffPass.h>
|
||||||
|
#include <Engine/Core/FrameContext.h>
|
||||||
|
|
||||||
|
namespace Engine::Passes
|
||||||
|
{
|
||||||
|
TemporalDiffPass::TemporalDiffPass(FullscreenGpu& gpu, f32 gain,
|
||||||
|
RG::TextureHandle hdrColor,
|
||||||
|
RG::TextureHandle output)
|
||||||
|
: m_Gpu(gpu), m_Gain(gain), m_HDR(hdrColor), m_Output(output)
|
||||||
|
{
|
||||||
|
m_Sets.resize(kMaxFramesInFlight);
|
||||||
|
for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) {
|
||||||
|
m_Sets[slot] = m_Gpu.AllocateSet();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TemporalDiffPass::Register(RG::RenderGraph& rg)
|
||||||
|
{
|
||||||
|
// 帧历史读 + 当前帧读:同一虚拟资源的两个槽位镜像在同一个
|
||||||
|
// pass 里采样(TAA 的标准形态)。历史读不建图内边,跨帧同步
|
||||||
|
// 由 RG 的历史 barrier(ALL_COMMANDS 可见性)保证。
|
||||||
|
rg.AddPass("TemporalDiff",
|
||||||
|
[this](RG::RenderGraphContext& ctx) {
|
||||||
|
m_Gpu.RecordFullscreen(ctx, ctx.View(m_Output), m_Gpu.diffFrag,
|
||||||
|
m_Sets[ctx.frame.frameSlot],
|
||||||
|
FullscreenPush{0.0f, m_Gain, 0.0f, 0.0f});
|
||||||
|
})
|
||||||
|
.ReadHistory(m_HDR, RG::ResourceUse::ShaderRead,
|
||||||
|
vk::ShaderStageFlagBits::eFragment)
|
||||||
|
.Read(m_HDR, RG::ResourceUse::ShaderRead,
|
||||||
|
vk::ShaderStageFlagBits::eFragment)
|
||||||
|
.Write(m_Output, RG::ResourceUse::ColorAttachment);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TemporalDiffPass::OnTargetsRebuilt(RG::RenderGraph& rg)
|
||||||
|
{
|
||||||
|
for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) {
|
||||||
|
// binding0 = 帧历史(上一帧槽位镜像),binding1 = 当前帧
|
||||||
|
m_Gpu.UpdateSetInput(m_Sets[slot], 0, rg, m_HDR,
|
||||||
|
rg.HistorySlot(slot));
|
||||||
|
m_Gpu.UpdateSetInput(m_Sets[slot], 1, rg, m_HDR, slot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Engine::Passes
|
||||||
@@ -9,9 +9,11 @@ namespace Engine::Passes
|
|||||||
{
|
{
|
||||||
TonemapPass::TonemapPass(FullscreenGpu& gpu, PostProcessParams& params,
|
TonemapPass::TonemapPass(FullscreenGpu& gpu, PostProcessParams& params,
|
||||||
RG::TextureHandle hdrColor, RG::TextureHandle bloomA,
|
RG::TextureHandle hdrColor, RG::TextureHandle bloomA,
|
||||||
|
RG::TextureHandle temporalDiff,
|
||||||
RG::TextureHandle backbuffer)
|
RG::TextureHandle backbuffer)
|
||||||
: m_Gpu(gpu), m_Params(params),
|
: m_Gpu(gpu), m_Params(params),
|
||||||
m_HDR(hdrColor), m_A(bloomA), m_Backbuffer(backbuffer)
|
m_HDR(hdrColor), m_A(bloomA), m_TemporalDiff(temporalDiff),
|
||||||
|
m_Backbuffer(backbuffer)
|
||||||
{
|
{
|
||||||
m_Sets.resize(kMaxFramesInFlight);
|
m_Sets.resize(kMaxFramesInFlight);
|
||||||
for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) {
|
for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) {
|
||||||
@@ -28,12 +30,17 @@ namespace Engine::Passes
|
|||||||
m_Sets[ctx.frame.frameSlot],
|
m_Sets[ctx.frame.frameSlot],
|
||||||
FullscreenPush{m_Params.exposure,
|
FullscreenPush{m_Params.exposure,
|
||||||
m_Params.useACES ? 1.0f : 0.0f,
|
m_Params.useACES ? 1.0f : 0.0f,
|
||||||
m_Params.bloomStrength, 0.0f});
|
m_Params.bloomStrength,
|
||||||
|
m_Params.showTemporalDiff ? 1.0f : 0.0f});
|
||||||
})
|
})
|
||||||
.Write(m_Backbuffer, RG::ResourceUse::ColorAttachment)
|
.Write(m_Backbuffer, RG::ResourceUse::ColorAttachment)
|
||||||
.Read(m_HDR, RG::ResourceUse::ShaderRead,
|
.Read(m_HDR, RG::ResourceUse::ShaderRead,
|
||||||
vk::ShaderStageFlagBits::eFragment)
|
vk::ShaderStageFlagBits::eFragment)
|
||||||
.Read(m_A, RG::ResourceUse::ShaderRead,
|
.Read(m_A, RG::ResourceUse::ShaderRead,
|
||||||
|
vk::ShaderStageFlagBits::eFragment)
|
||||||
|
// shader 静态采样 binding2(调试视图开关在 push constant 里),
|
||||||
|
// 声明读否则本 pass 的产出无人消费会被剔除、镜像也不会分配
|
||||||
|
.Read(m_TemporalDiff, RG::ResourceUse::ShaderRead,
|
||||||
vk::ShaderStageFlagBits::eFragment);
|
vk::ShaderStageFlagBits::eFragment);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,6 +49,7 @@ namespace Engine::Passes
|
|||||||
for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) {
|
for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) {
|
||||||
m_Gpu.UpdateSetInput(m_Sets[slot], 0, rg, m_HDR, slot);
|
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], 1, rg, m_A, slot);
|
||||||
|
m_Gpu.UpdateSetInput(m_Sets[slot], 2, rg, m_TemporalDiff, slot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
#include <Engine/Renderer/Passes/ForwardPass.h>
|
#include <Engine/Renderer/Passes/ForwardPass.h>
|
||||||
#include <Engine/Renderer/Passes/BloomPass.h>
|
#include <Engine/Renderer/Passes/BloomPass.h>
|
||||||
#include <Engine/Renderer/Passes/TonemapPass.h>
|
#include <Engine/Renderer/Passes/TonemapPass.h>
|
||||||
|
#include <Engine/Renderer/Passes/TemporalDiffPass.h>
|
||||||
#include <Engine/Renderer/Passes/ImGuiPass.h>
|
#include <Engine/Renderer/Passes/ImGuiPass.h>
|
||||||
#include <Engine/Renderer/Passes/PresentPass.h>
|
#include <Engine/Renderer/Passes/PresentPass.h>
|
||||||
#include <Engine/Vulkan/Device.h>
|
#include <Engine/Vulkan/Device.h>
|
||||||
@@ -143,6 +144,8 @@ namespace Engine
|
|||||||
void Renderer::SetBloomThreshold(f32 v) { m_PostParams->bloomThreshold = v; }
|
void Renderer::SetBloomThreshold(f32 v) { m_PostParams->bloomThreshold = v; }
|
||||||
f32 Renderer::GetBloomStrength() const { return m_PostParams->bloomStrength; }
|
f32 Renderer::GetBloomStrength() const { return m_PostParams->bloomStrength; }
|
||||||
void Renderer::SetBloomStrength(f32 v) { m_PostParams->bloomStrength = v; }
|
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; }
|
||||||
|
|
||||||
u32 Renderer::GetMainInstanceCount() const
|
u32 Renderer::GetMainInstanceCount() const
|
||||||
{
|
{
|
||||||
@@ -194,6 +197,12 @@ namespace Engine
|
|||||||
"BloomB",
|
"BloomB",
|
||||||
RG::TextureDesc{vk::Format::eR16G16B16A16Sfloat, 0, 0, 0.5f});
|
RG::TextureDesc{vk::Format::eR16G16B16A16Sfloat, 0, 0, 0.5f});
|
||||||
|
|
||||||
|
// 帧历史差异(调试视图):|上一帧 − 当前帧| × 增益。
|
||||||
|
// 帧历史机制(ReadHistory)的验证消费端,也是 TAA 的参考形态
|
||||||
|
m_TemporalDiff = m_RenderGraph->CreateTexture(
|
||||||
|
"TemporalDiff",
|
||||||
|
RG::TextureDesc{vk::Format::eR16G16B16A16Sfloat, 0, 0, 0.5f});
|
||||||
|
|
||||||
// ---- pass 注册:执行序由拓扑排序决定,与注册顺序无关 ----
|
// ---- pass 注册:执行序由拓扑排序决定,与注册顺序无关 ----
|
||||||
// 要加新 pass:new 一个 RenderPass 子类,在这里 push 一行。
|
// 要加新 pass:new 一个 RenderPass 子类,在这里 push 一行。
|
||||||
FrameSceneData& frame = m_SceneRenderer->Frame();
|
FrameSceneData& frame = m_SceneRenderer->Frame();
|
||||||
@@ -201,10 +210,13 @@ namespace Engine
|
|||||||
*m_SceneGpu, frame, m_ShadowMap));
|
*m_SceneGpu, frame, m_ShadowMap));
|
||||||
m_Passes.push_back(CreateScope<Passes::ForwardPass>(
|
m_Passes.push_back(CreateScope<Passes::ForwardPass>(
|
||||||
*m_SceneGpu, frame, m_HDRColor, m_Depth, m_ShadowMap));
|
*m_SceneGpu, frame, m_HDRColor, m_Depth, m_ShadowMap));
|
||||||
|
m_Passes.push_back(CreateScope<Passes::TemporalDiffPass>(
|
||||||
|
*m_FullscreenGpu, /*gain*/ 8.0f, m_HDRColor, m_TemporalDiff));
|
||||||
m_Passes.push_back(CreateScope<Passes::BloomPass>(
|
m_Passes.push_back(CreateScope<Passes::BloomPass>(
|
||||||
*m_FullscreenGpu, *m_PostParams, m_HDRColor, m_BloomA, m_BloomB));
|
*m_FullscreenGpu, *m_PostParams, m_HDRColor, m_BloomA, m_BloomB));
|
||||||
m_Passes.push_back(CreateScope<Passes::TonemapPass>(
|
m_Passes.push_back(CreateScope<Passes::TonemapPass>(
|
||||||
*m_FullscreenGpu, *m_PostParams, m_HDRColor, m_BloomA, m_Backbuffer));
|
*m_FullscreenGpu, *m_PostParams, m_HDRColor, m_BloomA,
|
||||||
|
m_TemporalDiff, m_Backbuffer));
|
||||||
m_Passes.push_back(CreateScope<Passes::ImGuiPass>(
|
m_Passes.push_back(CreateScope<Passes::ImGuiPass>(
|
||||||
&m_ImGuiLayer, m_Backbuffer));
|
&m_ImGuiLayer, m_Backbuffer));
|
||||||
m_Passes.push_back(CreateScope<Passes::PresentPass>(m_Backbuffer));
|
m_Passes.push_back(CreateScope<Passes::PresentPass>(m_Backbuffer));
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
// TemporalDiff:帧历史链路调试视图。
|
||||||
|
// 输出 |上一帧 HDR − 当前帧 HDR| × 增益:
|
||||||
|
// - 静止画面应接近纯黑(两帧内容一致)
|
||||||
|
// - 运动的实体边缘发光(时域差异可见)
|
||||||
|
// - 全屏噪声 / 错位残影 = 帧历史 barrier 或槽位镜像绑定错误
|
||||||
|
// 这是 RenderGraph 帧历史资源(ReadHistory)的验证消费端。
|
||||||
|
|
||||||
|
layout(location = 0) in vec2 fragUV;
|
||||||
|
layout(location = 0) out vec4 outColor;
|
||||||
|
|
||||||
|
layout(set = 0, binding = 0) uniform sampler2D prevImage; // 帧历史(上一帧槽位镜像)
|
||||||
|
layout(set = 0, binding = 1) uniform sampler2D currImage; // 当前帧
|
||||||
|
|
||||||
|
// 全屏 pass 通用 push(16B,程序级统一声明,VUID-08878)
|
||||||
|
layout(push_constant) uniform PushConstants {
|
||||||
|
float exposure; // 与其它全屏 pass 布局一致(本 pass 不用)
|
||||||
|
float gain; // 差异增益
|
||||||
|
float param1;
|
||||||
|
float param2;
|
||||||
|
} pc;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec3 prev = texture(prevImage, fragUV).rgb;
|
||||||
|
vec3 curr = texture(currImage, fragUV).rgb;
|
||||||
|
outColor = vec4(abs(prev - curr) * pc.gain, 1.0);
|
||||||
|
}
|
||||||
@@ -10,13 +10,14 @@ layout(location = 0) out vec4 outColor;
|
|||||||
|
|
||||||
layout(set = 0, binding = 0) uniform sampler2D hdrImage;
|
layout(set = 0, binding = 0) uniform sampler2D hdrImage;
|
||||||
layout(set = 0, binding = 1) uniform sampler2D bloomImage;
|
layout(set = 0, binding = 1) uniform sampler2D bloomImage;
|
||||||
|
layout(set = 0, binding = 2) uniform sampler2D temporalDiffImage; // 帧历史差异(调试视图)
|
||||||
|
|
||||||
// 全屏 pass 通用 push(16B,程序级统一声明,VUID-08878)
|
// 全屏 pass 通用 push(16B,程序级统一声明,VUID-08878)
|
||||||
layout(push_constant) uniform PushConstants {
|
layout(push_constant) uniform PushConstants {
|
||||||
float exposure; // 曝光系数
|
float exposure; // 曝光系数
|
||||||
float aces; // >0.5 启用 ACES 曲线,否则纯 gamma
|
float aces; // >0.5 启用 ACES 曲线,否则纯 gamma
|
||||||
float bloomStrength; // bloom 叠加强度(0 = 关闭)
|
float bloomStrength; // bloom 叠加强度(0 = 关闭)
|
||||||
float _pad0;
|
float debugView; // >0.5 显示帧历史差异视图(TemporalDiff)
|
||||||
} pc;
|
} pc;
|
||||||
|
|
||||||
vec3 acesFilm(vec3 x) {
|
vec3 acesFilm(vec3 x) {
|
||||||
@@ -24,6 +25,13 @@ vec3 acesFilm(vec3 x) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void main() {
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
// bloom 在 HDR 域叠加(半分辨率模糊图线性采样上采样)
|
// bloom 在 HDR 域叠加(半分辨率模糊图线性采样上采样)
|
||||||
vec4 hdrTex = texture(hdrImage, fragUV);
|
vec4 hdrTex = texture(hdrImage, fragUV);
|
||||||
vec3 hdr = hdrTex.rgb + texture(bloomImage, fragUV).rgb * pc.bloomStrength;
|
vec3 hdr = hdrTex.rgb + texture(bloomImage, fragUV).rgb * pc.bloomStrength;
|
||||||
|
|||||||
Reference in New Issue
Block a user