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; bool history = false;
}; };
std::vector<BarrierPair> barriers; std::vector<BarrierPair> barriers;
// 历史读的出口 barrier(exec 之后执行):历史镜像被临时转进读
// layout 采样,读完必须恢复 resting layout,否则下一帧该槽位
// entry barrier 的 oldLayout 与实际 layout 脱节
std::vector<BarrierPair> postBarriers;
}; };
// ---------- RenderGraph ---------- // ---------- RenderGraph ----------
@@ -18,10 +18,11 @@ namespace Engine::Passes {
class ForwardPass final : public RenderPass { class ForwardPass final : public RenderPass {
public: public:
ForwardPass(SceneGpu& gpu, FrameSceneData& frame, ForwardPass(SceneGpu& gpu, FrameSceneData& frame,
RG::TextureHandle hdrColor, RG::TextureHandle depth, RG::TextureHandle hdrColor, RG::TextureHandle velocity,
RG::TextureHandle shadowMap) RG::TextureHandle depth, RG::TextureHandle shadowMap)
: m_Gpu(gpu), m_Frame(frame), : 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; void Register(RG::RenderGraph& rg) override;
@@ -29,6 +30,7 @@ namespace Engine::Passes {
SceneGpu& m_Gpu; SceneGpu& m_Gpu;
FrameSceneData& m_Frame; FrameSceneData& m_Frame;
RG::TextureHandle m_HDRColor; RG::TextureHandle m_HDRColor;
RG::TextureHandle m_Velocity; // TAA 运动向量(第二颜色附件)
RG::TextureHandle m_Depth; RG::TextureHandle m_Depth;
RG::TextureHandle m_ShadowMap; RG::TextureHandle m_ShadowMap;
}; };
@@ -54,6 +54,7 @@ namespace Engine {
vk::Sampler sampler; vk::Sampler sampler;
vk::ShaderEXT vert{}, extractFrag{}, blurFrag{}, tonemapFrag{}; vk::ShaderEXT vert{}, extractFrag{}, blurFrag{}, tonemapFrag{};
vk::ShaderEXT diffFrag{}; // temporal_diff(帧历史调试) vk::ShaderEXT diffFrag{}; // temporal_diff(帧历史调试)
vk::ShaderEXT taaFrag{}; // taa(时域抗锯齿 resolve)
// 各 pass 自己持有 per-slot set;分配统一走这里 // 各 pass 自己持有 per-slot set;分配统一走这里
vk::DescriptorSet AllocateSet(); vk::DescriptorSet AllocateSet();
@@ -29,14 +29,15 @@ namespace Engine {
public: public:
SceneGpu(Device& device, DescriptorManager& descriptors, SceneGpu(Device& device, DescriptorManager& descriptors,
ShaderManager& shaders, ShaderManager& shaders,
UniformBuffer& ubo, UniformBuffer& objectBuffer); UniformBuffer& ubo, UniformBuffer& objectBuffer,
UniformBuffer& prevObjectBuffer);
~SceneGpu(); ~SceneGpu();
SceneGpu(const SceneGpu&) = delete; SceneGpu(const SceneGpu&) = delete;
SceneGpu& operator=(const SceneGpu&) = delete; SceneGpu& operator=(const SceneGpu&) = delete;
// ---- set 布局(shader 契约,改绑定必须同步 shaders/)---- // ---- set 布局(shader 契约,改绑定必须同步 shaders/)----
vk::DescriptorSetLayout uboSetLayout; // Set0 = {UBO, ObjectBuffer} vk::DescriptorSetLayout uboSetLayout; // Set0 = {UBO, ObjectBuffer, PrevObjectBuffer}
vk::DescriptorSetLayout textureSetLayout; // Set1 = {albedo} vk::DescriptorSetLayout textureSetLayout; // Set1 = {albedo}
vk::DescriptorSetLayout shadowSetLayout; // Set2 = {shadow map} vk::DescriptorSetLayout shadowSetLayout; // Set2 = {shadow map}
vk::PipelineLayout forwardLayout; // 3 sets,无 push(GPUDriven 经 SSBO) 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<ShaderManager> m_ShaderManager;
Scope<UniformBuffer> m_UniformBuffer; // per-slot UBO ring Scope<UniformBuffer> m_UniformBuffer; // per-slot UBO ring
Scope<UniformBuffer> m_ObjectBuffer; // GPUDriven 实例矩阵 ring(SSBO) Scope<UniformBuffer> m_ObjectBuffer; // GPUDriven 实例矩阵 ring(SSBO)
Scope<UniformBuffer> m_PrevObjectBuffer; // 上一帧实例矩阵 ring(TAA velocity)
Scope<Passes::SceneGpu> m_SceneGpu; // 场景类 pass 共享装配 Scope<Passes::SceneGpu> m_SceneGpu; // 场景类 pass 共享装配
Scope<Passes::FullscreenGpu> m_FullscreenGpu; // 全屏 pass 共享装配 Scope<Passes::FullscreenGpu> m_FullscreenGpu; // 全屏 pass 共享装配
Scope<Swapchain> m_Swapchain; Scope<Swapchain> m_Swapchain;
@@ -125,7 +126,9 @@ namespace Engine {
RG::TextureHandle m_Backbuffer{}; RG::TextureHandle m_Backbuffer{};
RG::TextureHandle m_Depth{}; RG::TextureHandle m_Depth{};
RG::TextureHandle m_ShadowMap{}; 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_BloomA{}; // 半分辨率:提取 + 垂直模糊结果
RG::TextureHandle m_BloomB{}; // 半分辨率:水平模糊中间结果 RG::TextureHandle m_BloomB{}; // 半分辨率:水平模糊中间结果
RG::TextureHandle m_TemporalDiff{}; // 半分辨率:帧历史差异(调试) RG::TextureHandle m_TemporalDiff{}; // 半分辨率:帧历史差异(调试)
+13 -2
View File
@@ -22,6 +22,7 @@
#include <Engine/Core/Types.h> #include <Engine/Core/Types.h>
#include <Engine/Renderer/SceneTypes.h> #include <Engine/Renderer/SceneTypes.h>
#include <vulkan/vulkan.hpp>
#include <unordered_map> #include <unordered_map>
namespace Engine { namespace Engine {
@@ -32,8 +33,9 @@ namespace Engine {
class SceneRenderer { class SceneRenderer {
public: public:
// 从场景构建当帧渲染数据。无 primary 相机返回 false(渲染跳帧)。 // 从场景构建当帧渲染数据。无 primary 相机返回 false(渲染跳帧)。
// aspect 由调用方传入(跟随 swapchain,本类不感知窗口)。 // targetExtent 用于 TAA 子像素抖动的 NDC 偏移折算。
bool BuildFrame(ECS::Scene& scene, Assets::AssetManager& assets, f32 aspect); bool BuildFrame(ECS::Scene& scene, Assets::AssetManager& assets,
vk::Extent2D targetExtent);
FrameSceneData& Frame() { return m_Frame; } FrameSceneData& Frame() { return m_Frame; }
@@ -53,6 +55,15 @@ namespace Engine {
std::unordered_map<Mesh*, u32> m_MainLookup; std::unordered_map<Mesh*, u32> m_MainLookup;
std::unordered_map<Mesh*, u32> m_ShadowLookup; 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; // 实例缓冲打满只告警一次 bool m_TruncationWarned = false; // 实例缓冲打满只告警一次
}; };
+11 -2
View File
@@ -42,13 +42,19 @@ namespace Engine {
glm::vec4 color; // rgb = 颜色,a = 强度 glm::vec4 color; // rgb = 颜色,a = 强度
}; };
// 镜像 simple_shader 的 UBO 布局(std140 语义由 shader 侧对齐) // 镜像 simple_shader 的 UBO 布局(std140 语义由 shader 侧对齐)。
// 字段只能追加在尾部(shadow.vert 共用本 set 前缀,前插会错位)。
struct UniformBufferObject { struct UniformBufferObject {
glm::mat4 view; glm::mat4 view;
glm::mat4 proj; glm::mat4 proj; // 抖动后的投影(光栅化用)
glm::mat4 lightSpace; // 阴影光源 VP(写入与采样共用同一矩阵) glm::mat4 lightSpace; // 阴影光源 VP(写入与采样共用同一矩阵)
glm::vec4 ambientColor; glm::vec4 ambientColor;
LightData lights[kMaxLights]; LightData lights[kMaxLights];
// ---- TAA(尾部追加;velocity 在 vertex 里算)----
glm::mat4 projNoJitter; // 未抖动投影(velocity 当前帧 clip 用)
glm::mat4 prevView; // 上一帧视图(velocity)
glm::mat4 prevProj; // 上一帧未抖动投影(velocity)
}; };
// 每帧从场景收集的渲染数据(SceneRenderer 生产,pass 消费)。 // 每帧从场景收集的渲染数据(SceneRenderer 生产,pass 消费)。
@@ -58,6 +64,9 @@ namespace Engine {
// 实例矩阵数组 = [主视图段 | 阴影视图段](共享同一 per-slot SSBO) // 实例矩阵数组 = [主视图段 | 阴影视图段](共享同一 per-slot SSBO)
std::vector<glm::mat4> instanceModels; std::vector<glm::mat4> instanceModels;
// 上一帧世界矩阵,与 instanceModels 平行对齐(同一下标 = 同一
// 实例的上一帧姿态,TAA velocity 用);首帧/新实体 = 当前矩阵
std::vector<glm::mat4> prevInstanceModels;
CullResult mainView; // 相机视锥 → Forward pass CullResult mainView; // 相机视锥 → Forward pass
CullResult shadowView; // 光源视锥 → Shadow pass(相机外实体仍投影子) CullResult shadowView; // 光源视锥 → Shadow pass(相机外实体仍投影子)
+30
View File
@@ -177,6 +177,7 @@ namespace Engine::RG {
for (auto& pass : m_Passes) { for (auto& pass : m_Passes) {
pass.culled = false; pass.culled = false;
pass.barriers.clear(); pass.barriers.clear();
pass.postBarriers.clear();
} }
for (auto& entry : m_Textures) ResolveUsage(entry); for (auto& entry : m_Textures) ResolveUsage(entry);
@@ -468,6 +469,20 @@ namespace Engine::RG {
.setOldLayout(entry.restingLayout); .setOldLayout(entry.restingLayout);
pass.barriers.push_back({use.resIndex, steady, first, true}); 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:本帧链只描述当前槽位镜像 continue; // 不推进 chain:本帧链只描述当前槽位镜像
} }
@@ -763,6 +778,21 @@ namespace Engine::RG {
pass.exec(ctx); 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) { if (m_QueryPool) {
frame.cmd.writeTimestamp2( frame.cmd.writeTimestamp2(
vk::PipelineStageFlagBits2::eBottomOfPipe, vk::PipelineStageFlagBits2::eBottomOfPipe,
+29 -11
View File
@@ -8,6 +8,7 @@
#include <Engine/Renderer/Material.h> #include <Engine/Renderer/Material.h>
#include <array> #include <array>
#include <span>
namespace Engine::Passes namespace Engine::Passes
{ {
@@ -20,12 +21,21 @@ namespace Engine::Passes
clearValue.color = vk::ClearColorValue( clearValue.color = vk::ClearColorValue(
std::array<float, 4>{0.1f, 0.1f, 0.1f, 0.0f}); std::array<float, 4>{0.1f, 0.1f, 0.1f, 0.0f});
vk::RenderingAttachmentInfo colorAttachment; vk::RenderingAttachmentInfo colorAttachments[2];
colorAttachment.setImageView(ctx.View(m_HDRColor)) colorAttachments[0].setImageView(ctx.View(m_HDRColor))
.setImageLayout(vk::ImageLayout::eColorAttachmentOptimal) .setImageLayout(vk::ImageLayout::eColorAttachmentOptimal)
.setLoadOp(vk::AttachmentLoadOp::eClear) .setLoadOp(vk::AttachmentLoadOp::eClear)
.setStoreOp(vk::AttachmentStoreOp::eStore) .setStoreOp(vk::AttachmentStoreOp::eStore)
.setClearValue(clearValue); .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; vk::RenderingAttachmentInfo depthAttachment;
depthAttachment.setImageView(ctx.View(m_Depth)) depthAttachment.setImageView(ctx.View(m_Depth))
@@ -35,10 +45,13 @@ namespace Engine::Passes
.setClearValue(vk::ClearValue( .setClearValue(vk::ClearValue(
vk::ClearDepthStencilValue(1.0f, 0))); vk::ClearDepthStencilValue(1.0f, 0)));
std::array<vk::RenderingAttachmentInfo, 2> colorArray = {
colorAttachments[0], colorAttachments[1]};
vk::RenderingInfo renderingInfo; vk::RenderingInfo renderingInfo;
renderingInfo.setRenderArea({{0, 0}, ctx.extent}) renderingInfo.setRenderArea({{0, 0}, ctx.extent})
.setLayerCount(1) .setLayerCount(1)
.setColorAttachments(colorAttachment) .setColorAttachments(colorArray)
.setPDepthAttachment(&depthAttachment); .setPDepthAttachment(&depthAttachment);
cmd.beginRendering(renderingInfo); cmd.beginRendering(renderingInfo);
@@ -95,8 +108,6 @@ namespace Engine::Passes
cmd.setScissorWithCount(scissor); cmd.setScissorWithCount(scissor);
vk::Bool32 blendEnable = VK_FALSE; vk::Bool32 blendEnable = VK_FALSE;
cmd.setColorBlendEnableEXT(0, blendEnable);
vk::ColorBlendEquationEXT blendEquation; vk::ColorBlendEquationEXT blendEquation;
blendEquation.setSrcColorBlendFactor(vk::BlendFactor::eOne) blendEquation.setSrcColorBlendFactor(vk::BlendFactor::eOne)
.setDstColorBlendFactor(vk::BlendFactor::eZero) .setDstColorBlendFactor(vk::BlendFactor::eZero)
@@ -104,14 +115,20 @@ namespace Engine::Passes
.setSrcAlphaBlendFactor(vk::BlendFactor::eOne) .setSrcAlphaBlendFactor(vk::BlendFactor::eOne)
.setDstAlphaBlendFactor(vk::BlendFactor::eZero) .setDstAlphaBlendFactor(vk::BlendFactor::eZero)
.setAlphaBlendOp(vk::BlendOp::eAdd); .setAlphaBlendOp(vk::BlendOp::eAdd);
cmd.setColorBlendEquationEXT(0, blendEquation);
vk::ColorComponentFlags colorWriteMask = vk::ColorComponentFlags colorWriteMask =
vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eR |
vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eG |
vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eB |
vk::ColorComponentFlagBits::eA; 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(相机视锥剔除后)---- // ---- 绘制:GPUDriven 按 mesh 分 batch(相机视锥剔除后)----
// 实例矩阵已由 Renderer 写入 ObjectBuffer(per-slot SSBO); // 实例矩阵已由 Renderer 写入 ObjectBuffer(per-slot SSBO);
@@ -127,6 +144,7 @@ namespace Engine::Passes
cmd.endRendering(); cmd.endRendering();
}) })
.Write(m_HDRColor, RG::ResourceUse::ColorAttachment) .Write(m_HDRColor, RG::ResourceUse::ColorAttachment)
.Write(m_Velocity, RG::ResourceUse::ColorAttachment)
.Write(m_Depth, RG::ResourceUse::DepthAttachment) .Write(m_Depth, RG::ResourceUse::DepthAttachment)
.Read(m_ShadowMap, RG::ResourceUse::ShaderRead, .Read(m_ShadowMap, RG::ResourceUse::ShaderRead,
vk::ShaderStageFlagBits::eFragment); vk::ShaderStageFlagBits::eFragment);
+9 -1
View File
@@ -68,14 +68,21 @@ 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 taaF = shaders.CreateFromFile(
shaderDir + "taa.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; 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"); ENGINE_FATAL("FullscreenGpu: failed to create shader objects");
} }
} }
@@ -89,6 +96,7 @@ namespace Engine::Passes
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); if (diffFrag) vkDevice.destroyShaderEXT(diffFrag);
if (taaFrag) vkDevice.destroyShaderEXT(taaFrag);
vkDevice.destroySampler(sampler); vkDevice.destroySampler(sampler);
vkDevice.destroyPipelineLayout(layout); vkDevice.destroyPipelineLayout(layout);
+8 -1
View File
@@ -15,15 +15,19 @@ namespace Engine::Passes
{ {
SceneGpu::SceneGpu(Device& device, DescriptorManager& descriptors, SceneGpu::SceneGpu(Device& device, DescriptorManager& descriptors,
ShaderManager& shaders, ShaderManager& shaders,
UniformBuffer& ubo, UniformBuffer& objectBuffer) UniformBuffer& ubo, UniformBuffer& objectBuffer,
UniformBuffer& prevObjectBuffer)
: m_Device(device), m_Descriptors(descriptors) : m_Device(device), m_Descriptors(descriptors)
{ {
// Set 0: UBO + GPUDriven 实例数据(SSBO,vertex 读取) // Set 0: UBO + GPUDriven 实例数据(SSBO,vertex 读取)
// + 上一帧实例矩阵(TAA velocity,vertex 读取)
std::vector<DescriptorBinding> uboBindings = { std::vector<DescriptorBinding> uboBindings = {
{0, vk::DescriptorType::eUniformBuffer, {0, vk::DescriptorType::eUniformBuffer,
vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment}, vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment},
{1, vk::DescriptorType::eStorageBuffer, {1, vk::DescriptorType::eStorageBuffer,
vk::ShaderStageFlagBits::eVertex}, vk::ShaderStageFlagBits::eVertex},
{2, vk::DescriptorType::eStorageBuffer,
vk::ShaderStageFlagBits::eVertex},
}; };
uboSetLayout = descriptors.CreateLayout(uboBindings); uboSetLayout = descriptors.CreateLayout(uboBindings);
@@ -103,6 +107,9 @@ namespace Engine::Passes
uboSets[slot], 0, ubo.Get(slot), ubo.GetSize()); uboSets[slot], 0, ubo.Get(slot), ubo.GetSize());
descriptors.UpdateStorageBuffer( descriptors.UpdateStorageBuffer(
uboSets[slot], 1, objectBuffer.Get(slot), objectBuffer.GetSize()); uboSets[slot], 1, objectBuffer.Get(slot), objectBuffer.GetSize());
descriptors.UpdateStorageBuffer(
uboSets[slot], 2, prevObjectBuffer.Get(slot),
prevObjectBuffer.GetSize());
shadowSets[slot] = descriptors.AllocateDescriptorSet(shadowSetLayout); shadowSets[slot] = descriptors.AllocateDescriptorSet(shadowSetLayout);
} }
+51
View File
@@ -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
+31 -11
View File
@@ -14,6 +14,7 @@
#include <Engine/Renderer/Passes/RenderPass.h> #include <Engine/Renderer/Passes/RenderPass.h>
#include <Engine/Renderer/Passes/ShadowPass.h> #include <Engine/Renderer/Passes/ShadowPass.h>
#include <Engine/Renderer/Passes/ForwardPass.h> #include <Engine/Renderer/Passes/ForwardPass.h>
#include <Engine/Renderer/Passes/TAAPass.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/TemporalDiffPass.h>
@@ -48,19 +49,23 @@ namespace Engine
m_DescriptorManager = CreateScope<DescriptorManager>(*m_Device); m_DescriptorManager = CreateScope<DescriptorManager>(*m_Device);
m_ShaderManager = CreateScope<ShaderManager>(*m_Device); m_ShaderManager = CreateScope<ShaderManager>(*m_Device);
// 2. Per-frame ring:UBO + GPUDriven 实例 SSBO(同一 ring 类, // 2. Per-frame ring:UBO + GPUDriven 实例 SSBO(当前/上一帧,
// SSBO 只是 usage 不同;正确性保证一致) // 同一 ring 类,SSBO 只是 usage 不同;正确性保证一致)
m_UniformBuffer = CreateScope<UniformBuffer>( m_UniformBuffer = CreateScope<UniformBuffer>(
*m_Device, *m_Allocator, sizeof(UniformBufferObject), kMaxFramesInFlight); *m_Device, *m_Allocator, sizeof(UniformBufferObject), kMaxFramesInFlight);
m_ObjectBuffer = CreateScope<UniformBuffer>( m_ObjectBuffer = CreateScope<UniformBuffer>(
*m_Device, *m_Allocator, *m_Device, *m_Allocator,
sizeof(glm::mat4) * kMaxInstances, kMaxFramesInFlight, sizeof(glm::mat4) * kMaxInstances, kMaxFramesInFlight,
vk::BufferUsageFlagBits::eStorageBuffer); vk::BufferUsageFlagBits::eStorageBuffer);
m_PrevObjectBuffer = CreateScope<UniformBuffer>(
*m_Device, *m_Allocator,
sizeof(glm::mat4) * kMaxInstances, kMaxFramesInFlight,
vk::BufferUsageFlagBits::eStorageBuffer);
// 3. pass 装配体:布局/shader/采样器/per-slot 描述符 // 3. pass 装配体:布局/shader/采样器/per-slot 描述符
m_SceneGpu = CreateScope<Passes::SceneGpu>( m_SceneGpu = CreateScope<Passes::SceneGpu>(
*m_Device, *m_DescriptorManager, *m_ShaderManager, *m_Device, *m_DescriptorManager, *m_ShaderManager,
*m_UniformBuffer, *m_ObjectBuffer); *m_UniformBuffer, *m_ObjectBuffer, *m_PrevObjectBuffer);
m_FullscreenGpu = CreateScope<Passes::FullscreenGpu>( m_FullscreenGpu = CreateScope<Passes::FullscreenGpu>(
*m_Device, *m_DescriptorManager, *m_ShaderManager); *m_Device, *m_DescriptorManager, *m_ShaderManager);
@@ -183,12 +188,22 @@ namespace Engine
"ShadowMap", "ShadowMap",
RG::TextureDesc{vk::Format::eD32Sfloat, kShadowMapSize, kShadowMapSize}); RG::TextureDesc{vk::Format::eD32Sfloat, kShadowMapSize, kShadowMapSize});
// HDR 颜色图:Forward 的输出(线性光照值),Tonemap 显式完成 // HDR 颜色图:Forward 的输出(线性光照值 + 抖动),TAA 显式完成
// 显示变换后写入 Backbuffer(对齐 UE/Unity 管线) // 时域累积后写入 TAAColor(对齐 UE/Unity 管线)
m_HDRColor = m_RenderGraph->CreateTexture( m_HDRColor = m_RenderGraph->CreateTexture(
"HDRColor", "HDRColor",
RG::TextureDesc{vk::Format::eR16G16B16A16Sfloat}); 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 自动跟随) // Bloom ping-pong:半分辨率(sizeScale 相对目标尺寸,resize 自动跟随)
m_BloomA = m_RenderGraph->CreateTexture( m_BloomA = m_RenderGraph->CreateTexture(
"BloomA", "BloomA",
@@ -209,13 +224,16 @@ namespace Engine
m_Passes.push_back(CreateScope<Passes::ShadowPass>( m_Passes.push_back(CreateScope<Passes::ShadowPass>(
*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_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_Passes.push_back(CreateScope<Passes::TemporalDiffPass>(
*m_FullscreenGpu, /*gain*/ 8.0f, m_HDRColor, m_TemporalDiff)); *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_TAAColor, 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_FullscreenGpu, *m_PostParams, m_TAAColor, m_BloomA,
m_TemporalDiff, m_Backbuffer)); 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));
@@ -295,9 +313,7 @@ namespace Engine
const auto& vkDevice = m_Device->GetDevice(); const auto& vkDevice = m_Device->GetDevice();
// 1. 场景 → 当帧渲染数据(相机/灯光/双视图剔除;纯 CPU,不碰 GPU) // 1. 场景 → 当帧渲染数据(相机/灯光/双视图剔除;纯 CPU,不碰 GPU)
const f32 aspect = static_cast<f32>(m_Extent.width) / if (!m_SceneRenderer->BuildFrame(scene, assets, m_Extent)) {
static_cast<f32>(m_Extent.height);
if (!m_SceneRenderer->BuildFrame(scene, assets, aspect)) {
return; // 无 primary 相机:跳帧 return; // 无 primary 相机:跳帧
} }
const FrameSceneData& frame = m_SceneRenderer->Frame(); const FrameSceneData& frame = m_SceneRenderer->Frame();
@@ -311,6 +327,10 @@ namespace Engine
if (!frame.instanceModels.empty()) { if (!frame.instanceModels.empty()) {
m_ObjectBuffer->Update(frameSlot, frame.instanceModels.data(), m_ObjectBuffer->Update(frameSlot, frame.instanceModels.data(),
frame.instanceModels.size() * sizeof(glm::mat4)); frame.instanceModels.size() * sizeof(glm::mat4));
// 上一帧姿态与当前帧平行对齐(velocity 顶点着色器按下标取)
m_PrevObjectBuffer->Update(frameSlot, frame.prevInstanceModels.data(),
frame.prevInstanceModels.size() *
sizeof(glm::mat4));
} }
// 3. acquire swapchain image // 3. acquire swapchain image
+54 -3
View File
@@ -28,6 +28,18 @@ namespace Engine
constexpr f32 kLightDistance = 30.0f; constexpr f32 kLightDistance = 30.0f;
constexpr f32 kLightNear = 1.0f; constexpr f32 kLightNear = 1.0f;
constexpr f32 kLightFar = 80.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) void SceneRenderer::ComputeLightSpace(const glm::vec3& shadowDir)
@@ -53,10 +65,15 @@ namespace Engine
} }
bool SceneRenderer::BuildFrame(ECS::Scene& scene, Assets::AssetManager& assets, bool SceneRenderer::BuildFrame(ECS::Scene& scene, Assets::AssetManager& assets,
f32 aspect) vk::Extent2D targetExtent)
{ {
const auto collectStart = std::chrono::steady_clock::now(); 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. 相机 ---- // ---- 1. 相机 ----
auto* cameraComp = scene.GetPrimaryCamera(); auto* cameraComp = scene.GetPrimaryCamera();
if (!cameraComp) { if (!cameraComp) {
@@ -65,9 +82,24 @@ namespace Engine
} }
const Camera& camera = cameraComp->camera; 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.view = camera.GetView();
ubo.proj = camera.GetProjection(aspect, 0.1f, 100.0f); 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; ubo.ambientColor = scene.GetSettings().ambientColor;
// ---- 2. 灯光(第一个灯作为阴影光源)---- // ---- 2. 灯光(第一个灯作为阴影光源)----
@@ -98,6 +130,7 @@ namespace Engine
// 实例矩阵数组 = [主视图段 | 阴影视图段],batch.first 为全局下标。 // 实例矩阵数组 = [主视图段 | 阴影视图段],batch.first 为全局下标。
// 哈希聚合保证各视图内 batch 区间连续。 // 哈希聚合保证各视图内 batch 区间连续。
m_Frame.instanceModels.clear(); m_Frame.instanceModels.clear();
m_Frame.prevInstanceModels.clear();
m_Frame.mainView.batches.clear(); m_Frame.mainView.batches.clear();
m_Frame.shadowView.batches.clear(); m_Frame.shadowView.batches.clear();
m_Frame.mainView.instanceCount = 0; m_Frame.mainView.instanceCount = 0;
@@ -112,10 +145,19 @@ namespace Engine
const Frustum shadowFrustum = Frustum::FromMatrix(ubo.lightSpace); const Frustum shadowFrustum = Frustum::FromMatrix(ubo.lightSpace);
scene.Each<TransformComponent, MeshRendererComponent>( scene.Each<TransformComponent, MeshRendererComponent>(
[&](Entity, TransformComponent& t, MeshRendererComponent& mr) { [&](Entity entity, TransformComponent& t, MeshRendererComponent& mr) {
Model* model = assets.GetModel(mr.model); Model* model = assets.GetModel(mr.model);
if (!model) return; 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( const f32 worldScale = glm::max(
glm::length(glm::vec3(t.world[0])), glm::length(glm::vec3(t.world[0])),
@@ -145,7 +187,9 @@ namespace Engine
} }
view.batches[it->second].count++; view.batches[it->second].count++;
view.instanceCount++; view.instanceCount++;
// 两个数组平行 push:同下标 = 同一实例的当前/上一帧姿态
m_Frame.instanceModels.push_back(t.world); m_Frame.instanceModels.push_back(t.world);
m_Frame.prevInstanceModels.push_back(prevWorld);
}; };
tryAppend(m_Frame.mainView, mainFrustum, m_MainLookup); 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) { if (instanceTruncated && !m_TruncationWarned) {
// 只告警一次(每帧告警会刷屏) // 只告警一次(每帧告警会刷屏)
m_TruncationWarned = true; m_TruncationWarned = true;
+3
View File
@@ -11,8 +11,10 @@ layout(location = 0) in vec2 fragUV;
layout(location = 1) in vec3 fragNormal; layout(location = 1) in vec3 fragNormal;
layout(location = 2) in vec3 fragWorldPos; layout(location = 2) in vec3 fragWorldPos;
layout(location = 3) in vec3 fragLightSpacePos; layout(location = 3) in vec3 fragLightSpacePos;
layout(location = 4) in vec2 fragVelocity;
layout(location = 0) out vec4 outColor; layout(location = 0) out vec4 outColor;
layout(location = 1) out vec2 outVelocity; // TAA velocity 附件
layout(set = 0, binding = 0) uniform UniformBufferObject { layout(set = 0, binding = 0) uniform UniformBufferObject {
mat4 view; mat4 view;
@@ -53,4 +55,5 @@ void main() {
} }
outColor = vec4(result, 1.0); outColor = vec4(result, 1.0);
outVelocity = fragVelocity;
} }
+18 -2
View File
@@ -15,6 +15,7 @@ layout(location = 0) out vec2 fragUV;
layout(location = 1) out vec3 fragNormal; layout(location = 1) out vec3 fragNormal;
layout(location = 2) out vec3 fragWorldPos; layout(location = 2) out vec3 fragWorldPos;
layout(location = 3) out vec3 fragLightSpacePos; layout(location = 3) out vec3 fragLightSpacePos;
layout(location = 4) out vec2 fragVelocity; // TAA:clip 空间运动向量
// GPUDriven:模型矩阵从实例缓冲按实例索引取。 // GPUDriven:模型矩阵从实例缓冲按实例索引取。
// 同一 mesh 的 N 个实体 = 1 次 drawIndexed(instanceCount=N), // 同一 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 布局一致) mat4 models[]; // 连续 mat4 数组(16B 对齐,std430/std140 布局一致)
} objects; } objects;
// 上一帧实例矩阵(与 ObjectBuffer 同下标 = 同一实例的上一帧姿态)
layout(std430, set = 0, binding = 2) readonly buffer PrevObjectBuffer {
mat4 prevModels[];
} prevObjects;
layout(set = 0, binding = 0) uniform UniformBufferObject { layout(set = 0, binding = 0) uniform UniformBufferObject {
mat4 view; mat4 view;
mat4 proj; mat4 proj; // 抖动后(光栅化)
mat4 lightSpace; mat4 lightSpace;
vec4 ambientColor; vec4 ambientColor;
Light lights[MAX_LIGHTS]; Light lights[MAX_LIGHTS];
mat4 projNoJitter; // 未抖动(velocity 当前帧)
mat4 prevView; // velocity 上一帧
mat4 prevProj;
} ubo; } ubo;
void main() { void main() {
@@ -37,6 +46,13 @@ void main() {
vec4 worldPos = model * vec4(inPosition, 1.0); vec4 worldPos = model * vec4(inPosition, 1.0);
gl_Position = ubo.proj * ubo.view * worldPos; 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; fragWorldPos = worldPos.xyz;
// 光空间坐标(含 Vulkan [0,1] 深度重映射,与 shadow pass 写入端共用同一矩阵) // 光空间坐标(含 Vulkan [0,1] 深度重映射,与 shadow pass 写入端共用同一矩阵)
fragLightSpacePos = (ubo.lightSpace * worldPos).xyz; fragLightSpacePos = (ubo.lightSpace * worldPos).xyz;
@@ -45,4 +61,4 @@ void main() {
fragNormal = normalize(normalMatrix * inNormal); fragNormal = normalize(normalMatrix * inNormal);
fragUV = inUV; fragUV = inUV;
} }
+52
View File
@@ -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);
}