From 2024772bacb644821bf3ce320328aaf020ffb066 Mon Sep 17 00:00:00 2001 From: atdunbg Date: Sun, 18 Oct 2026 15:22:48 +0800 Subject: [PATCH] =?UTF-8?q?TAA=20=E6=97=B6=E5=9F=9F=E9=80=9F=E5=BA=A6?= =?UTF-8?q?=E9=93=BE=E8=B7=AF=E8=B4=AF=E9=80=9A=EF=BC=9A=E5=A4=A9=E7=A9=BA?= =?UTF-8?q?=E7=9B=92=E8=A1=A5=E5=86=99=20velocity=EF=BC=88=E6=AD=A4?= =?UTF-8?q?=E5=89=8D=E6=81=92=200=EF=BC=8C=E7=9B=B8=E6=9C=BA=E7=A7=BB?= =?UTF-8?q?=E5=8A=A8=E6=97=B6=E5=A4=A9=E7=A9=BA/=E8=83=8C=E6=99=AF?= =?UTF-8?q?=E9=87=8D=E6=8A=95=E5=BD=B1=E9=94=99=E8=AF=AF=20=E2=86=92=20?= =?UTF-8?q?=E9=97=AA=E7=83=81=EF=BC=89=EF=BC=9BskyPrevVP=EF=BC=88=E4=B8=8A?= =?UTF-8?q?=E4=B8=80=E5=B8=A7=E6=9C=AA=E6=8A=96=E5=8A=A8=20proj=20=C3=97?= =?UTF-8?q?=20=E5=8E=BB=E5=B9=B3=E7=A7=BB=E8=A7=86=E5=9B=BE=EF=BC=89?= =?UTF-8?q?=E8=90=BD=E5=B8=A7=EF=BC=9BTAA=20=E5=8A=A0=E5=8E=BB=E9=81=AE?= =?UTF-8?q?=E6=8C=A1=E6=B7=B1=E5=BA=A6=E6=A3=80=E6=B5=8B=EF=BC=88=E9=87=8D?= =?UTF-8?q?=E6=8A=95=E5=BD=B1=E7=82=B9=E5=8E=86=E5=8F=B2=E6=B7=B1=E5=BA=A6?= =?UTF-8?q?=E4=B8=8E=E5=BD=93=E5=89=8D=E5=83=8F=E7=B4=A0=E6=AF=94=E5=AF=B9?= =?UTF-8?q?=EF=BC=8C=E6=96=B0=E6=9A=B4=E9=9C=B2=E5=8C=BA=E6=8B=92=E5=8E=86?= =?UTF-8?q?=E5=8F=B2=EF=BC=8C=E9=9C=80=20near/far=20+=20=E6=8A=95=E5=BD=B1?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F=E8=90=BD=E5=B8=A7=EF=BC=89=EF=BC=9B=E7=A7=BB?= =?UTF-8?q?=E9=99=A4=20min-depth=20dilation=EF=BC=88=E6=8A=8A=E5=89=8D?= =?UTF-8?q?=E6=99=AF=E9=80=9F=E5=BA=A6=E6=B3=84=E6=BC=8F=E7=BB=99=E8=BD=AE?= =?UTF-8?q?=E5=BB=93=E6=97=81=E8=83=8C=E6=99=AF=E5=83=8F=E7=B4=A0=20?= =?UTF-8?q?=E2=86=92=20=E8=BE=B9=E7=95=8C=E9=97=AA=EF=BC=89=EF=BC=9BForwar?= =?UTF-8?q?d=20=E4=B8=8D=E9=80=8F=E6=98=8E=E6=AE=B5=20velocity=20=E9=99=84?= =?UTF-8?q?=E4=BB=B6=20Clear=E2=86=92Load=20=E6=89=BF=E6=8E=A5=E5=A4=A9?= =?UTF-8?q?=E7=A9=BA=E7=BB=93=E6=9E=9C=EF=BC=9Bvelocity=20=E4=B8=8D?= =?UTF-8?q?=E5=85=A5=E6=8A=96=E5=8A=A8=E6=B3=A8=E9=87=8A=E8=AE=A2=E6=AD=A3?= =?UTF-8?q?=EF=BC=88=E6=8A=96=E5=8A=A8=E8=BF=9B=20velocity=20=3D=20?= =?UTF-8?q?=E9=9D=99=E6=AD=A2=E7=94=BB=E9=9D=A2=E6=8C=81=E7=BB=AD=E4=BA=9A?= =?UTF-8?q?=E7=BA=B9=E7=B4=A0=E9=87=8D=E9=87=87=E6=A0=B7=E5=8F=91=E7=B3=8A?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Engine/Renderer/Passes/SkyboxPass.h | 6 +- .../include/Engine/Renderer/Passes/TAAPass.h | 3 +- .../include/Engine/Renderer/SceneRenderer.h | 2 +- Engine/include/Engine/Renderer/SceneTypes.h | 17 ++++- Engine/src/Renderer/Passes/ForwardPass.cpp | 6 +- Engine/src/Renderer/Passes/SkyboxPass.cpp | 58 ++++++++++++---- Engine/src/Renderer/Passes/TAAPass.cpp | 23 ++++++- Engine/src/Renderer/Renderer.cpp | 4 +- Engine/src/Renderer/SceneRenderer.cpp | 15 +++- shaders/env/skybox.frag | 13 +++- shaders/env/skybox.vert | 3 +- shaders/pbr/simple_shader.vert | 5 +- shaders/post/taa.frag | 68 +++++++++++++++---- 13 files changed, 178 insertions(+), 45 deletions(-) diff --git a/Engine/include/Engine/Renderer/Passes/SkyboxPass.h b/Engine/include/Engine/Renderer/Passes/SkyboxPass.h index 283c3da..54e38bd 100644 --- a/Engine/include/Engine/Renderer/Passes/SkyboxPass.h +++ b/Engine/include/Engine/Renderer/Passes/SkyboxPass.h @@ -35,7 +35,8 @@ namespace Engine { // env 可为空(HDR 资产缺失):仅承担清屏,天空保持清屏色 SkyboxPass(Device& device, ShaderManager& shaders, DescriptorManager& descriptors, FrameSceneData& frame, - RG::TextureHandle hdrColor, const Cubemap* env); + RG::TextureHandle hdrColor, RG::TextureHandle velocity, + const Cubemap* env); ~SkyboxPass(); SkyboxPass(const SkyboxPass&) = delete; @@ -49,10 +50,11 @@ namespace Engine { DescriptorManager& m_Descriptors; FrameSceneData& m_Frame; RG::TextureHandle m_HDRColor; + RG::TextureHandle m_Velocity; // 天空 velocity 附件(TAA) const Cubemap* m_Env; // 可空 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; // 析构自动销毁 std::array m_Sets{}; }; diff --git a/Engine/include/Engine/Renderer/Passes/TAAPass.h b/Engine/include/Engine/Renderer/Passes/TAAPass.h index a6a073b..c1e767e 100644 --- a/Engine/include/Engine/Renderer/Passes/TAAPass.h +++ b/Engine/include/Engine/Renderer/Passes/TAAPass.h @@ -22,7 +22,7 @@ namespace Engine::Passes { public: TAAPass(FullscreenGpu& gpu, FrameSceneData& frame, f32 historyAlpha, RG::TextureHandle hdrColor, RG::TextureHandle velocity, - RG::TextureHandle taaColor); + RG::TextureHandle depth, RG::TextureHandle taaColor); ~TAAPass() override = default; void Register(RG::RenderGraph& rg) override; @@ -34,6 +34,7 @@ namespace Engine::Passes { f32 m_HistoryAlpha; // 开启时的历史权重 RG::TextureHandle m_HDR; RG::TextureHandle m_Velocity; + RG::TextureHandle m_Depth; // 场景深度(深度感知 dilation 用) RG::TextureHandle m_TAAColor; // 输出 = 下一帧的历史 std::vector m_Sets; // per-slot diff --git a/Engine/include/Engine/Renderer/SceneRenderer.h b/Engine/include/Engine/Renderer/SceneRenderer.h index e10484b..33f84b6 100644 --- a/Engine/include/Engine/Renderer/SceneRenderer.h +++ b/Engine/include/Engine/Renderer/SceneRenderer.h @@ -183,7 +183,7 @@ namespace Engine { // ---- TAA 时域状态 ---- u64 m_FrameIndex = 0; // 抖动序列游标(Halton 2,3 ×8 采样) glm::mat4 m_LastView{1.0f}; // 上一帧视图(velocity) - glm::mat4 m_LastProjNoJitter{1.0f}; // 上一帧未抖动投影(velocity) + glm::mat4 m_LastProjNoJitter{1.0f}; // 上一帧未抖动投影(velocity 用) // 实体的上一帧世界矩阵(key = entt 实体 integral)。 // 用 sorted vector + binary_search 替代 unordered_map: // unordered_map 在每帧大量 emplace 时 rehash 抖动明显, diff --git a/Engine/include/Engine/Renderer/SceneTypes.h b/Engine/include/Engine/Renderer/SceneTypes.h index dbacc43..3564b8b 100644 --- a/Engine/include/Engine/Renderer/SceneTypes.h +++ b/Engine/include/Engine/Renderer/SceneTypes.h @@ -164,9 +164,9 @@ namespace Engine { LightData lights[kMaxLights]; // ---- TAA(velocity 在 vertex 里算)---- - glm::mat4 projNoJitter; // 未抖动投影(velocity 当前帧 clip 用) + glm::mat4 projNoJitter; // 未抖动投影(天空盒反投影/折射同位采样/静态预览) glm::mat4 prevView; // 上一帧视图(velocity) - glm::mat4 prevProj; // 上一帧未抖动投影(velocity) + glm::mat4 prevProj; // 上一帧"已抖动"投影(velocity 历史 UV 对齐) glm::vec4 cameraPos; // 相机世界位置(高光 V 向量) }; @@ -183,6 +183,13 @@ namespace Engine { struct FrameSceneData { 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}; bool DrawSkybox = true; // 本帧生效的环境贴图资产路径(来自第一个 active 的 @@ -194,6 +201,12 @@ namespace Engine { //(视图去平移只留旋转 → 反投影结果即世界方向;Skybox pass push) glm::mat4 skyInvVP{1.0f}; + // 天空盒 velocity 用:上一帧未抖动 proj × 上一帧视图(去平移)。 + // 天空是"无限远"表面,只受相机旋转影响:frag 把当前世界方向过 + // 此矩阵得上一帧屏幕位置,差即天空的运动向量(相机移动时天空/ + // 背景不再闪烁的根治点——此前天空从不写 velocity = 恒 0) + glm::mat4 skyPrevVP{1.0f}; + // 实例矩阵数组 = [主视图段 | 阴影视图段](共享同一 per-slot SSBO) std::vector instanceModels; // 上一帧世界矩阵,与 instanceModels 平行对齐(同一下标 = 同一 diff --git a/Engine/src/Renderer/Passes/ForwardPass.cpp b/Engine/src/Renderer/Passes/ForwardPass.cpp index 958b2bb..2b6fe53 100644 --- a/Engine/src/Renderer/Passes/ForwardPass.cpp +++ b/Engine/src/Renderer/Passes/ForwardPass.cpp @@ -131,12 +131,14 @@ namespace Engine::Passes .setStoreOp(vk::AttachmentStoreOp::eStore) .setClearValue(clearValue); - // velocity 附件:清 0 = 静止(背景无几何 → 历史原位采样) + // velocity 附件由 Skybox 段首写(清 0 + 天空运动向量),这里 Load + // 承接其结果,再叠加几何体运动向量。若此处 Clear 会擦掉天空 + // velocity → 相机移动时天空重投影错误 → 背景闪烁。 vk::ClearValue velocityClear; velocityClear.color = vk::ClearColorValue(std::array{0.f, 0.f, 0.f, 0.f}); colorAttachments[1].setImageView(ctx.View(m_Velocity)) .setImageLayout(vk::ImageLayout::eColorAttachmentOptimal) - .setLoadOp(vk::AttachmentLoadOp::eClear) + .setLoadOp(vk::AttachmentLoadOp::eLoad) .setStoreOp(vk::AttachmentStoreOp::eStore) .setClearValue(velocityClear); diff --git a/Engine/src/Renderer/Passes/SkyboxPass.cpp b/Engine/src/Renderer/Passes/SkyboxPass.cpp index 6d3e8b6..4e5ab4e 100644 --- a/Engine/src/Renderer/Passes/SkyboxPass.cpp +++ b/Engine/src/Renderer/Passes/SkyboxPass.cpp @@ -18,11 +18,20 @@ 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, 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_HDRColor(hdrColor), m_Env(env) + m_HDRColor(hdrColor), m_Velocity(velocity), m_Env(env) { // Set0 = {samplerCube}(fragment 采样) std::vector bindings = { @@ -31,22 +40,22 @@ namespace Engine::Passes }; 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( std::vector{m_SetLayout}, - sizeof(glm::mat4)); + sizeof(SkyboxPush)); const std::string shaderDir = "shaders/env/"; auto vert = shaders.CreateFromFile( shaderDir + "skybox.vert.spv", vk::ShaderStageFlagBits::eVertex, std::vector{m_SetLayout}, - sizeof(glm::mat4)); + sizeof(SkyboxPush)); auto frag = shaders.CreateFromFile( shaderDir + "skybox.frag.spv", vk::ShaderStageFlagBits::eFragment, std::vector{m_SetLayout}, - sizeof(glm::mat4)); + sizeof(SkyboxPush)); m_Vert = std::move(vert); m_Frag = std::move(frag); @@ -108,10 +117,26 @@ namespace Engine::Passes .setStoreOp(vk::AttachmentStoreOp::eStore) .setClearValue(clear); + // velocity 附件:本 pass 是 velocity 的 first writer。清 0 后, + // 画天空时写天空运动向量;无天空/不画时保持 0(背景静止)。 + // Forward 段改为 Load 承接本 pass 的结果 + vk::ClearValue velocityClear; + velocityClear.color = vk::ClearColorValue( + std::array{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 colorArray = { + color, velocity}; + vk::RenderingInfo renderingInfo; renderingInfo.setRenderArea({{0, 0}, ctx.extent}) .setLayerCount(1) - .setColorAttachments(color); // 无深度附件 + .setColorAttachments(colorArray); // 无深度附件 cmd.beginRendering(renderingInfo); @@ -128,11 +153,12 @@ namespace Engine::Passes cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, m_Layout, 0, m_Sets[ctx.frame.frameSlot], {}); - cmd.pushConstants( + const SkyboxPush push{m_Frame.skyInvVP, m_Frame.skyPrevVP}; + cmd.pushConstants( m_Layout, vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment, - 0, m_Frame.skyInvVP); + 0, push); // ---- 动态状态 ---- cmd.setRasterizerDiscardEnable(VK_FALSE); @@ -180,16 +206,22 @@ namespace Engine::Passes vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA; - cmd.setColorBlendEnableEXT(0, blendEnable); - cmd.setColorBlendEquationEXT(0, blendEquation); - cmd.setColorWriteMaskEXT(0, colorWriteMask); + cmd.setColorBlendEnableEXT(0, + std::array{VK_FALSE, VK_FALSE}); + const std::array blendEquations = { + blendEquation, blendEquation}; + cmd.setColorBlendEquationEXT(0, blendEquations); + const std::array writeMasks = { + colorWriteMask, colorWriteMask}; + cmd.setColorWriteMaskEXT(0, writeMasks); cmd.draw(3, 1, 0, 0); } cmd.endRendering(); }) - .Write(m_HDRColor, RG::ResourceUse::ColorAttachment); + .Write(m_HDRColor, RG::ResourceUse::ColorAttachment) + .Write(m_Velocity, RG::ResourceUse::ColorAttachment); } } // namespace Engine::Passes diff --git a/Engine/src/Renderer/Passes/TAAPass.cpp b/Engine/src/Renderer/Passes/TAAPass.cpp index aa0e9f3..cc38a0c 100644 --- a/Engine/src/Renderer/Passes/TAAPass.cpp +++ b/Engine/src/Renderer/Passes/TAAPass.cpp @@ -9,9 +9,10 @@ namespace Engine::Passes { TAAPass::TAAPass(FullscreenGpu& gpu, FrameSceneData& frame, f32 historyAlpha, 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_HDR(hdrColor), m_Velocity(velocity), m_TAAColor(taaColor) + m_HDR(hdrColor), m_Velocity(velocity), m_Depth(depth), + m_TAAColor(taaColor) { m_Sets.resize(kMaxFramesInFlight); for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) { @@ -26,14 +27,25 @@ namespace Engine::Passes // TAA 关闭:alpha=0 → mix(cur, hist, 0) 直通当前帧 //(抖动已由 SceneRenderer 同步停掉,画面静止无锯齿晃动) 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_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, vk::ShaderStageFlagBits::eFragment) .Read(m_Velocity, RG::ResourceUse::ShaderRead, vk::ShaderStageFlagBits::eFragment) + .Read(m_Depth, RG::ResourceUse::ShaderRead, + vk::ShaderStageFlagBits::eFragment) + // 上一帧深度(同 m_Depth 的上一槽位镜像):去遮挡检测比较 + // 当前像素深度与重投影点上的历史深度,判断历史是否属于同一表面 + .ReadHistory(m_Depth, RG::ResourceUse::ShaderRead, + vk::ShaderStageFlagBits::eFragment) // 自引用历史:TAAColor 的上一帧槽位就是累积历史 .ReadHistory(m_TAAColor, RG::ResourceUse::ShaderRead, vk::ShaderStageFlagBits::eFragment) @@ -45,6 +57,11 @@ namespace Engine::Passes 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); + // 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 = 历史(上一帧槽位镜像) m_Gpu.UpdateSetInput(m_Sets[slot], 2, rg, m_TAAColor, rg.HistorySlot(slot)); diff --git a/Engine/src/Renderer/Renderer.cpp b/Engine/src/Renderer/Renderer.cpp index 3b5e80f..39a29a5 100644 --- a/Engine/src/Renderer/Renderer.cpp +++ b/Engine/src/Renderer/Renderer.cpp @@ -500,7 +500,7 @@ namespace Engine // Skybox = HDRColor 的 first writer(清屏 + 画背景),先于 Forward 注册 m_Passes.push_back(CreateScope( *m_Device, *m_ShaderManager, *m_DescriptorManager, frame, - m_HDRColor, m_Environment.get())); + m_HDRColor, m_Velocity, m_Environment.get())); m_Passes.push_back(CreateScope( *m_SceneGpu, frame, m_HDRColor, m_Velocity, m_Depth, m_SceneColor, m_ShadowMaps)); @@ -539,7 +539,7 @@ namespace Engine *m_ShaderManager, *m_SceneGpu, frame, m_SelectionMask)); m_Passes.push_back(CreateScope( *m_FullscreenGpu, frame, /*historyAlpha*/ 0.9f, m_HDRColor, - m_Velocity, m_TAAColor)); + m_Velocity, m_Depth, m_TAAColor)); m_Passes.push_back(CreateScope( *m_FullscreenGpu, /*gain*/ 8.0f, m_HDRColor, m_TemporalDiff)); m_Passes.push_back(CreateScope( diff --git a/Engine/src/Renderer/SceneRenderer.cpp b/Engine/src/Renderer/SceneRenderer.cpp index 7673382..903b749 100644 --- a/Engine/src/Renderer/SceneRenderer.cpp +++ b/Engine/src/Renderer/SceneRenderer.cpp @@ -466,7 +466,7 @@ namespace Engine // ---- 0. TAA 时域状态先行(velocity = 上一帧姿态与相机)---- UniformBufferObject& ubo = m_Frame.ubo; ubo.prevView = m_LastView; - ubo.prevProj = m_LastProjNoJitter; + ubo.prevProj = m_LastProjNoJitter; // 上一帧未抖动投影(velocity 用) // ---- 1. 相机:渲染源按形态分派(UE 语义)。编辑器形态 // Scene view 恒用编辑器相机(视口 UI 状态,空场景可用); @@ -506,6 +506,13 @@ namespace Engine // 编辑器形态 = 编辑器相机,游戏形态 = primary 相机实体 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) ubo.projNoJitter = ubo.proj; @@ -581,6 +588,12 @@ namespace Engine m_Frame.skyInvVP = 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] 必是方向光)---- // entt Each 遍历序与实体创建序不保证一致——直接按迭代序填充会让 // lights[0] 落到点光上(params.x=1)→ shader 阴影分支整个跳过。 diff --git a/shaders/env/skybox.frag b/shaders/env/skybox.frag index 53691ad..f7886f2 100644 --- a/shaders/env/skybox.frag +++ b/shaders/env/skybox.frag @@ -6,16 +6,27 @@ layout(location = 0) in vec2 fragUV; layout(location = 0) out vec4 outColor; +layout(location = 1) out vec2 outVelocity; // TAA velocity 附件 layout(set = 0, binding = 0) uniform samplerCube envMap; // push 与 skybox.vert 一致声明(VUID-08878) layout(push_constant) uniform PushConstants { - mat4 invVP; + mat4 invVP; // 当前帧反投影(方向重建) + mat4 prevVP; // 上一帧 proj×view(去平移):天空 velocity } pc; void main() { vec4 target = pc.invVP * vec4(fragUV * 2.0 - 1.0, 1.0, 1.0); vec3 dir = normalize(target.xyz / target.w); 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; } diff --git a/shaders/env/skybox.vert b/shaders/env/skybox.vert index d8a2ff5..c7aaf37 100644 --- a/shaders/env/skybox.vert +++ b/shaders/env/skybox.vert @@ -8,7 +8,8 @@ layout(location = 0) out vec2 fragUV; // push 与 skybox.frag 一致声明(VUID-08878:shader object 接口一致) layout(push_constant) uniform PushConstants { - mat4 invVP; + mat4 invVP; // 当前帧反投影(方向重建) + mat4 prevVP; // 上一帧 proj×view(去平移):天空 velocity } pc; void main() { diff --git a/shaders/pbr/simple_shader.vert b/shaders/pbr/simple_shader.vert index f2d1482..e2bcbd9 100644 --- a/shaders/pbr/simple_shader.vert +++ b/shaders/pbr/simple_shader.vert @@ -75,7 +75,10 @@ void main() { gl_Position = ubo.proj * ubo.view * worldPos; // ---- TAA velocity:当前帧与上一帧的 clip 空间位置差(UV 空间)---- - // 抖动只进光栅化 gl_Position,不进 velocity(否则抖动本身被当成运动) + // 抖动不进 velocity:velocity 描述表面点的真实屏幕位移,必须用未抖动投影。 + // 静态内容 → velocity=0 → 历史精确采回同一纹素,只做逐帧抖动的超采样平均 + //(清清楚楚、无重采样模糊)。若把抖动计入 velocity,静止画面也会得到非零 + // 速度 → 每帧亚纹素重采样历史 → 持续发糊(本次"画面很糊"的根因)。 // v1:蒙皮形变不进 velocity(prev 用同一蒙皮姿态),边缘 ghosting 接受 vec4 currClip = ubo.projNoJitter * ubo.view * worldPos; vec4 prevWorldPos = prevObjects.prevModels[gl_InstanceIndex] * vec4(skinnedPos, 1.0); diff --git a/shaders/post/taa.frag b/shaders/post/taa.frag index 1492bd4..94c6320 100644 --- a/shaders/post/taa.frag +++ b/shaders/post/taa.frag @@ -1,26 +1,32 @@ #version 450 // TAA resolve:时域抗锯齿。 -// 1. velocity 反推历史像素位置,取历史累积色 -// 2. 方差裁剪(YCoCg):历史色钳向邻域均值 ± γσ(∩ AABB 极值盒)—— -// 比 RGB min/max 盒稳得多:AABB 盒子被逐帧抖动的高亮像素拽来拽去, -// 收敛历史持续被重钳 → 周期性残差经 bloom 阈值放大 = 静态模型泛光闪烁 -// 3. mix(cur, hist, alpha) 时域累积 +// 1. 深度感知 velocity dilation:轮廓处取最近表面的速度,反推历史像素位置 +// 2. 去遮挡检测(disocclusion):重投影点上的"上一帧深度"若与当前像素 +// 深度显著不同 → 该像素上一帧属于别的表面(刚露出/刚被遮挡), +// 历史颜色不属于当前表面 → 拒绝历史(alpha→0),用当前帧。 +// 这是"移动时一闪一闪"的结构性根因:只查 histUV 是否出屏、不查历史 +// 是否同一表面时,新暴露区域会持续采到前/背景串味的历史色 +// 3. 方差裁剪(YCoCg):历史色钳向邻域均值 ± γσ(∩ AABB 极值盒) +// 4. 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; // 上一帧累积结果 +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; // 上一帧累积结果 +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 { - float exposure; // 与其它全屏 pass 布局一致(本 pass 不用) - float alpha; // 历史权重(0.9 = 90% 历史) - float param1; - float param2; + float ortho; // 1 = 正交投影(深度线性化走线性分支) + float alpha; // 历史权重(0.9 = 90% 历史) + float zNear; // 相机近裁剪面 + float zFar; // 相机远裁剪面 } pc; // YCoCg 感知空间:亮度/色度分离,方差裁剪不在 HDR RGB 直接做 @@ -36,20 +42,52 @@ vec3 YCoCgToRgb(vec3 c) { 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() { 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; + float dCenter = texture(depthImage, fragUV).r; // 中心像素当前深度 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; // 历史采样:越出画面(相机快速平移)直接用当前帧,不累积 if (all(greaterThanEqual(histUV, vec2(0.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; // 邻域统计(3×3,YCoCg):均值 m1、均方 m2、极值盒 [mn, mx] - vec2 ts = 1.0 / vec2(textureSize(curImage, 0)); vec3 m1 = vec3(0.0); vec3 m2 = vec3(0.0); vec3 mn = vec3(1e9); @@ -75,7 +113,7 @@ void main() { vec3 hi = min(m1 + kGamma * sigma, mx); 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);