TAA 时域速度链路贯通:天空盒补写 velocity(此前恒 0,相机移动时天空/背景重投影错误 → 闪烁);skyPrevVP(上一帧未抖动 proj × 去平移视图)落帧;TAA 加去遮挡深度检测(重投影点历史深度与当前像素比对,新暴露区拒历史,需 near/far + 投影模式落帧);移除 min-depth dilation(把前景速度泄漏给轮廓旁背景像素 → 边界闪);Forward 不透明段 velocity 附件 Clear→Load 承接天空结果;velocity 不入抖动注释订正(抖动进 velocity = 静止画面持续亚纹素重采样发糊)
This commit is contained in:
@@ -35,7 +35,8 @@ namespace Engine {
|
|||||||
// env 可为空(HDR 资产缺失):仅承担清屏,天空保持清屏色
|
// env 可为空(HDR 资产缺失):仅承担清屏,天空保持清屏色
|
||||||
SkyboxPass(Device& device, ShaderManager& shaders,
|
SkyboxPass(Device& device, ShaderManager& shaders,
|
||||||
DescriptorManager& descriptors, FrameSceneData& frame,
|
DescriptorManager& descriptors, FrameSceneData& frame,
|
||||||
RG::TextureHandle hdrColor, const Cubemap* env);
|
RG::TextureHandle hdrColor, RG::TextureHandle velocity,
|
||||||
|
const Cubemap* env);
|
||||||
~SkyboxPass();
|
~SkyboxPass();
|
||||||
|
|
||||||
SkyboxPass(const SkyboxPass&) = delete;
|
SkyboxPass(const SkyboxPass&) = delete;
|
||||||
@@ -49,10 +50,11 @@ namespace Engine {
|
|||||||
DescriptorManager& m_Descriptors;
|
DescriptorManager& m_Descriptors;
|
||||||
FrameSceneData& m_Frame;
|
FrameSceneData& m_Frame;
|
||||||
RG::TextureHandle m_HDRColor;
|
RG::TextureHandle m_HDRColor;
|
||||||
|
RG::TextureHandle m_Velocity; // 天空 velocity 附件(TAA)
|
||||||
const Cubemap* m_Env; // 可空
|
const Cubemap* m_Env; // 可空
|
||||||
|
|
||||||
vk::DescriptorSetLayout m_SetLayout; // Set0 = {samplerCube}
|
vk::DescriptorSetLayout m_SetLayout; // Set0 = {samplerCube}
|
||||||
vk::PipelineLayout m_Layout; // push = invVP 64B
|
vk::PipelineLayout m_Layout; // push = {invVP, prevVP} 128B
|
||||||
ShaderManager::ShaderRef m_Vert, m_Frag; // 析构自动销毁
|
ShaderManager::ShaderRef m_Vert, m_Frag; // 析构自动销毁
|
||||||
std::array<vk::DescriptorSet, kMaxFramesInFlight> m_Sets{};
|
std::array<vk::DescriptorSet, kMaxFramesInFlight> m_Sets{};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ namespace Engine::Passes {
|
|||||||
public:
|
public:
|
||||||
TAAPass(FullscreenGpu& gpu, FrameSceneData& frame, f32 historyAlpha,
|
TAAPass(FullscreenGpu& gpu, FrameSceneData& frame, f32 historyAlpha,
|
||||||
RG::TextureHandle hdrColor, RG::TextureHandle velocity,
|
RG::TextureHandle hdrColor, RG::TextureHandle velocity,
|
||||||
RG::TextureHandle taaColor);
|
RG::TextureHandle depth, RG::TextureHandle taaColor);
|
||||||
~TAAPass() override = default;
|
~TAAPass() override = default;
|
||||||
|
|
||||||
void Register(RG::RenderGraph& rg) override;
|
void Register(RG::RenderGraph& rg) override;
|
||||||
@@ -34,6 +34,7 @@ namespace Engine::Passes {
|
|||||||
f32 m_HistoryAlpha; // 开启时的历史权重
|
f32 m_HistoryAlpha; // 开启时的历史权重
|
||||||
RG::TextureHandle m_HDR;
|
RG::TextureHandle m_HDR;
|
||||||
RG::TextureHandle m_Velocity;
|
RG::TextureHandle m_Velocity;
|
||||||
|
RG::TextureHandle m_Depth; // 场景深度(深度感知 dilation 用)
|
||||||
RG::TextureHandle m_TAAColor; // 输出 = 下一帧的历史
|
RG::TextureHandle m_TAAColor; // 输出 = 下一帧的历史
|
||||||
|
|
||||||
std::vector<vk::DescriptorSet> m_Sets; // per-slot
|
std::vector<vk::DescriptorSet> m_Sets; // per-slot
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ namespace Engine {
|
|||||||
// ---- TAA 时域状态 ----
|
// ---- TAA 时域状态 ----
|
||||||
u64 m_FrameIndex = 0; // 抖动序列游标(Halton 2,3 ×8 采样)
|
u64 m_FrameIndex = 0; // 抖动序列游标(Halton 2,3 ×8 采样)
|
||||||
glm::mat4 m_LastView{1.0f}; // 上一帧视图(velocity)
|
glm::mat4 m_LastView{1.0f}; // 上一帧视图(velocity)
|
||||||
glm::mat4 m_LastProjNoJitter{1.0f}; // 上一帧未抖动投影(velocity)
|
glm::mat4 m_LastProjNoJitter{1.0f}; // 上一帧未抖动投影(velocity 用)
|
||||||
// 实体的上一帧世界矩阵(key = entt 实体 integral)。
|
// 实体的上一帧世界矩阵(key = entt 实体 integral)。
|
||||||
// 用 sorted vector + binary_search 替代 unordered_map:
|
// 用 sorted vector + binary_search 替代 unordered_map:
|
||||||
// unordered_map 在每帧大量 emplace 时 rehash 抖动明显,
|
// unordered_map 在每帧大量 emplace 时 rehash 抖动明显,
|
||||||
|
|||||||
@@ -164,9 +164,9 @@ namespace Engine {
|
|||||||
LightData lights[kMaxLights];
|
LightData lights[kMaxLights];
|
||||||
|
|
||||||
// ---- TAA(velocity 在 vertex 里算)----
|
// ---- TAA(velocity 在 vertex 里算)----
|
||||||
glm::mat4 projNoJitter; // 未抖动投影(velocity 当前帧 clip 用)
|
glm::mat4 projNoJitter; // 未抖动投影(天空盒反投影/折射同位采样/静态预览)
|
||||||
glm::mat4 prevView; // 上一帧视图(velocity)
|
glm::mat4 prevView; // 上一帧视图(velocity)
|
||||||
glm::mat4 prevProj; // 上一帧未抖动投影(velocity)
|
glm::mat4 prevProj; // 上一帧"已抖动"投影(velocity 历史 UV 对齐)
|
||||||
glm::vec4 cameraPos; // 相机世界位置(高光 V 向量)
|
glm::vec4 cameraPos; // 相机世界位置(高光 V 向量)
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -183,6 +183,13 @@ namespace Engine {
|
|||||||
struct FrameSceneData {
|
struct FrameSceneData {
|
||||||
UniformBufferObject ubo{};
|
UniformBufferObject ubo{};
|
||||||
|
|
||||||
|
// 相机裁剪面 + 投影模式(TAA 去遮挡检测用:把 [0,1] 深度线性化为
|
||||||
|
// 视图空间距离再比较。透视深度非线性,需 n·f/(f−d(f−n));正交
|
||||||
|
// 深度本身线性,用 cameraOrtho 分支)
|
||||||
|
f32 cameraNearZ = 0.1f;
|
||||||
|
f32 cameraFarZ = 10000.0f;
|
||||||
|
bool cameraOrtho = false;
|
||||||
|
|
||||||
glm::vec4 ClearColor{0.1, 0.1, 0.1, 1.0};
|
glm::vec4 ClearColor{0.1, 0.1, 0.1, 1.0};
|
||||||
bool DrawSkybox = true;
|
bool DrawSkybox = true;
|
||||||
// 本帧生效的环境贴图资产路径(来自第一个 active 的
|
// 本帧生效的环境贴图资产路径(来自第一个 active 的
|
||||||
@@ -194,6 +201,12 @@ namespace Engine {
|
|||||||
//(视图去平移只留旋转 → 反投影结果即世界方向;Skybox pass push)
|
//(视图去平移只留旋转 → 反投影结果即世界方向;Skybox pass push)
|
||||||
glm::mat4 skyInvVP{1.0f};
|
glm::mat4 skyInvVP{1.0f};
|
||||||
|
|
||||||
|
// 天空盒 velocity 用:上一帧未抖动 proj × 上一帧视图(去平移)。
|
||||||
|
// 天空是"无限远"表面,只受相机旋转影响:frag 把当前世界方向过
|
||||||
|
// 此矩阵得上一帧屏幕位置,差即天空的运动向量(相机移动时天空/
|
||||||
|
// 背景不再闪烁的根治点——此前天空从不写 velocity = 恒 0)
|
||||||
|
glm::mat4 skyPrevVP{1.0f};
|
||||||
|
|
||||||
// 实例矩阵数组 = [主视图段 | 阴影视图段](共享同一 per-slot SSBO)
|
// 实例矩阵数组 = [主视图段 | 阴影视图段](共享同一 per-slot SSBO)
|
||||||
std::vector<glm::mat4> instanceModels;
|
std::vector<glm::mat4> instanceModels;
|
||||||
// 上一帧世界矩阵,与 instanceModels 平行对齐(同一下标 = 同一
|
// 上一帧世界矩阵,与 instanceModels 平行对齐(同一下标 = 同一
|
||||||
|
|||||||
@@ -131,12 +131,14 @@ namespace Engine::Passes
|
|||||||
.setStoreOp(vk::AttachmentStoreOp::eStore)
|
.setStoreOp(vk::AttachmentStoreOp::eStore)
|
||||||
.setClearValue(clearValue);
|
.setClearValue(clearValue);
|
||||||
|
|
||||||
// velocity 附件:清 0 = 静止(背景无几何 → 历史原位采样)
|
// velocity 附件由 Skybox 段首写(清 0 + 天空运动向量),这里 Load
|
||||||
|
// 承接其结果,再叠加几何体运动向量。若此处 Clear 会擦掉天空
|
||||||
|
// velocity → 相机移动时天空重投影错误 → 背景闪烁。
|
||||||
vk::ClearValue velocityClear;
|
vk::ClearValue velocityClear;
|
||||||
velocityClear.color = vk::ClearColorValue(std::array<float, 4>{0.f, 0.f, 0.f, 0.f});
|
velocityClear.color = vk::ClearColorValue(std::array<float, 4>{0.f, 0.f, 0.f, 0.f});
|
||||||
colorAttachments[1].setImageView(ctx.View(m_Velocity))
|
colorAttachments[1].setImageView(ctx.View(m_Velocity))
|
||||||
.setImageLayout(vk::ImageLayout::eColorAttachmentOptimal)
|
.setImageLayout(vk::ImageLayout::eColorAttachmentOptimal)
|
||||||
.setLoadOp(vk::AttachmentLoadOp::eClear)
|
.setLoadOp(vk::AttachmentLoadOp::eLoad)
|
||||||
.setStoreOp(vk::AttachmentStoreOp::eStore)
|
.setStoreOp(vk::AttachmentStoreOp::eStore)
|
||||||
.setClearValue(velocityClear);
|
.setClearValue(velocityClear);
|
||||||
|
|
||||||
|
|||||||
@@ -18,11 +18,20 @@
|
|||||||
|
|
||||||
namespace Engine::Passes
|
namespace Engine::Passes
|
||||||
{
|
{
|
||||||
|
// push 布局与 skybox.vert/.frag 的 PushConstants 一致(128B):
|
||||||
|
// invVP = 当前帧反投影;prevVP = 上一帧 proj×view(天空 velocity)
|
||||||
|
struct SkyboxPush
|
||||||
|
{
|
||||||
|
glm::mat4 invVP;
|
||||||
|
glm::mat4 prevVP;
|
||||||
|
};
|
||||||
|
|
||||||
SkyboxPass::SkyboxPass(Device& device, ShaderManager& shaders,
|
SkyboxPass::SkyboxPass(Device& device, ShaderManager& shaders,
|
||||||
DescriptorManager& descriptors, FrameSceneData& frame,
|
DescriptorManager& descriptors, FrameSceneData& frame,
|
||||||
RG::TextureHandle hdrColor, const Cubemap* env)
|
RG::TextureHandle hdrColor, RG::TextureHandle velocity,
|
||||||
|
const Cubemap* env)
|
||||||
: m_Device(device), m_Descriptors(descriptors), m_Frame(frame),
|
: m_Device(device), m_Descriptors(descriptors), m_Frame(frame),
|
||||||
m_HDRColor(hdrColor), m_Env(env)
|
m_HDRColor(hdrColor), m_Velocity(velocity), m_Env(env)
|
||||||
{
|
{
|
||||||
// Set0 = {samplerCube}(fragment 采样)
|
// Set0 = {samplerCube}(fragment 采样)
|
||||||
std::vector<DescriptorBinding> bindings = {
|
std::vector<DescriptorBinding> bindings = {
|
||||||
@@ -31,22 +40,22 @@ namespace Engine::Passes
|
|||||||
};
|
};
|
||||||
m_SetLayout = descriptors.CreateLayout(bindings);
|
m_SetLayout = descriptors.CreateLayout(bindings);
|
||||||
|
|
||||||
// push = invVP 64B(skybox.vert/.frag 声明一致,VUID-08878)
|
// push = {invVP, prevVP} 128B(skybox.vert/.frag 声明一致,VUID-08878)
|
||||||
m_Layout = descriptors.CreatePipelineLayout(
|
m_Layout = descriptors.CreatePipelineLayout(
|
||||||
std::vector<vk::DescriptorSetLayout>{m_SetLayout},
|
std::vector<vk::DescriptorSetLayout>{m_SetLayout},
|
||||||
sizeof(glm::mat4));
|
sizeof(SkyboxPush));
|
||||||
|
|
||||||
const std::string shaderDir = "shaders/env/";
|
const std::string shaderDir = "shaders/env/";
|
||||||
auto vert = shaders.CreateFromFile(
|
auto vert = shaders.CreateFromFile(
|
||||||
shaderDir + "skybox.vert.spv",
|
shaderDir + "skybox.vert.spv",
|
||||||
vk::ShaderStageFlagBits::eVertex,
|
vk::ShaderStageFlagBits::eVertex,
|
||||||
std::vector<vk::DescriptorSetLayout>{m_SetLayout},
|
std::vector<vk::DescriptorSetLayout>{m_SetLayout},
|
||||||
sizeof(glm::mat4));
|
sizeof(SkyboxPush));
|
||||||
auto frag = shaders.CreateFromFile(
|
auto frag = shaders.CreateFromFile(
|
||||||
shaderDir + "skybox.frag.spv",
|
shaderDir + "skybox.frag.spv",
|
||||||
vk::ShaderStageFlagBits::eFragment,
|
vk::ShaderStageFlagBits::eFragment,
|
||||||
std::vector<vk::DescriptorSetLayout>{m_SetLayout},
|
std::vector<vk::DescriptorSetLayout>{m_SetLayout},
|
||||||
sizeof(glm::mat4));
|
sizeof(SkyboxPush));
|
||||||
|
|
||||||
m_Vert = std::move(vert);
|
m_Vert = std::move(vert);
|
||||||
m_Frag = std::move(frag);
|
m_Frag = std::move(frag);
|
||||||
@@ -108,10 +117,26 @@ namespace Engine::Passes
|
|||||||
.setStoreOp(vk::AttachmentStoreOp::eStore)
|
.setStoreOp(vk::AttachmentStoreOp::eStore)
|
||||||
.setClearValue(clear);
|
.setClearValue(clear);
|
||||||
|
|
||||||
|
// velocity 附件:本 pass 是 velocity 的 first writer。清 0 后,
|
||||||
|
// 画天空时写天空运动向量;无天空/不画时保持 0(背景静止)。
|
||||||
|
// Forward 段改为 Load 承接本 pass 的结果
|
||||||
|
vk::ClearValue velocityClear;
|
||||||
|
velocityClear.color = vk::ClearColorValue(
|
||||||
|
std::array<float, 4>{0.f, 0.f, 0.f, 0.f});
|
||||||
|
vk::RenderingAttachmentInfo velocity;
|
||||||
|
velocity.setImageView(ctx.View(m_Velocity))
|
||||||
|
.setImageLayout(vk::ImageLayout::eColorAttachmentOptimal)
|
||||||
|
.setLoadOp(vk::AttachmentLoadOp::eClear)
|
||||||
|
.setStoreOp(vk::AttachmentStoreOp::eStore)
|
||||||
|
.setClearValue(velocityClear);
|
||||||
|
|
||||||
|
const std::array<vk::RenderingAttachmentInfo, 2> colorArray = {
|
||||||
|
color, velocity};
|
||||||
|
|
||||||
vk::RenderingInfo renderingInfo;
|
vk::RenderingInfo renderingInfo;
|
||||||
renderingInfo.setRenderArea({{0, 0}, ctx.extent})
|
renderingInfo.setRenderArea({{0, 0}, ctx.extent})
|
||||||
.setLayerCount(1)
|
.setLayerCount(1)
|
||||||
.setColorAttachments(color); // 无深度附件
|
.setColorAttachments(colorArray); // 无深度附件
|
||||||
|
|
||||||
cmd.beginRendering(renderingInfo);
|
cmd.beginRendering(renderingInfo);
|
||||||
|
|
||||||
@@ -128,11 +153,12 @@ namespace Engine::Passes
|
|||||||
cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics,
|
cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics,
|
||||||
m_Layout, 0,
|
m_Layout, 0,
|
||||||
m_Sets[ctx.frame.frameSlot], {});
|
m_Sets[ctx.frame.frameSlot], {});
|
||||||
cmd.pushConstants<glm::mat4>(
|
const SkyboxPush push{m_Frame.skyInvVP, m_Frame.skyPrevVP};
|
||||||
|
cmd.pushConstants<SkyboxPush>(
|
||||||
m_Layout,
|
m_Layout,
|
||||||
vk::ShaderStageFlagBits::eVertex |
|
vk::ShaderStageFlagBits::eVertex |
|
||||||
vk::ShaderStageFlagBits::eFragment,
|
vk::ShaderStageFlagBits::eFragment,
|
||||||
0, m_Frame.skyInvVP);
|
0, push);
|
||||||
|
|
||||||
// ---- 动态状态 ----
|
// ---- 动态状态 ----
|
||||||
cmd.setRasterizerDiscardEnable(VK_FALSE);
|
cmd.setRasterizerDiscardEnable(VK_FALSE);
|
||||||
@@ -180,16 +206,22 @@ namespace Engine::Passes
|
|||||||
vk::ColorComponentFlagBits::eG |
|
vk::ColorComponentFlagBits::eG |
|
||||||
vk::ColorComponentFlagBits::eB |
|
vk::ColorComponentFlagBits::eB |
|
||||||
vk::ColorComponentFlagBits::eA;
|
vk::ColorComponentFlagBits::eA;
|
||||||
cmd.setColorBlendEnableEXT(0, blendEnable);
|
cmd.setColorBlendEnableEXT(0,
|
||||||
cmd.setColorBlendEquationEXT(0, blendEquation);
|
std::array<vk::Bool32, 2>{VK_FALSE, VK_FALSE});
|
||||||
cmd.setColorWriteMaskEXT(0, colorWriteMask);
|
const std::array<vk::ColorBlendEquationEXT, 2> blendEquations = {
|
||||||
|
blendEquation, blendEquation};
|
||||||
|
cmd.setColorBlendEquationEXT(0, blendEquations);
|
||||||
|
const std::array<vk::ColorComponentFlags, 2> writeMasks = {
|
||||||
|
colorWriteMask, colorWriteMask};
|
||||||
|
cmd.setColorWriteMaskEXT(0, writeMasks);
|
||||||
|
|
||||||
cmd.draw(3, 1, 0, 0);
|
cmd.draw(3, 1, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.endRendering();
|
cmd.endRendering();
|
||||||
})
|
})
|
||||||
.Write(m_HDRColor, RG::ResourceUse::ColorAttachment);
|
.Write(m_HDRColor, RG::ResourceUse::ColorAttachment)
|
||||||
|
.Write(m_Velocity, RG::ResourceUse::ColorAttachment);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Engine::Passes
|
} // namespace Engine::Passes
|
||||||
|
|||||||
@@ -9,9 +9,10 @@ namespace Engine::Passes
|
|||||||
{
|
{
|
||||||
TAAPass::TAAPass(FullscreenGpu& gpu, FrameSceneData& frame, f32 historyAlpha,
|
TAAPass::TAAPass(FullscreenGpu& gpu, FrameSceneData& frame, f32 historyAlpha,
|
||||||
RG::TextureHandle hdrColor, RG::TextureHandle velocity,
|
RG::TextureHandle hdrColor, RG::TextureHandle velocity,
|
||||||
RG::TextureHandle taaColor)
|
RG::TextureHandle depth, RG::TextureHandle taaColor)
|
||||||
: m_Gpu(gpu), m_Frame(frame), m_HistoryAlpha(historyAlpha),
|
: m_Gpu(gpu), m_Frame(frame), m_HistoryAlpha(historyAlpha),
|
||||||
m_HDR(hdrColor), m_Velocity(velocity), m_TAAColor(taaColor)
|
m_HDR(hdrColor), m_Velocity(velocity), m_Depth(depth),
|
||||||
|
m_TAAColor(taaColor)
|
||||||
{
|
{
|
||||||
m_Sets.resize(kMaxFramesInFlight);
|
m_Sets.resize(kMaxFramesInFlight);
|
||||||
for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) {
|
for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) {
|
||||||
@@ -26,14 +27,25 @@ namespace Engine::Passes
|
|||||||
// TAA 关闭:alpha=0 → mix(cur, hist, 0) 直通当前帧
|
// TAA 关闭:alpha=0 → mix(cur, hist, 0) 直通当前帧
|
||||||
//(抖动已由 SceneRenderer 同步停掉,画面静止无锯齿晃动)
|
//(抖动已由 SceneRenderer 同步停掉,画面静止无锯齿晃动)
|
||||||
const f32 alpha = m_Frame.taaEnabled ? m_HistoryAlpha : 0.0f;
|
const f32 alpha = m_Frame.taaEnabled ? m_HistoryAlpha : 0.0f;
|
||||||
|
// push 槽位:{ortho, alpha, zNear, zFar}——去遮挡检测在 shader
|
||||||
|
// 内用 near/far 把深度线性化,正交模式走另一分支
|
||||||
m_Gpu.RecordFullscreen(ctx, ctx.View(m_TAAColor), m_Gpu.taaFrag,
|
m_Gpu.RecordFullscreen(ctx, ctx.View(m_TAAColor), m_Gpu.taaFrag,
|
||||||
m_Sets[ctx.frame.frameSlot],
|
m_Sets[ctx.frame.frameSlot],
|
||||||
FullscreenPush{0.0f, alpha, 0.0f, 0.0f});
|
FullscreenPush{m_Frame.cameraOrtho ? 1.0f : 0.0f,
|
||||||
|
alpha,
|
||||||
|
m_Frame.cameraNearZ,
|
||||||
|
m_Frame.cameraFarZ});
|
||||||
})
|
})
|
||||||
.Read(m_HDR, RG::ResourceUse::ShaderRead,
|
.Read(m_HDR, RG::ResourceUse::ShaderRead,
|
||||||
vk::ShaderStageFlagBits::eFragment)
|
vk::ShaderStageFlagBits::eFragment)
|
||||||
.Read(m_Velocity, RG::ResourceUse::ShaderRead,
|
.Read(m_Velocity, RG::ResourceUse::ShaderRead,
|
||||||
vk::ShaderStageFlagBits::eFragment)
|
vk::ShaderStageFlagBits::eFragment)
|
||||||
|
.Read(m_Depth, RG::ResourceUse::ShaderRead,
|
||||||
|
vk::ShaderStageFlagBits::eFragment)
|
||||||
|
// 上一帧深度(同 m_Depth 的上一槽位镜像):去遮挡检测比较
|
||||||
|
// 当前像素深度与重投影点上的历史深度,判断历史是否属于同一表面
|
||||||
|
.ReadHistory(m_Depth, RG::ResourceUse::ShaderRead,
|
||||||
|
vk::ShaderStageFlagBits::eFragment)
|
||||||
// 自引用历史:TAAColor 的上一帧槽位就是累积历史
|
// 自引用历史:TAAColor 的上一帧槽位就是累积历史
|
||||||
.ReadHistory(m_TAAColor, RG::ResourceUse::ShaderRead,
|
.ReadHistory(m_TAAColor, RG::ResourceUse::ShaderRead,
|
||||||
vk::ShaderStageFlagBits::eFragment)
|
vk::ShaderStageFlagBits::eFragment)
|
||||||
@@ -45,6 +57,11 @@ 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_Velocity, slot);
|
m_Gpu.UpdateSetInput(m_Sets[slot], 1, rg, m_Velocity, slot);
|
||||||
|
// binding3 = 场景深度(深度感知 dilation)
|
||||||
|
m_Gpu.UpdateSetInput(m_Sets[slot], 3, rg, m_Depth, slot);
|
||||||
|
// binding4 = 上一帧深度(去遮挡检测,历史槽位镜像)
|
||||||
|
m_Gpu.UpdateSetInput(m_Sets[slot], 4, rg, m_Depth,
|
||||||
|
rg.HistorySlot(slot));
|
||||||
// binding2 = 历史(上一帧槽位镜像)
|
// binding2 = 历史(上一帧槽位镜像)
|
||||||
m_Gpu.UpdateSetInput(m_Sets[slot], 2, rg, m_TAAColor,
|
m_Gpu.UpdateSetInput(m_Sets[slot], 2, rg, m_TAAColor,
|
||||||
rg.HistorySlot(slot));
|
rg.HistorySlot(slot));
|
||||||
|
|||||||
@@ -500,7 +500,7 @@ namespace Engine
|
|||||||
// Skybox = HDRColor 的 first writer(清屏 + 画背景),先于 Forward 注册
|
// Skybox = HDRColor 的 first writer(清屏 + 画背景),先于 Forward 注册
|
||||||
m_Passes.push_back(CreateScope<Passes::SkyboxPass>(
|
m_Passes.push_back(CreateScope<Passes::SkyboxPass>(
|
||||||
*m_Device, *m_ShaderManager, *m_DescriptorManager, frame,
|
*m_Device, *m_ShaderManager, *m_DescriptorManager, frame,
|
||||||
m_HDRColor, m_Environment.get()));
|
m_HDRColor, m_Velocity, m_Environment.get()));
|
||||||
m_Passes.push_back(CreateScope<Passes::ForwardPass>(
|
m_Passes.push_back(CreateScope<Passes::ForwardPass>(
|
||||||
*m_SceneGpu, frame, m_HDRColor, m_Velocity, m_Depth,
|
*m_SceneGpu, frame, m_HDRColor, m_Velocity, m_Depth,
|
||||||
m_SceneColor, m_ShadowMaps));
|
m_SceneColor, m_ShadowMaps));
|
||||||
@@ -539,7 +539,7 @@ namespace Engine
|
|||||||
*m_ShaderManager, *m_SceneGpu, frame, m_SelectionMask));
|
*m_ShaderManager, *m_SceneGpu, frame, m_SelectionMask));
|
||||||
m_Passes.push_back(CreateScope<Passes::TAAPass>(
|
m_Passes.push_back(CreateScope<Passes::TAAPass>(
|
||||||
*m_FullscreenGpu, frame, /*historyAlpha*/ 0.9f, m_HDRColor,
|
*m_FullscreenGpu, frame, /*historyAlpha*/ 0.9f, m_HDRColor,
|
||||||
m_Velocity, m_TAAColor));
|
m_Velocity, m_Depth, 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>(
|
||||||
|
|||||||
@@ -466,7 +466,7 @@ namespace Engine
|
|||||||
// ---- 0. TAA 时域状态先行(velocity = 上一帧姿态与相机)----
|
// ---- 0. TAA 时域状态先行(velocity = 上一帧姿态与相机)----
|
||||||
UniformBufferObject& ubo = m_Frame.ubo;
|
UniformBufferObject& ubo = m_Frame.ubo;
|
||||||
ubo.prevView = m_LastView;
|
ubo.prevView = m_LastView;
|
||||||
ubo.prevProj = m_LastProjNoJitter;
|
ubo.prevProj = m_LastProjNoJitter; // 上一帧未抖动投影(velocity 用)
|
||||||
|
|
||||||
// ---- 1. 相机:渲染源按形态分派(UE 语义)。编辑器形态
|
// ---- 1. 相机:渲染源按形态分派(UE 语义)。编辑器形态
|
||||||
// Scene view 恒用编辑器相机(视口 UI 状态,空场景可用);
|
// Scene view 恒用编辑器相机(视口 UI 状态,空场景可用);
|
||||||
@@ -506,6 +506,13 @@ namespace Engine
|
|||||||
// 编辑器形态 = 编辑器相机,游戏形态 = primary 相机实体
|
// 编辑器形态 = 编辑器相机,游戏形态 = primary 相机实体
|
||||||
ubo.proj = camera->GetProjection(aspect);
|
ubo.proj = camera->GetProjection(aspect);
|
||||||
|
|
||||||
|
// 相机裁剪面 + 投影模式落帧:TAA 去遮挡检测把 [0,1] 深度线性化
|
||||||
|
// 为视图空间距离做比较,需要 near/far 与投影模式
|
||||||
|
m_Frame.cameraNearZ = camera->GetNearZ();
|
||||||
|
m_Frame.cameraFarZ = camera->GetFarZ();
|
||||||
|
m_Frame.cameraOrtho =
|
||||||
|
camera->GetProjectionMode() == Camera::ProjectionMode::Orthographic;
|
||||||
|
|
||||||
// 未抖动投影:velocity 的当前帧 clip 位置用它(抖动不能进 velocity)
|
// 未抖动投影:velocity 的当前帧 clip 位置用它(抖动不能进 velocity)
|
||||||
ubo.projNoJitter = ubo.proj;
|
ubo.projNoJitter = ubo.proj;
|
||||||
|
|
||||||
@@ -581,6 +588,12 @@ namespace Engine
|
|||||||
m_Frame.skyInvVP =
|
m_Frame.skyInvVP =
|
||||||
glm::inverse(ubo.projNoJitter * glm::mat4(glm::mat3(ubo.view)));
|
glm::inverse(ubo.projNoJitter * glm::mat4(glm::mat3(ubo.view)));
|
||||||
|
|
||||||
|
// 天空 velocity:上一帧未抖动 proj × 上一帧视图(去平移)。天空是
|
||||||
|
// 无限远表面,方向不随时间平移变化,故用 w=0 传入方向即忽略平移;
|
||||||
|
// 相机旋转/移动时天空的运动向量由此精确得出
|
||||||
|
m_Frame.skyPrevVP =
|
||||||
|
m_LastProjNoJitter * glm::mat4(glm::mat3(m_LastView));
|
||||||
|
|
||||||
// ---- 2. 灯光(CSM 阴影光源约定 = lights[0] 必是方向光)----
|
// ---- 2. 灯光(CSM 阴影光源约定 = lights[0] 必是方向光)----
|
||||||
// entt Each 遍历序与实体创建序不保证一致——直接按迭代序填充会让
|
// entt Each 遍历序与实体创建序不保证一致——直接按迭代序填充会让
|
||||||
// lights[0] 落到点光上(params.x=1)→ shader 阴影分支整个跳过。
|
// lights[0] 落到点光上(params.x=1)→ shader 阴影分支整个跳过。
|
||||||
|
|||||||
Vendored
+12
-1
@@ -6,16 +6,27 @@
|
|||||||
|
|
||||||
layout(location = 0) in vec2 fragUV;
|
layout(location = 0) in vec2 fragUV;
|
||||||
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 samplerCube envMap;
|
layout(set = 0, binding = 0) uniform samplerCube envMap;
|
||||||
|
|
||||||
// push 与 skybox.vert 一致声明(VUID-08878)
|
// push 与 skybox.vert 一致声明(VUID-08878)
|
||||||
layout(push_constant) uniform PushConstants {
|
layout(push_constant) uniform PushConstants {
|
||||||
mat4 invVP;
|
mat4 invVP; // 当前帧反投影(方向重建)
|
||||||
|
mat4 prevVP; // 上一帧 proj×view(去平移):天空 velocity
|
||||||
} pc;
|
} pc;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec4 target = pc.invVP * vec4(fragUV * 2.0 - 1.0, 1.0, 1.0);
|
vec4 target = pc.invVP * vec4(fragUV * 2.0 - 1.0, 1.0, 1.0);
|
||||||
vec3 dir = normalize(target.xyz / target.w);
|
vec3 dir = normalize(target.xyz / target.w);
|
||||||
outColor = vec4(texture(envMap, dir).rgb, 1.0);
|
outColor = vec4(texture(envMap, dir).rgb, 1.0);
|
||||||
|
|
||||||
|
// 天空 velocity:天空是无限远表面,方向不随相机平移变化(w=0 忽略
|
||||||
|
// 平移),过上一帧 proj×view 得上一帧屏幕位置,与当前 fragUV 之差即
|
||||||
|
// 运动向量。相机移动时天空/背景因此正确重投影,不再闪烁
|
||||||
|
vec4 prevClip = pc.prevVP * vec4(dir, 0.0);
|
||||||
|
vec2 prevUV = abs(prevClip.w) > 1e-6
|
||||||
|
? (prevClip.xy / prevClip.w) * 0.5 + 0.5
|
||||||
|
: fragUV;
|
||||||
|
outVelocity = fragUV - prevUV;
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+2
-1
@@ -8,7 +8,8 @@ layout(location = 0) out vec2 fragUV;
|
|||||||
|
|
||||||
// push 与 skybox.frag 一致声明(VUID-08878:shader object 接口一致)
|
// push 与 skybox.frag 一致声明(VUID-08878:shader object 接口一致)
|
||||||
layout(push_constant) uniform PushConstants {
|
layout(push_constant) uniform PushConstants {
|
||||||
mat4 invVP;
|
mat4 invVP; // 当前帧反投影(方向重建)
|
||||||
|
mat4 prevVP; // 上一帧 proj×view(去平移):天空 velocity
|
||||||
} pc;
|
} pc;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
|||||||
@@ -75,7 +75,10 @@ void main() {
|
|||||||
gl_Position = ubo.proj * ubo.view * worldPos;
|
gl_Position = ubo.proj * ubo.view * worldPos;
|
||||||
|
|
||||||
// ---- TAA velocity:当前帧与上一帧的 clip 空间位置差(UV 空间)----
|
// ---- TAA velocity:当前帧与上一帧的 clip 空间位置差(UV 空间)----
|
||||||
// 抖动只进光栅化 gl_Position,不进 velocity(否则抖动本身被当成运动)
|
// 抖动不进 velocity:velocity 描述表面点的真实屏幕位移,必须用未抖动投影。
|
||||||
|
// 静态内容 → velocity=0 → 历史精确采回同一纹素,只做逐帧抖动的超采样平均
|
||||||
|
//(清清楚楚、无重采样模糊)。若把抖动计入 velocity,静止画面也会得到非零
|
||||||
|
// 速度 → 每帧亚纹素重采样历史 → 持续发糊(本次"画面很糊"的根因)。
|
||||||
// v1:蒙皮形变不进 velocity(prev 用同一蒙皮姿态),边缘 ghosting 接受
|
// v1:蒙皮形变不进 velocity(prev 用同一蒙皮姿态),边缘 ghosting 接受
|
||||||
vec4 currClip = ubo.projNoJitter * ubo.view * worldPos;
|
vec4 currClip = ubo.projNoJitter * ubo.view * worldPos;
|
||||||
vec4 prevWorldPos = prevObjects.prevModels[gl_InstanceIndex] * vec4(skinnedPos, 1.0);
|
vec4 prevWorldPos = prevObjects.prevModels[gl_InstanceIndex] * vec4(skinnedPos, 1.0);
|
||||||
|
|||||||
+53
-15
@@ -1,26 +1,32 @@
|
|||||||
#version 450
|
#version 450
|
||||||
|
|
||||||
// TAA resolve:时域抗锯齿。
|
// TAA resolve:时域抗锯齿。
|
||||||
// 1. velocity 反推历史像素位置,取历史累积色
|
// 1. 深度感知 velocity dilation:轮廓处取最近表面的速度,反推历史像素位置
|
||||||
// 2. 方差裁剪(YCoCg):历史色钳向邻域均值 ± γσ(∩ AABB 极值盒)——
|
// 2. 去遮挡检测(disocclusion):重投影点上的"上一帧深度"若与当前像素
|
||||||
// 比 RGB min/max 盒稳得多:AABB 盒子被逐帧抖动的高亮像素拽来拽去,
|
// 深度显著不同 → 该像素上一帧属于别的表面(刚露出/刚被遮挡),
|
||||||
// 收敛历史持续被重钳 → 周期性残差经 bloom 阈值放大 = 静态模型泛光闪烁
|
// 历史颜色不属于当前表面 → 拒绝历史(alpha→0),用当前帧。
|
||||||
// 3. mix(cur, hist, alpha) 时域累积
|
// 这是"移动时一闪一闪"的结构性根因:只查 histUV 是否出屏、不查历史
|
||||||
|
// 是否同一表面时,新暴露区域会持续采到前/背景串味的历史色
|
||||||
|
// 3. 方差裁剪(YCoCg):历史色钳向邻域均值 ± γσ(∩ AABB 极值盒)
|
||||||
|
// 4. mix(cur, hist, alpha) 时域累积
|
||||||
// 输出写入 TAAColor,它同时也是下一帧的历史(ReadHistory 自引用)。
|
// 输出写入 TAAColor,它同时也是下一帧的历史(ReadHistory 自引用)。
|
||||||
|
|
||||||
layout(location = 0) in vec2 fragUV;
|
layout(location = 0) in vec2 fragUV;
|
||||||
layout(location = 0) out vec4 outColor;
|
layout(location = 0) out vec4 outColor;
|
||||||
|
|
||||||
layout(set = 0, binding = 0) uniform sampler2D curImage; // 当前帧 HDR
|
layout(set = 0, binding = 0) uniform sampler2D curImage; // 当前帧 HDR
|
||||||
layout(set = 0, binding = 1) uniform sampler2D velocityImage; // 运动向量(UV 空间)
|
layout(set = 0, binding = 1) uniform sampler2D velocityImage; // 运动向量(UV 空间)
|
||||||
layout(set = 0, binding = 2) uniform sampler2D historyImage; // 上一帧累积结果
|
layout(set = 0, binding = 2) uniform sampler2D historyImage; // 上一帧累积结果
|
||||||
|
layout(set = 0, binding = 3) uniform sampler2D depthImage; // 当前帧深度(近=0,远=1)
|
||||||
|
layout(set = 0, binding = 4) uniform sampler2D historyDepthImage; // 上一帧深度(同资源历史槽位)
|
||||||
|
|
||||||
// 全屏 pass 通用 push(16B,程序级统一声明,VUID-08878)
|
// 全屏 pass 通用 push(16B,程序级统一声明,VUID-08878):
|
||||||
|
// 本 pass 语义 = {ortho, alpha, zNear, zFar}
|
||||||
layout(push_constant) uniform PushConstants {
|
layout(push_constant) uniform PushConstants {
|
||||||
float exposure; // 与其它全屏 pass 布局一致(本 pass 不用)
|
float ortho; // 1 = 正交投影(深度线性化走线性分支)
|
||||||
float alpha; // 历史权重(0.9 = 90% 历史)
|
float alpha; // 历史权重(0.9 = 90% 历史)
|
||||||
float param1;
|
float zNear; // 相机近裁剪面
|
||||||
float param2;
|
float zFar; // 相机远裁剪面
|
||||||
} pc;
|
} pc;
|
||||||
|
|
||||||
// YCoCg 感知空间:亮度/色度分离,方差裁剪不在 HDR RGB 直接做
|
// YCoCg 感知空间:亮度/色度分离,方差裁剪不在 HDR RGB 直接做
|
||||||
@@ -36,20 +42,52 @@ vec3 YCoCgToRgb(vec3 c) {
|
|||||||
c.x - c.y - c.z);
|
c.x - c.y - c.z);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// [0,1] 深度 → 视图空间距离(正值,近小远大)。
|
||||||
|
// 透视:d = 0.5·ndc + 0.5 重映射后,z = n·f / (f − d·(f−n))
|
||||||
|
//(n → d=0,f → d=1)。正交:深度本身线性,z = n + d·(f−n)
|
||||||
|
float linearizeDepth(float d, float n, float f, float ortho) {
|
||||||
|
float persp = (n * f) / (f - d * (f - n));
|
||||||
|
float orthoL = n + d * (f - n);
|
||||||
|
return mix(persp, orthoL, ortho);
|
||||||
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec4 cur = texture(curImage, fragUV);
|
vec4 cur = texture(curImage, fragUV);
|
||||||
|
vec2 ts = 1.0 / vec2(textureSize(curImage, 0));
|
||||||
|
|
||||||
|
// 每像素 velocity 直接取自 velocity 缓冲(几何体与天空各自写入),
|
||||||
|
// 不做 dilation:像素中心属谁就用谁的速度。min-depth dilation 会把
|
||||||
|
// 前景(快物体)速度串给轮廓旁的背景像素 → 背景重投影到错误位置 →
|
||||||
|
// 轮廓周围持续闪烁,故移除。
|
||||||
vec2 vel = texture(velocityImage, fragUV).xy;
|
vec2 vel = texture(velocityImage, fragUV).xy;
|
||||||
|
float dCenter = texture(depthImage, fragUV).r; // 中心像素当前深度
|
||||||
vec2 histUV = fragUV - vel;
|
vec2 histUV = fragUV - vel;
|
||||||
|
|
||||||
|
// 运动自适应历史权重:静止 → 高权重(充分累积抗锯齿、稳定锐利);
|
||||||
|
// 运动/遮挡 → 降权重(0.4),快速收敛,消除拖影与移动闪烁。
|
||||||
|
// 固定高权重是"移动闪烁"的根源:新暴露区域要很多帧才收敛(UE/Unity 同款)
|
||||||
|
float speed = clamp(length(vel) * 100.0, 0.0, 1.0);
|
||||||
|
float alpha = mix(pc.alpha, 0.4, speed);
|
||||||
|
|
||||||
vec3 result = cur.rgb;
|
vec3 result = cur.rgb;
|
||||||
|
|
||||||
// 历史采样:越出画面(相机快速平移)直接用当前帧,不累积
|
// 历史采样:越出画面(相机快速平移)直接用当前帧,不累积
|
||||||
if (all(greaterThanEqual(histUV, vec2(0.0))) &&
|
if (all(greaterThanEqual(histUV, vec2(0.0))) &&
|
||||||
all(lessThanEqual(histUV, vec2(1.0)))) {
|
all(lessThanEqual(histUV, vec2(1.0)))) {
|
||||||
|
// ---- 去遮挡检测:深度一致性 ----
|
||||||
|
// 比较当前像素的视图距离与重投影点上的上一帧视图距离。差异显著
|
||||||
|
// 说明该像素上一帧被另一表面占据(刚露出/刚被遮挡)→ 历史无效,
|
||||||
|
// alpha 归零(新暴露像素直接吃当前帧,下一帧起重新累积)。
|
||||||
|
// 阈值取相对量:与绝对距离无关,近/远场景同一套参数
|
||||||
|
float zCur = linearizeDepth(dCenter, pc.zNear, pc.zFar, pc.ortho);
|
||||||
|
float zHist = linearizeDepth(texture(historyDepthImage, histUV).r,
|
||||||
|
pc.zNear, pc.zFar, pc.ortho);
|
||||||
|
float relDiff = abs(zCur - zHist) / max(zCur, 1e-4);
|
||||||
|
alpha *= 1.0 - smoothstep(0.05, 0.15, relDiff);
|
||||||
|
|
||||||
vec3 hist = texture(historyImage, histUV).rgb;
|
vec3 hist = texture(historyImage, histUV).rgb;
|
||||||
|
|
||||||
// 邻域统计(3×3,YCoCg):均值 m1、均方 m2、极值盒 [mn, mx]
|
// 邻域统计(3×3,YCoCg):均值 m1、均方 m2、极值盒 [mn, mx]
|
||||||
vec2 ts = 1.0 / vec2(textureSize(curImage, 0));
|
|
||||||
vec3 m1 = vec3(0.0);
|
vec3 m1 = vec3(0.0);
|
||||||
vec3 m2 = vec3(0.0);
|
vec3 m2 = vec3(0.0);
|
||||||
vec3 mn = vec3(1e9);
|
vec3 mn = vec3(1e9);
|
||||||
@@ -75,7 +113,7 @@ void main() {
|
|||||||
vec3 hi = min(m1 + kGamma * sigma, mx);
|
vec3 hi = min(m1 + kGamma * sigma, mx);
|
||||||
|
|
||||||
vec3 clamped = YCoCgToRgb(clamp(RgbToYCoCg(hist), lo, hi));
|
vec3 clamped = YCoCgToRgb(clamp(RgbToYCoCg(hist), lo, hi));
|
||||||
result = mix(cur.rgb, clamped, pc.alpha);
|
result = mix(cur.rgb, clamped, alpha);
|
||||||
}
|
}
|
||||||
|
|
||||||
outColor = vec4(result, cur.a);
|
outColor = vec4(result, cur.a);
|
||||||
|
|||||||
Reference in New Issue
Block a user