feat: TAA 时域抗锯齿(velocity + 邻域 clamp + 子像素抖动)

- Velocity buffer:Forward 第二颜色附件,vertex 计算 clip 空间运动向量
  (UBO 尾部追加 projNoJitter/prevView/prevProj;上一帧实例矩阵 SSBO
  Set0 binding2,与当前实例数组平行对齐——转动的物体速度正确不鬼影)
- 子像素抖动:SceneRenderer Halton(2,3)×8 序列,抖动只进光栅化投影
- TAA resolve pass:velocity 反推历史位置 + 3×3 邻域 clamp 去鬼影 +
  mix(cur,hist,0.9) 时域累积;TAAColor 自引用历史
- RG 历史读补 post-barrier:采样后恢复镜像 resting layout(TAA 的历史
  是 ColorAttachment layout,不恢复则下帧 entry oldLayout 脱节)
- 管线:Forward(HDR+Velocity) → TAA(TAAColor) → Bloom/Tonemap 改源 TAAColor
- 编译零错误、validation 零警告、8 秒冒烟通过(10 pass 全活跃)
This commit is contained in:
2026-09-23 13:29:31 +08:00
parent a29aed2803
commit 27b7f8785c
18 changed files with 369 additions and 39 deletions
@@ -177,6 +177,11 @@ namespace Engine::RG {
bool history = false;
};
std::vector<BarrierPair> barriers;
// 历史读的出口 barrier(exec 之后执行):历史镜像被临时转进读
// layout 采样,读完必须恢复 resting layout,否则下一帧该槽位
// entry barrier 的 oldLayout 与实际 layout 脱节
std::vector<BarrierPair> postBarriers;
};
// ---------- RenderGraph ----------
@@ -18,10 +18,11 @@ namespace Engine::Passes {
class ForwardPass final : public RenderPass {
public:
ForwardPass(SceneGpu& gpu, FrameSceneData& frame,
RG::TextureHandle hdrColor, RG::TextureHandle depth,
RG::TextureHandle shadowMap)
RG::TextureHandle hdrColor, RG::TextureHandle velocity,
RG::TextureHandle depth, RG::TextureHandle shadowMap)
: m_Gpu(gpu), m_Frame(frame),
m_HDRColor(hdrColor), m_Depth(depth), m_ShadowMap(shadowMap) {}
m_HDRColor(hdrColor), m_Velocity(velocity),
m_Depth(depth), m_ShadowMap(shadowMap) {}
void Register(RG::RenderGraph& rg) override;
@@ -29,6 +30,7 @@ namespace Engine::Passes {
SceneGpu& m_Gpu;
FrameSceneData& m_Frame;
RG::TextureHandle m_HDRColor;
RG::TextureHandle m_Velocity; // TAA 运动向量(第二颜色附件)
RG::TextureHandle m_Depth;
RG::TextureHandle m_ShadowMap;
};
@@ -54,6 +54,7 @@ namespace Engine {
vk::Sampler sampler;
vk::ShaderEXT vert{}, extractFrag{}, blurFrag{}, tonemapFrag{};
vk::ShaderEXT diffFrag{}; // temporal_diff(帧历史调试)
vk::ShaderEXT taaFrag{}; // taa(时域抗锯齿 resolve)
// 各 pass 自己持有 per-slot set;分配统一走这里
vk::DescriptorSet AllocateSet();
@@ -29,14 +29,15 @@ namespace Engine {
public:
SceneGpu(Device& device, DescriptorManager& descriptors,
ShaderManager& shaders,
UniformBuffer& ubo, UniformBuffer& objectBuffer);
UniformBuffer& ubo, UniformBuffer& objectBuffer,
UniformBuffer& prevObjectBuffer);
~SceneGpu();
SceneGpu(const SceneGpu&) = delete;
SceneGpu& operator=(const SceneGpu&) = delete;
// ---- set 布局(shader 契约,改绑定必须同步 shaders/)----
vk::DescriptorSetLayout uboSetLayout; // Set0 = {UBO, ObjectBuffer}
vk::DescriptorSetLayout uboSetLayout; // Set0 = {UBO, ObjectBuffer, PrevObjectBuffer}
vk::DescriptorSetLayout textureSetLayout; // Set1 = {albedo}
vk::DescriptorSetLayout shadowSetLayout; // Set2 = {shadow map}
vk::PipelineLayout forwardLayout; // 3 sets,无 push(GPUDriven 经 SSBO)
@@ -0,0 +1,42 @@
//
// Created by atdunbg on 2026/9/23.
//
// TAAPass:时域抗锯齿 resolve。
// 读当前帧 HDR + velocity + 历史累积(ReadHistory 自引用 TAAColor),
// 邻域 clamp 去鬼影后时域混合。TAAColor 既是输出也是下一帧的历史——
// 这是帧历史资源最典型的"自引用"形态(UE 的 history pool 同构)。
//
#ifndef HELLOVULKAN_TAAPASS_H
#define HELLOVULKAN_TAAPASS_H
#include <Engine/Renderer/Passes/RenderPass.h>
#include <Engine/Renderer/Passes/FullscreenGpu.h>
#include <vulkan/vulkan.hpp>
#include <vector>
namespace Engine::Passes {
class TAAPass final : public RenderPass {
public:
TAAPass(FullscreenGpu& gpu, f32 historyAlpha,
RG::TextureHandle hdrColor, RG::TextureHandle velocity,
RG::TextureHandle taaColor);
~TAAPass() override = default;
void Register(RG::RenderGraph& rg) override;
void OnTargetsRebuilt(RG::RenderGraph& rg) override;
private:
FullscreenGpu& m_Gpu;
f32 m_HistoryAlpha;
RG::TextureHandle m_HDR;
RG::TextureHandle m_Velocity;
RG::TextureHandle m_TAAColor; // 输出 = 下一帧的历史
std::vector<vk::DescriptorSet> m_Sets; // per-slot
};
} // namespace Engine::Passes
#endif //HELLOVULKAN_TAAPASS_H
+4 -1
View File
@@ -109,6 +109,7 @@ namespace Engine {
Scope<ShaderManager> m_ShaderManager;
Scope<UniformBuffer> m_UniformBuffer; // per-slot UBO ring
Scope<UniformBuffer> m_ObjectBuffer; // GPUDriven 实例矩阵 ring(SSBO)
Scope<UniformBuffer> m_PrevObjectBuffer; // 上一帧实例矩阵 ring(TAA velocity)
Scope<Passes::SceneGpu> m_SceneGpu; // 场景类 pass 共享装配
Scope<Passes::FullscreenGpu> m_FullscreenGpu; // 全屏 pass 共享装配
Scope<Swapchain> m_Swapchain;
@@ -125,7 +126,9 @@ namespace Engine {
RG::TextureHandle m_Backbuffer{};
RG::TextureHandle m_Depth{};
RG::TextureHandle m_ShadowMap{};
RG::TextureHandle m_HDRColor{}; // Forward 输出(线性 HDR),Tonemap 归一
RG::TextureHandle m_HDRColor{}; // Forward 输出(线性 HDR),TAA 归一
RG::TextureHandle m_Velocity{}; // Forward 输出(clip 空间运动向量)
RG::TextureHandle m_TAAColor{}; // TAA 累积输出(自引用历史),后处理源
RG::TextureHandle m_BloomA{}; // 半分辨率:提取 + 垂直模糊结果
RG::TextureHandle m_BloomB{}; // 半分辨率:水平模糊中间结果
RG::TextureHandle m_TemporalDiff{}; // 半分辨率:帧历史差异(调试)
+13 -2
View File
@@ -22,6 +22,7 @@
#include <Engine/Core/Types.h>
#include <Engine/Renderer/SceneTypes.h>
#include <vulkan/vulkan.hpp>
#include <unordered_map>
namespace Engine {
@@ -32,8 +33,9 @@ namespace Engine {
class SceneRenderer {
public:
// 从场景构建当帧渲染数据。无 primary 相机返回 false(渲染跳帧)。
// aspect 由调用方传入(跟随 swapchain,本类不感知窗口)。
bool BuildFrame(ECS::Scene& scene, Assets::AssetManager& assets, f32 aspect);
// targetExtent 用于 TAA 子像素抖动的 NDC 偏移折算。
bool BuildFrame(ECS::Scene& scene, Assets::AssetManager& assets,
vk::Extent2D targetExtent);
FrameSceneData& Frame() { return m_Frame; }
@@ -53,6 +55,15 @@ namespace Engine {
std::unordered_map<Mesh*, u32> m_MainLookup;
std::unordered_map<Mesh*, u32> m_ShadowLookup;
// ---- TAA 时域状态 ----
u64 m_FrameIndex = 0; // 抖动序列游标(Halton 2,3 ×8 采样)
glm::mat4 m_LastView{1.0f}; // 上一帧视图(velocity)
glm::mat4 m_LastProjNoJitter{1.0f}; // 上一帧未抖动投影(velocity)
// 实体的上一帧世界矩阵(key = entt 实体 integral)。
// 每帧重建 m_CurWorlds 后与 m_PrevWorlds swap——自动清理销毁实体
std::unordered_map<u32, glm::mat4> m_PrevWorlds;
std::unordered_map<u32, glm::mat4> m_CurWorlds;
bool m_TruncationWarned = false; // 实例缓冲打满只告警一次
};
+11 -2
View File
@@ -42,13 +42,19 @@ namespace Engine {
glm::vec4 color; // rgb = 颜色,a = 强度
};
// 镜像 simple_shader 的 UBO 布局(std140 语义由 shader 侧对齐)
// 镜像 simple_shader 的 UBO 布局(std140 语义由 shader 侧对齐)。
// 字段只能追加在尾部(shadow.vert 共用本 set 前缀,前插会错位)。
struct UniformBufferObject {
glm::mat4 view;
glm::mat4 proj;
glm::mat4 proj; // 抖动后的投影(光栅化用)
glm::mat4 lightSpace; // 阴影光源 VP(写入与采样共用同一矩阵)
glm::vec4 ambientColor;
LightData lights[kMaxLights];
// ---- TAA(尾部追加;velocity 在 vertex 里算)----
glm::mat4 projNoJitter; // 未抖动投影(velocity 当前帧 clip 用)
glm::mat4 prevView; // 上一帧视图(velocity)
glm::mat4 prevProj; // 上一帧未抖动投影(velocity)
};
// 每帧从场景收集的渲染数据(SceneRenderer 生产,pass 消费)。
@@ -58,6 +64,9 @@ namespace Engine {
// 实例矩阵数组 = [主视图段 | 阴影视图段](共享同一 per-slot SSBO)
std::vector<glm::mat4> instanceModels;
// 上一帧世界矩阵,与 instanceModels 平行对齐(同一下标 = 同一
// 实例的上一帧姿态,TAA velocity 用);首帧/新实体 = 当前矩阵
std::vector<glm::mat4> prevInstanceModels;
CullResult mainView; // 相机视锥 → Forward pass
CullResult shadowView; // 光源视锥 → Shadow pass(相机外实体仍投影子)