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:
@@ -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
|
||||
@@ -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{}; // 半分辨率:帧历史差异(调试)
|
||||
|
||||
@@ -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; // 实例缓冲打满只告警一次
|
||||
};
|
||||
|
||||
|
||||
@@ -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(相机外实体仍投影子)
|
||||
|
||||
|
||||
@@ -177,6 +177,7 @@ namespace Engine::RG {
|
||||
for (auto& pass : m_Passes) {
|
||||
pass.culled = false;
|
||||
pass.barriers.clear();
|
||||
pass.postBarriers.clear();
|
||||
}
|
||||
|
||||
for (auto& entry : m_Textures) ResolveUsage(entry);
|
||||
@@ -468,6 +469,20 @@ namespace Engine::RG {
|
||||
.setOldLayout(entry.restingLayout);
|
||||
|
||||
pass.barriers.push_back({use.resIndex, steady, first, true});
|
||||
|
||||
// 出口 barrier:采样完成后把历史镜像转回 resting layout。
|
||||
// (若 desired == resting 则是无害的空转换,但保留它以
|
||||
// 统一语义——src scope 同时给出读取完成的内存序)
|
||||
vk::ImageMemoryBarrier2 exit;
|
||||
exit.setSrcStageMask(dstStage)
|
||||
.setSrcAccessMask(dstAccess)
|
||||
.setDstStageMask(vk::PipelineStageFlagBits2::eNone)
|
||||
.setDstAccessMask(vk::AccessFlagBits2::eNone)
|
||||
.setOldLayout(desiredLayout)
|
||||
.setNewLayout(entry.restingLayout)
|
||||
.setSubresourceRange({entry.aspect, 0, 1, 0, 1});
|
||||
pass.postBarriers.push_back({use.resIndex, exit, exit, true});
|
||||
|
||||
continue; // 不推进 chain:本帧链只描述当前槽位镜像
|
||||
}
|
||||
|
||||
@@ -763,6 +778,21 @@ namespace Engine::RG {
|
||||
pass.exec(ctx);
|
||||
}
|
||||
|
||||
// 历史读出口:恢复上一帧槽位镜像的 resting layout
|
||||
if (!pass.postBarriers.empty()) {
|
||||
m_ScratchBarriers.clear();
|
||||
for (const auto& bp : pass.postBarriers) {
|
||||
auto& entry = m_Textures[bp.resIndex];
|
||||
const u32 histSlot = HistorySlot(frame.frameSlot);
|
||||
vk::ImageMemoryBarrier2 b = bp.steady;
|
||||
b.setImage(entry.physical[histSlot]->image);
|
||||
m_ScratchBarriers.push_back(b);
|
||||
}
|
||||
vk::DependencyInfo dep;
|
||||
dep.setImageMemoryBarriers(m_ScratchBarriers);
|
||||
frame.cmd.pipelineBarrier2(dep);
|
||||
}
|
||||
|
||||
if (m_QueryPool) {
|
||||
frame.cmd.writeTimestamp2(
|
||||
vk::PipelineStageFlagBits2::eBottomOfPipe,
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <Engine/Renderer/Material.h>
|
||||
|
||||
#include <array>
|
||||
#include <span>
|
||||
|
||||
namespace Engine::Passes
|
||||
{
|
||||
@@ -20,12 +21,21 @@ namespace Engine::Passes
|
||||
clearValue.color = vk::ClearColorValue(
|
||||
std::array<float, 4>{0.1f, 0.1f, 0.1f, 0.0f});
|
||||
|
||||
vk::RenderingAttachmentInfo colorAttachment;
|
||||
colorAttachment.setImageView(ctx.View(m_HDRColor))
|
||||
.setImageLayout(vk::ImageLayout::eColorAttachmentOptimal)
|
||||
.setLoadOp(vk::AttachmentLoadOp::eClear)
|
||||
.setStoreOp(vk::AttachmentStoreOp::eStore)
|
||||
.setClearValue(clearValue);
|
||||
vk::RenderingAttachmentInfo colorAttachments[2];
|
||||
colorAttachments[0].setImageView(ctx.View(m_HDRColor))
|
||||
.setImageLayout(vk::ImageLayout::eColorAttachmentOptimal)
|
||||
.setLoadOp(vk::AttachmentLoadOp::eClear)
|
||||
.setStoreOp(vk::AttachmentStoreOp::eStore)
|
||||
.setClearValue(clearValue);
|
||||
|
||||
// velocity 附件:清 0 = 静止(背景无几何 → 历史原位采样)
|
||||
vk::ClearValue velocityClear;
|
||||
velocityClear.color = vk::ClearColorValue(std::array<float, 4>{0.f, 0.f, 0.f, 0.f});
|
||||
colorAttachments[1].setImageView(ctx.View(m_Velocity))
|
||||
.setImageLayout(vk::ImageLayout::eColorAttachmentOptimal)
|
||||
.setLoadOp(vk::AttachmentLoadOp::eClear)
|
||||
.setStoreOp(vk::AttachmentStoreOp::eStore)
|
||||
.setClearValue(velocityClear);
|
||||
|
||||
vk::RenderingAttachmentInfo depthAttachment;
|
||||
depthAttachment.setImageView(ctx.View(m_Depth))
|
||||
@@ -35,10 +45,13 @@ namespace Engine::Passes
|
||||
.setClearValue(vk::ClearValue(
|
||||
vk::ClearDepthStencilValue(1.0f, 0)));
|
||||
|
||||
std::array<vk::RenderingAttachmentInfo, 2> colorArray = {
|
||||
colorAttachments[0], colorAttachments[1]};
|
||||
|
||||
vk::RenderingInfo renderingInfo;
|
||||
renderingInfo.setRenderArea({{0, 0}, ctx.extent})
|
||||
.setLayerCount(1)
|
||||
.setColorAttachments(colorAttachment)
|
||||
.setColorAttachments(colorArray)
|
||||
.setPDepthAttachment(&depthAttachment);
|
||||
|
||||
cmd.beginRendering(renderingInfo);
|
||||
@@ -95,8 +108,6 @@ namespace Engine::Passes
|
||||
cmd.setScissorWithCount(scissor);
|
||||
|
||||
vk::Bool32 blendEnable = VK_FALSE;
|
||||
cmd.setColorBlendEnableEXT(0, blendEnable);
|
||||
|
||||
vk::ColorBlendEquationEXT blendEquation;
|
||||
blendEquation.setSrcColorBlendFactor(vk::BlendFactor::eOne)
|
||||
.setDstColorBlendFactor(vk::BlendFactor::eZero)
|
||||
@@ -104,14 +115,20 @@ namespace Engine::Passes
|
||||
.setSrcAlphaBlendFactor(vk::BlendFactor::eOne)
|
||||
.setDstAlphaBlendFactor(vk::BlendFactor::eZero)
|
||||
.setAlphaBlendOp(vk::BlendOp::eAdd);
|
||||
cmd.setColorBlendEquationEXT(0, blendEquation);
|
||||
|
||||
vk::ColorComponentFlags colorWriteMask =
|
||||
vk::ColorComponentFlagBits::eR |
|
||||
vk::ColorComponentFlagBits::eG |
|
||||
vk::ColorComponentFlagBits::eB |
|
||||
vk::ColorComponentFlagBits::eA;
|
||||
cmd.setColorWriteMaskEXT(0, colorWriteMask);
|
||||
|
||||
// 两个颜色附件(HDR + velocity)都是无混合直写
|
||||
cmd.setColorBlendEnableEXT(0,
|
||||
std::array<vk::Bool32, 2>{blendEnable, blendEnable});
|
||||
cmd.setColorBlendEquationEXT(0,
|
||||
std::array<vk::ColorBlendEquationEXT, 2>{blendEquation, blendEquation});
|
||||
cmd.setColorWriteMaskEXT(0,
|
||||
std::array<vk::ColorComponentFlags, 2>{colorWriteMask, colorWriteMask});
|
||||
|
||||
// ---- 绘制:GPUDriven 按 mesh 分 batch(相机视锥剔除后)----
|
||||
// 实例矩阵已由 Renderer 写入 ObjectBuffer(per-slot SSBO);
|
||||
@@ -127,6 +144,7 @@ namespace Engine::Passes
|
||||
cmd.endRendering();
|
||||
})
|
||||
.Write(m_HDRColor, RG::ResourceUse::ColorAttachment)
|
||||
.Write(m_Velocity, RG::ResourceUse::ColorAttachment)
|
||||
.Write(m_Depth, RG::ResourceUse::DepthAttachment)
|
||||
.Read(m_ShadowMap, RG::ResourceUse::ShaderRead,
|
||||
vk::ShaderStageFlagBits::eFragment);
|
||||
|
||||
@@ -68,14 +68,21 @@ namespace Engine::Passes
|
||||
vk::ShaderStageFlagBits::eFragment,
|
||||
std::vector<vk::DescriptorSetLayout>{setLayout},
|
||||
static_cast<u32>(sizeof(FullscreenPush)));
|
||||
auto taaF = shaders.CreateFromFile(
|
||||
shaderDir + "taa.frag.spv",
|
||||
vk::ShaderStageFlagBits::eFragment,
|
||||
std::vector<vk::DescriptorSetLayout>{setLayout},
|
||||
static_cast<u32>(sizeof(FullscreenPush)));
|
||||
|
||||
vert = vertShader.Handle;
|
||||
tonemapFrag = tonemapF.Handle;
|
||||
extractFrag = extractF.Handle;
|
||||
blurFrag = blurF.Handle;
|
||||
diffFrag = diffF.Handle;
|
||||
taaFrag = taaF.Handle;
|
||||
|
||||
if (!vert || !tonemapFrag || !extractFrag || !blurFrag || !diffFrag) {
|
||||
if (!vert || !tonemapFrag || !extractFrag || !blurFrag ||
|
||||
!diffFrag || !taaFrag) {
|
||||
ENGINE_FATAL("FullscreenGpu: failed to create shader objects");
|
||||
}
|
||||
}
|
||||
@@ -89,6 +96,7 @@ namespace Engine::Passes
|
||||
if (extractFrag) vkDevice.destroyShaderEXT(extractFrag);
|
||||
if (blurFrag) vkDevice.destroyShaderEXT(blurFrag);
|
||||
if (diffFrag) vkDevice.destroyShaderEXT(diffFrag);
|
||||
if (taaFrag) vkDevice.destroyShaderEXT(taaFrag);
|
||||
|
||||
vkDevice.destroySampler(sampler);
|
||||
vkDevice.destroyPipelineLayout(layout);
|
||||
|
||||
@@ -15,15 +15,19 @@ namespace Engine::Passes
|
||||
{
|
||||
SceneGpu::SceneGpu(Device& device, DescriptorManager& descriptors,
|
||||
ShaderManager& shaders,
|
||||
UniformBuffer& ubo, UniformBuffer& objectBuffer)
|
||||
UniformBuffer& ubo, UniformBuffer& objectBuffer,
|
||||
UniformBuffer& prevObjectBuffer)
|
||||
: m_Device(device), m_Descriptors(descriptors)
|
||||
{
|
||||
// Set 0: UBO + GPUDriven 实例数据(SSBO,vertex 读取)
|
||||
// + 上一帧实例矩阵(TAA velocity,vertex 读取)
|
||||
std::vector<DescriptorBinding> uboBindings = {
|
||||
{0, vk::DescriptorType::eUniformBuffer,
|
||||
vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment},
|
||||
{1, vk::DescriptorType::eStorageBuffer,
|
||||
vk::ShaderStageFlagBits::eVertex},
|
||||
{2, vk::DescriptorType::eStorageBuffer,
|
||||
vk::ShaderStageFlagBits::eVertex},
|
||||
};
|
||||
uboSetLayout = descriptors.CreateLayout(uboBindings);
|
||||
|
||||
@@ -103,6 +107,9 @@ namespace Engine::Passes
|
||||
uboSets[slot], 0, ubo.Get(slot), ubo.GetSize());
|
||||
descriptors.UpdateStorageBuffer(
|
||||
uboSets[slot], 1, objectBuffer.Get(slot), objectBuffer.GetSize());
|
||||
descriptors.UpdateStorageBuffer(
|
||||
uboSets[slot], 2, prevObjectBuffer.Get(slot),
|
||||
prevObjectBuffer.GetSize());
|
||||
|
||||
shadowSets[slot] = descriptors.AllocateDescriptorSet(shadowSetLayout);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
//
|
||||
// Created by atdunbg on 2026/9/23.
|
||||
//
|
||||
|
||||
#include <Engine/Renderer/Passes/TAAPass.h>
|
||||
#include <Engine/Core/FrameContext.h>
|
||||
|
||||
namespace Engine::Passes
|
||||
{
|
||||
TAAPass::TAAPass(FullscreenGpu& gpu, f32 historyAlpha,
|
||||
RG::TextureHandle hdrColor, RG::TextureHandle velocity,
|
||||
RG::TextureHandle taaColor)
|
||||
: m_Gpu(gpu), m_HistoryAlpha(historyAlpha),
|
||||
m_HDR(hdrColor), m_Velocity(velocity), m_TAAColor(taaColor)
|
||||
{
|
||||
m_Sets.resize(kMaxFramesInFlight);
|
||||
for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) {
|
||||
m_Sets[slot] = m_Gpu.AllocateSet();
|
||||
}
|
||||
}
|
||||
|
||||
void TAAPass::Register(RG::RenderGraph& rg)
|
||||
{
|
||||
rg.AddPass("TAA",
|
||||
[this](RG::RenderGraphContext& ctx) {
|
||||
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});
|
||||
})
|
||||
.Read(m_HDR, RG::ResourceUse::ShaderRead,
|
||||
vk::ShaderStageFlagBits::eFragment)
|
||||
.Read(m_Velocity, RG::ResourceUse::ShaderRead,
|
||||
vk::ShaderStageFlagBits::eFragment)
|
||||
// 自引用历史:TAAColor 的上一帧槽位就是累积历史
|
||||
.ReadHistory(m_TAAColor, RG::ResourceUse::ShaderRead,
|
||||
vk::ShaderStageFlagBits::eFragment)
|
||||
.Write(m_TAAColor, RG::ResourceUse::ColorAttachment);
|
||||
}
|
||||
|
||||
void TAAPass::OnTargetsRebuilt(RG::RenderGraph& rg)
|
||||
{
|
||||
for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) {
|
||||
m_Gpu.UpdateSetInput(m_Sets[slot], 0, rg, m_HDR, slot);
|
||||
m_Gpu.UpdateSetInput(m_Sets[slot], 1, rg, m_Velocity, slot);
|
||||
// binding2 = 历史(上一帧槽位镜像)
|
||||
m_Gpu.UpdateSetInput(m_Sets[slot], 2, rg, m_TAAColor,
|
||||
rg.HistorySlot(slot));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Engine::Passes
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <Engine/Renderer/Passes/RenderPass.h>
|
||||
#include <Engine/Renderer/Passes/ShadowPass.h>
|
||||
#include <Engine/Renderer/Passes/ForwardPass.h>
|
||||
#include <Engine/Renderer/Passes/TAAPass.h>
|
||||
#include <Engine/Renderer/Passes/BloomPass.h>
|
||||
#include <Engine/Renderer/Passes/TonemapPass.h>
|
||||
#include <Engine/Renderer/Passes/TemporalDiffPass.h>
|
||||
@@ -48,19 +49,23 @@ namespace Engine
|
||||
m_DescriptorManager = CreateScope<DescriptorManager>(*m_Device);
|
||||
m_ShaderManager = CreateScope<ShaderManager>(*m_Device);
|
||||
|
||||
// 2. Per-frame ring:UBO + GPUDriven 实例 SSBO(同一 ring 类,
|
||||
// SSBO 只是 usage 不同;正确性保证一致)
|
||||
// 2. Per-frame ring:UBO + GPUDriven 实例 SSBO(当前/上一帧,
|
||||
// 同一 ring 类,SSBO 只是 usage 不同;正确性保证一致)
|
||||
m_UniformBuffer = CreateScope<UniformBuffer>(
|
||||
*m_Device, *m_Allocator, sizeof(UniformBufferObject), kMaxFramesInFlight);
|
||||
m_ObjectBuffer = CreateScope<UniformBuffer>(
|
||||
*m_Device, *m_Allocator,
|
||||
sizeof(glm::mat4) * kMaxInstances, kMaxFramesInFlight,
|
||||
vk::BufferUsageFlagBits::eStorageBuffer);
|
||||
m_PrevObjectBuffer = CreateScope<UniformBuffer>(
|
||||
*m_Device, *m_Allocator,
|
||||
sizeof(glm::mat4) * kMaxInstances, kMaxFramesInFlight,
|
||||
vk::BufferUsageFlagBits::eStorageBuffer);
|
||||
|
||||
// 3. pass 装配体:布局/shader/采样器/per-slot 描述符
|
||||
m_SceneGpu = CreateScope<Passes::SceneGpu>(
|
||||
*m_Device, *m_DescriptorManager, *m_ShaderManager,
|
||||
*m_UniformBuffer, *m_ObjectBuffer);
|
||||
*m_UniformBuffer, *m_ObjectBuffer, *m_PrevObjectBuffer);
|
||||
m_FullscreenGpu = CreateScope<Passes::FullscreenGpu>(
|
||||
*m_Device, *m_DescriptorManager, *m_ShaderManager);
|
||||
|
||||
@@ -183,12 +188,22 @@ namespace Engine
|
||||
"ShadowMap",
|
||||
RG::TextureDesc{vk::Format::eD32Sfloat, kShadowMapSize, kShadowMapSize});
|
||||
|
||||
// HDR 颜色图:Forward 的输出(线性光照值),Tonemap 显式完成
|
||||
// 显示变换后写入 Backbuffer(对齐 UE/Unity 管线)
|
||||
// HDR 颜色图:Forward 的输出(线性光照值 + 抖动),TAA 显式完成
|
||||
// 时域累积后写入 TAAColor(对齐 UE/Unity 管线)
|
||||
m_HDRColor = m_RenderGraph->CreateTexture(
|
||||
"HDRColor",
|
||||
RG::TextureDesc{vk::Format::eR16G16B16A16Sfloat});
|
||||
|
||||
// velocity:clip 空间运动向量(TAA 反推历史像素位置)
|
||||
m_Velocity = m_RenderGraph->CreateTexture(
|
||||
"Velocity",
|
||||
RG::TextureDesc{vk::Format::eR16G16B16A16Sfloat});
|
||||
|
||||
// TAA 累积输出:既是 resolve 结果也是下一帧的历史(自引用)
|
||||
m_TAAColor = m_RenderGraph->CreateTexture(
|
||||
"TAAColor",
|
||||
RG::TextureDesc{vk::Format::eR16G16B16A16Sfloat});
|
||||
|
||||
// Bloom ping-pong:半分辨率(sizeScale 相对目标尺寸,resize 自动跟随)
|
||||
m_BloomA = m_RenderGraph->CreateTexture(
|
||||
"BloomA",
|
||||
@@ -209,13 +224,16 @@ namespace Engine
|
||||
m_Passes.push_back(CreateScope<Passes::ShadowPass>(
|
||||
*m_SceneGpu, frame, m_ShadowMap));
|
||||
m_Passes.push_back(CreateScope<Passes::ForwardPass>(
|
||||
*m_SceneGpu, frame, m_HDRColor, m_Depth, m_ShadowMap));
|
||||
*m_SceneGpu, frame, m_HDRColor, m_Velocity, m_Depth, m_ShadowMap));
|
||||
m_Passes.push_back(CreateScope<Passes::TAAPass>(
|
||||
*m_FullscreenGpu, /*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_HDRColor, m_BloomA, m_BloomB));
|
||||
*m_FullscreenGpu, *m_PostParams, m_TAAColor, m_BloomA, m_BloomB));
|
||||
m_Passes.push_back(CreateScope<Passes::TonemapPass>(
|
||||
*m_FullscreenGpu, *m_PostParams, m_HDRColor, m_BloomA,
|
||||
*m_FullscreenGpu, *m_PostParams, m_TAAColor, m_BloomA,
|
||||
m_TemporalDiff, m_Backbuffer));
|
||||
m_Passes.push_back(CreateScope<Passes::ImGuiPass>(
|
||||
&m_ImGuiLayer, m_Backbuffer));
|
||||
@@ -295,9 +313,7 @@ namespace Engine
|
||||
const auto& vkDevice = m_Device->GetDevice();
|
||||
|
||||
// 1. 场景 → 当帧渲染数据(相机/灯光/双视图剔除;纯 CPU,不碰 GPU)
|
||||
const f32 aspect = static_cast<f32>(m_Extent.width) /
|
||||
static_cast<f32>(m_Extent.height);
|
||||
if (!m_SceneRenderer->BuildFrame(scene, assets, aspect)) {
|
||||
if (!m_SceneRenderer->BuildFrame(scene, assets, m_Extent)) {
|
||||
return; // 无 primary 相机:跳帧
|
||||
}
|
||||
const FrameSceneData& frame = m_SceneRenderer->Frame();
|
||||
@@ -311,6 +327,10 @@ namespace Engine
|
||||
if (!frame.instanceModels.empty()) {
|
||||
m_ObjectBuffer->Update(frameSlot, frame.instanceModels.data(),
|
||||
frame.instanceModels.size() * sizeof(glm::mat4));
|
||||
// 上一帧姿态与当前帧平行对齐(velocity 顶点着色器按下标取)
|
||||
m_PrevObjectBuffer->Update(frameSlot, frame.prevInstanceModels.data(),
|
||||
frame.prevInstanceModels.size() *
|
||||
sizeof(glm::mat4));
|
||||
}
|
||||
|
||||
// 3. acquire swapchain image
|
||||
|
||||
@@ -28,6 +28,18 @@ namespace Engine
|
||||
constexpr f32 kLightDistance = 30.0f;
|
||||
constexpr f32 kLightNear = 1.0f;
|
||||
constexpr f32 kLightFar = 80.0f;
|
||||
|
||||
// Halton 低差异序列(TAA 子像素抖动)
|
||||
f32 Halton(u32 index, u32 base)
|
||||
{
|
||||
f32 f = 1.0f, r = 0.0f;
|
||||
while (index > 0) {
|
||||
f /= static_cast<f32>(base);
|
||||
r += f * static_cast<f32>(index % base);
|
||||
index /= base;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
void SceneRenderer::ComputeLightSpace(const glm::vec3& shadowDir)
|
||||
@@ -53,10 +65,15 @@ namespace Engine
|
||||
}
|
||||
|
||||
bool SceneRenderer::BuildFrame(ECS::Scene& scene, Assets::AssetManager& assets,
|
||||
f32 aspect)
|
||||
vk::Extent2D targetExtent)
|
||||
{
|
||||
const auto collectStart = std::chrono::steady_clock::now();
|
||||
|
||||
// ---- 0. TAA 时域状态先行(velocity = 上一帧姿态与相机)----
|
||||
UniformBufferObject& ubo = m_Frame.ubo;
|
||||
ubo.prevView = m_LastView;
|
||||
ubo.prevProj = m_LastProjNoJitter;
|
||||
|
||||
// ---- 1. 相机 ----
|
||||
auto* cameraComp = scene.GetPrimaryCamera();
|
||||
if (!cameraComp) {
|
||||
@@ -65,9 +82,24 @@ namespace Engine
|
||||
}
|
||||
const Camera& camera = cameraComp->camera;
|
||||
|
||||
UniformBufferObject& ubo = m_Frame.ubo;
|
||||
const f32 aspect = static_cast<f32>(targetExtent.width) /
|
||||
static_cast<f32>(targetExtent.height);
|
||||
ubo.view = camera.GetView();
|
||||
ubo.proj = camera.GetProjection(aspect, 0.1f, 100.0f);
|
||||
|
||||
// 未抖动投影:velocity 的当前帧 clip 位置用它(抖动不能进 velocity)
|
||||
ubo.projNoJitter = ubo.proj;
|
||||
|
||||
// 子像素抖动(Halton×8):光栅化采样位置逐帧偏移,TAA 累积去锯齿。
|
||||
// NDC 偏移 = 像素偏移 × 2 / 尺寸
|
||||
{
|
||||
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;
|
||||
ubo.proj[2][0] += jx * 2.0f / static_cast<f32>(targetExtent.width);
|
||||
ubo.proj[2][1] += jy * 2.0f / static_cast<f32>(targetExtent.height);
|
||||
}
|
||||
|
||||
ubo.ambientColor = scene.GetSettings().ambientColor;
|
||||
|
||||
// ---- 2. 灯光(第一个灯作为阴影光源)----
|
||||
@@ -98,6 +130,7 @@ namespace Engine
|
||||
// 实例矩阵数组 = [主视图段 | 阴影视图段],batch.first 为全局下标。
|
||||
// 哈希聚合保证各视图内 batch 区间连续。
|
||||
m_Frame.instanceModels.clear();
|
||||
m_Frame.prevInstanceModels.clear();
|
||||
m_Frame.mainView.batches.clear();
|
||||
m_Frame.shadowView.batches.clear();
|
||||
m_Frame.mainView.instanceCount = 0;
|
||||
@@ -112,10 +145,19 @@ namespace Engine
|
||||
const Frustum shadowFrustum = Frustum::FromMatrix(ubo.lightSpace);
|
||||
|
||||
scene.Each<TransformComponent, MeshRendererComponent>(
|
||||
[&](Entity, TransformComponent& t, MeshRendererComponent& mr) {
|
||||
[&](Entity entity, TransformComponent& t, MeshRendererComponent& mr) {
|
||||
Model* model = assets.GetModel(mr.model);
|
||||
if (!model) return;
|
||||
|
||||
// TAA velocity:本实例的上一帧世界矩阵(首帧/新实体 = 当前
|
||||
// 矩阵 → 速度为 0,不产生假运动)
|
||||
const u32 entKey = static_cast<u32>(entt::to_integral(entity));
|
||||
glm::mat4 prevWorld = t.world;
|
||||
if (auto it = m_PrevWorlds.find(entKey); it != m_PrevWorlds.end()) {
|
||||
prevWorld = it->second;
|
||||
}
|
||||
m_CurWorlds.emplace(entKey, t.world);
|
||||
|
||||
// 实例世界缩放(矩阵列向量最大长度,包围球半径随缩放)
|
||||
const f32 worldScale = glm::max(
|
||||
glm::length(glm::vec3(t.world[0])),
|
||||
@@ -145,7 +187,9 @@ namespace Engine
|
||||
}
|
||||
view.batches[it->second].count++;
|
||||
view.instanceCount++;
|
||||
// 两个数组平行 push:同下标 = 同一实例的当前/上一帧姿态
|
||||
m_Frame.instanceModels.push_back(t.world);
|
||||
m_Frame.prevInstanceModels.push_back(prevWorld);
|
||||
};
|
||||
|
||||
tryAppend(m_Frame.mainView, mainFrustum, m_MainLookup);
|
||||
@@ -153,6 +197,13 @@ namespace Engine
|
||||
}
|
||||
});
|
||||
|
||||
// 帧末滚动时域状态:cur → prev(每帧重建,销毁的实体自动清出)
|
||||
m_PrevWorlds.swap(m_CurWorlds);
|
||||
m_CurWorlds.clear();
|
||||
m_LastView = ubo.view;
|
||||
m_LastProjNoJitter = ubo.projNoJitter;
|
||||
++m_FrameIndex;
|
||||
|
||||
if (instanceTruncated && !m_TruncationWarned) {
|
||||
// 只告警一次(每帧告警会刷屏)
|
||||
m_TruncationWarned = true;
|
||||
|
||||
@@ -11,8 +11,10 @@ layout(location = 0) in vec2 fragUV;
|
||||
layout(location = 1) in vec3 fragNormal;
|
||||
layout(location = 2) in vec3 fragWorldPos;
|
||||
layout(location = 3) in vec3 fragLightSpacePos;
|
||||
layout(location = 4) in vec2 fragVelocity;
|
||||
|
||||
layout(location = 0) out vec4 outColor;
|
||||
layout(location = 1) out vec2 outVelocity; // TAA velocity 附件
|
||||
|
||||
layout(set = 0, binding = 0) uniform UniformBufferObject {
|
||||
mat4 view;
|
||||
@@ -53,4 +55,5 @@ void main() {
|
||||
}
|
||||
|
||||
outColor = vec4(result, 1.0);
|
||||
outVelocity = fragVelocity;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ layout(location = 0) out vec2 fragUV;
|
||||
layout(location = 1) out vec3 fragNormal;
|
||||
layout(location = 2) out vec3 fragWorldPos;
|
||||
layout(location = 3) out vec3 fragLightSpacePos;
|
||||
layout(location = 4) out vec2 fragVelocity; // TAA:clip 空间运动向量
|
||||
|
||||
// GPUDriven:模型矩阵从实例缓冲按实例索引取。
|
||||
// 同一 mesh 的 N 个实体 = 1 次 drawIndexed(instanceCount=N),
|
||||
@@ -23,12 +24,20 @@ layout(std430, set = 0, binding = 1) readonly buffer ObjectBuffer {
|
||||
mat4 models[]; // 连续 mat4 数组(16B 对齐,std430/std140 布局一致)
|
||||
} objects;
|
||||
|
||||
// 上一帧实例矩阵(与 ObjectBuffer 同下标 = 同一实例的上一帧姿态)
|
||||
layout(std430, set = 0, binding = 2) readonly buffer PrevObjectBuffer {
|
||||
mat4 prevModels[];
|
||||
} prevObjects;
|
||||
|
||||
layout(set = 0, binding = 0) uniform UniformBufferObject {
|
||||
mat4 view;
|
||||
mat4 proj;
|
||||
mat4 proj; // 抖动后(光栅化)
|
||||
mat4 lightSpace;
|
||||
vec4 ambientColor;
|
||||
Light lights[MAX_LIGHTS];
|
||||
mat4 projNoJitter; // 未抖动(velocity 当前帧)
|
||||
mat4 prevView; // velocity 上一帧
|
||||
mat4 prevProj;
|
||||
} ubo;
|
||||
|
||||
void main() {
|
||||
@@ -37,6 +46,13 @@ void main() {
|
||||
vec4 worldPos = model * vec4(inPosition, 1.0);
|
||||
gl_Position = ubo.proj * ubo.view * worldPos;
|
||||
|
||||
// ---- TAA velocity:当前帧与上一帧的 clip 空间位置差(UV 空间)----
|
||||
// 抖动只进光栅化 gl_Position,不进 velocity(否则抖动本身被当成运动)
|
||||
vec4 currClip = ubo.projNoJitter * ubo.view * worldPos;
|
||||
vec4 prevWorldPos = prevObjects.prevModels[gl_InstanceIndex] * vec4(inPosition, 1.0);
|
||||
vec4 prevClip = ubo.prevProj * ubo.prevView * prevWorldPos;
|
||||
fragVelocity = (currClip.xy / currClip.w - prevClip.xy / prevClip.w) * 0.5;
|
||||
|
||||
fragWorldPos = worldPos.xyz;
|
||||
// 光空间坐标(含 Vulkan [0,1] 深度重映射,与 shadow pass 写入端共用同一矩阵)
|
||||
fragLightSpacePos = (ubo.lightSpace * worldPos).xyz;
|
||||
@@ -45,4 +61,4 @@ void main() {
|
||||
fragNormal = normalize(normalMatrix * inNormal);
|
||||
|
||||
fragUV = inUV;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
#version 450
|
||||
|
||||
// TAA resolve:时域抗锯齿。
|
||||
// 1. velocity 反推历史像素位置,取历史累积色
|
||||
// 2. 邻域 clamp(3×3 AABB)压制鬼影:历史色超出当前帧邻域范围的部分丢弃
|
||||
// 3. mix(cur, hist, alpha) 时域累积
|
||||
// 输出写入 TAAColor,它同时也是下一帧的历史(ReadHistory 自引用)。
|
||||
|
||||
layout(location = 0) in vec2 fragUV;
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
layout(set = 0, binding = 0) uniform sampler2D curImage; // 当前帧 HDR
|
||||
layout(set = 0, binding = 1) uniform sampler2D velocityImage; // 运动向量(UV 空间)
|
||||
layout(set = 0, binding = 2) uniform sampler2D historyImage; // 上一帧累积结果
|
||||
|
||||
// 全屏 pass 通用 push(16B,程序级统一声明,VUID-08878)
|
||||
layout(push_constant) uniform PushConstants {
|
||||
float exposure; // 与其它全屏 pass 布局一致(本 pass 不用)
|
||||
float alpha; // 历史权重(0.9 = 90% 历史)
|
||||
float param1;
|
||||
float param2;
|
||||
} pc;
|
||||
|
||||
void main() {
|
||||
vec4 cur = texture(curImage, fragUV);
|
||||
vec2 vel = texture(velocityImage, fragUV).xy;
|
||||
vec2 histUV = fragUV - vel;
|
||||
|
||||
vec3 result = cur.rgb;
|
||||
|
||||
// 历史采样:越出画面(相机快速平移)直接用当前帧,不累积
|
||||
if (all(greaterThanEqual(histUV, vec2(0.0))) &&
|
||||
all(lessThanEqual(histUV, vec2(1.0)))) {
|
||||
vec3 hist = texture(historyImage, histUV).rgb;
|
||||
|
||||
// 邻域 clamp:当前帧 3×3 邻域的 min/max 包围盒
|
||||
vec2 ts = 1.0 / vec2(textureSize(curImage, 0));
|
||||
vec3 minC = cur.rgb;
|
||||
vec3 maxC = cur.rgb;
|
||||
for (int y = -1; y <= 1; ++y) {
|
||||
for (int x = -1; x <= 1; ++x) {
|
||||
vec3 c = texture(curImage, fragUV + vec2(x, y) * ts).rgb;
|
||||
minC = min(minC, c);
|
||||
maxC = max(maxC, c);
|
||||
}
|
||||
}
|
||||
vec3 clamped = clamp(hist, minC, maxC);
|
||||
result = mix(cur.rgb, clamped, pc.alpha);
|
||||
}
|
||||
|
||||
outColor = vec4(result, cur.a);
|
||||
}
|
||||
Reference in New Issue
Block a user