diff --git a/Engine/include/Engine/ECS/Scene.h b/Engine/include/Engine/ECS/Scene.h index 301f22e..5c2c1db 100644 --- a/Engine/include/Engine/ECS/Scene.h +++ b/Engine/include/Engine/ECS/Scene.h @@ -43,7 +43,7 @@ namespace Engine::ECS { // 环境类设置已迁为环境实体组件(SkyboxComponent/SkyLightComponent, // UE Skybox/SkyLight Actor 语义);仅保留渲Target清屏色 struct Settings { - glm::vec4 ClearColor = {0.1, 0.1, 0.1, 1.0}; + glm::vec4 ClearColor = {0.0, 0.0, 0.0, 1.0}; }; Settings& GetSettings() { return m_Settings; } const Settings& GetSettings() const { return m_Settings; } // 序列化只读 diff --git a/Engine/include/Engine/Renderer/Passes/ForwardPass.h b/Engine/include/Engine/Renderer/Passes/ForwardPass.h index a5de070..d1421d8 100644 --- a/Engine/include/Engine/Renderer/Passes/ForwardPass.h +++ b/Engine/include/Engine/Renderer/Passes/ForwardPass.h @@ -37,6 +37,15 @@ namespace Engine::Passes { void RecordSharedState(const RG::RenderGraphContext& ctx, vk::CommandBuffer cmd, bool translucent) const; + // 透明队列绘制单位总数(= 各批实例数之和;排序后扁平单位序) + u32 TransparentUnitCount() const; + + // 绘制透明单位区间 [start,end)(批次内连续实例合并为一次 draw): + // 透明渲染实例 + 预乘 alpha 混合 + 只测不写深度的全部状态 + void DrawTransparentRange(const RG::RenderGraphContext& ctx, + vk::CommandBuffer cmd, + u32 start, u32 end) const; + SceneGpu& m_Gpu; FrameSceneData& m_Frame; RG::TextureHandle m_HDRColor; diff --git a/Engine/include/Engine/Renderer/Passes/HitProxyPass.h b/Engine/include/Engine/Renderer/Passes/HitProxyPass.h index 85aa9f1..e52bd3e 100644 --- a/Engine/include/Engine/Renderer/Passes/HitProxyPass.h +++ b/Engine/include/Engine/Renderer/Passes/HitProxyPass.h @@ -5,7 +5,7 @@ // "HitProxy" —— 拾取请求帧把不透明 + 透明 batch 用 id 管线画进 // R32Uint 目标(片元值 = 实体位模式)。深度自测自写 // (重清主深度):遮挡语义 = 可见表面胜出;运行在 -// Forward/ForwardTrans/PhysicsDebug 之后的 WAW 链尾, +// Forward/ForwardTransL*/PhysicsDebug 之后的 WAW 链尾, // 主链下游无人再读深度,下帧 Forward 照常重新 Clear // "HitProxyCopy" —— 请求像素 1×1 CopyImageToBuffer 进 host-visible // 暂存。TransferSrc use 让 RG 自动完成 diff --git a/Engine/include/Engine/Renderer/SceneRenderer.h b/Engine/include/Engine/Renderer/SceneRenderer.h index 33f84b6..34056c4 100644 --- a/Engine/include/Engine/Renderer/SceneRenderer.h +++ b/Engine/include/Engine/Renderer/SceneRenderer.h @@ -150,7 +150,8 @@ namespace Engine { std::vector instanceNext; // 与 instances 平行:同 batch 下一实例 // 每 batch 实例链头(与 result.batches 平行;kNoInstance = 空) std::vector batchHead; - // 每 batch 实例距离累加(透明排序用:平均距离降序 = 远→近) + // 每 batch 实例视图深度累加(透明排序用:平均深度降序 = 远→近; + // 透明队列逐实例成批故 count=1,等价逐实例深度) std::vector batchDistSum; // batch 查找:数组寻址(下标 = Mesh::GetSlot(),值 = 每 LOD 的 // batchIdx+1,0 = 未用)——逐实例 hash 在大场景是毫秒级开销 diff --git a/Engine/include/Engine/Renderer/SceneTypes.h b/Engine/include/Engine/Renderer/SceneTypes.h index 3564b8b..f516362 100644 --- a/Engine/include/Engine/Renderer/SceneTypes.h +++ b/Engine/include/Engine/Renderer/SceneTypes.h @@ -190,7 +190,7 @@ namespace Engine { f32 cameraFarZ = 10000.0f; bool cameraOrtho = false; - glm::vec4 ClearColor{0.1, 0.1, 0.1, 1.0}; + glm::vec4 ClearColor{0.0, 0.0, 0.0, 1.0}; bool DrawSkybox = true; // 本帧生效的环境贴图资产路径(来自第一个 active 的 // SkyboxComponent;空 = 无环境 → Renderer 回中性兜底 cubemap)。 diff --git a/Engine/src/Renderer/Passes/ForwardPass.cpp b/Engine/src/Renderer/Passes/ForwardPass.cpp index 2b6fe53..f16a1e6 100644 --- a/Engine/src/Renderer/Passes/ForwardPass.cpp +++ b/Engine/src/Renderer/Passes/ForwardPass.cpp @@ -5,12 +5,14 @@ // 显示变换不在本 pass——HDRColor 经 Tonemap 归一后才写 Backbuffer // (对齐 UE/Unity:SceneColor(HDR) → tonemapper → UI)。 // -// 透明管线对齐 UE/Unity 标准语义: +// 透明管线:分层屏幕空间折射(玻璃见玻璃) // - 不透明("Forward"):深度测试 + 写,无混合直写 -// - 透明("ForwardTrans"):独立渲染实例,深度测试(对不透明)+ -// 深度写入关闭,HDR/velocity/depth 全部 Load 承接不透明段结果, -// 预乘 alpha 混合(线性空间 over),远 → 近排序(SceneRenderer -// 侧按 batch 平均距离排) +// - 透明("ForwardTransL*"):按绘制单位拆成至多 kTransparentLayers +// 层,远 → 近(SceneRenderer 侧按 batch 平均距离排)。每层绘制前把 +// HDRColor 刷新进 SceneColor 副本("SceneColorCopy"/"SceneColorCopyL*"), +// 使该层玻璃折射采样包含所有更远的透明体。深度测试(对不透明)+ +// 深度写入关闭,HDR/velocity/depth 全部 Load 承接前序结果,预乘 +// alpha 混合(线性空间 over)。 // // NVIDIA shader-object 实测注意(见 .trae/documents 性能档案): // 早期实验称 depthWriteEnable=FALSE 有灾难病态(~5fps),但该实验 @@ -29,6 +31,8 @@ #include #include +#include +#include namespace Engine::Passes { @@ -52,6 +56,15 @@ namespace Engine::Passes vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA; + + // 透明折射分层上限:RenderGraph 的 pass 只注册一次、编译期定序 + //(声明与执行分离),层数必须编译期固定。透明队列按远→近拆成至多 + // kTransparentLayers 层,每层绘制前刷新一次场景色副本,使该层 + // 玻璃的折射采样包含所有更远的透明体——前层玻璃折射出后层玻璃 + //(玻璃见玻璃),而非只看到不透明段快照(UE/Unity 单快照的固有 + // 局限)。超出层数的单位并入最后一层(仍正常绘制,仅同层内不再 + // 互相折射)。 + constexpr u32 kTransparentLayers = 8; } // namespace // 两段渲染共享的命令录制:shader/描述符绑定 + 几何/光栅/视口状态。 @@ -121,7 +134,7 @@ namespace Engine::Passes vk::ClearValue clearValue; clearValue.color = vk::ClearColorValue( - std::array{0.1f, 0.1f, 0.1f, 0.0f}); + std::array{0.0f, 0.0f, 0.0f, 0.0f}); vk::RenderingAttachmentInfo colorAttachments[2]; // HDR 附件由 Skybox pass 清屏并画背景,这里 Load 在其上合成几何 @@ -197,9 +210,8 @@ namespace Engine::Passes vk::ShaderStageFlagBits::eFragment); // ---- 场景色副本:不透明段(含天空盒背景)的快照 ---- - // 透射折射的采样源。必须排在 Forward 之后(RAW 链)、 - // ForwardTrans 之前(后者对 HDRColor 的写入不能进折射样本, - // 且玻璃互相不可见——屏幕空间透射只含不透明场景,与 UE 一致)。 + // 第 0 层折射的采样源。必须排在 Forward 之后(RAW 链)、透明层 + // 之前。后续层由 "SceneColorCopyL*" 逐层刷新(含前序透明体)。 rg.AddPass("SceneColorCopy", [this](RG::RenderGraphContext& ctx) { const vk::CommandBuffer cmd = ctx.cmd; @@ -220,118 +232,173 @@ namespace Engine::Passes .Read(m_HDRColor, RG::ResourceUse::TransferSrc) .Write(m_SceneColor, RG::ResourceUse::TransferDst); - // ---- 透明段:独立渲染实例,UE/Unity 标准语义 ---- - // 深度测试对不透明深度(eLess)+ 深度写入关闭——玻璃之间 - // 互相不遮挡(谁先画都留在画面上,先后只影响 over 混合的 - // 颜色数学),交叉半透明不再整块消失。 - // 对不透明深度的 RAW 由 depth 的 Write use 链保证排在 - // 不透明段之后(WAW 边)。 - // alpha 语义:frag 输出未预乘线性色 + 覆盖率 alpha, - // 预乘在 tonemap 段(rgb * a)完成 —— hard constraint。 - rg.AddPass("ForwardTrans", [this](RG::RenderGraphContext& ctx) { - if (m_Frame.translucentView.batches.empty()) return; + // ---- 透明段:分层渲染实例(玻璃见玻璃)---- + // 深度测试对不透明深度(eLess)+ 深度写入关闭——玻璃之间互相 + // 不遮挡(先后只影响 over 混合/折射采样的颜色数学),交叉半透明 + // 不再整块消失。对不透明深度的 RAW 由 depth 的 Write use 链保证 + // 排在不透明段之后(WAW 边)。 + // alpha 语义:frag 输出未预乘线性色 + 覆盖率 alpha,预乘在 tonemap + // 段(rgb * a)完成 —— hard constraint。 + // + // 分层折射:透明队列按远→近(SceneRenderer 排序)。每个绘制单位 + // 占一层,层 L 绘制第 L 个单位;绘制前把 HDRColor 刷新进 SceneColor + // 副本,使层 L 的玻璃折射采样包含所有更远的透明体(含更远玻璃), + // 而不是只有不透明段快照。上限 kTransparentLayers(见文件头 + // 匿名 namespace 注释),超出层数的单位并入最后一层。 + for (u32 layer = 0; layer < kTransparentLayers; ++layer) { + // 层 L 的折射采样源刷新(层 0 用不透明段快照,无需再拷) + if (layer > 0) { + rg.AddPass("SceneColorCopyL" + std::to_string(layer), + [this, layer](RG::RenderGraphContext& ctx) { + // 本层无绘制单位:既无新内容可拷,也无玻璃会采样 + if (TransparentUnitCount() <= layer) return; - const vk::CommandBuffer cmd = ctx.cmd; + const vk::CommandBuffer cmd = ctx.cmd; + vk::ImageCopy region{}; + region.srcSubresource = + vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, + 0, 0, 1); + region.dstSubresource = + vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, + 0, 0, 1); + region.setExtent(vk::Extent3D(ctx.extent.width, + ctx.extent.height, 1)); + cmd.copyImage(ctx.Image(m_HDRColor), + vk::ImageLayout::eTransferSrcOptimal, + ctx.Image(m_SceneColor), + vk::ImageLayout::eTransferDstOptimal, + {region}); + }) + .Read(m_HDRColor, RG::ResourceUse::TransferSrc) + .Write(m_SceneColor, RG::ResourceUse::TransferDst); + } - vk::ClearValue clearValue; - clearValue.color = vk::ClearColorValue( - std::array{0.1f, 0.1f, 0.1f, 0.0f}); + rg.AddPass("ForwardTransL" + std::to_string(layer), + [this, layer](RG::RenderGraphContext& ctx) { + const u32 total = TransparentUnitCount(); + if (total <= layer) return; // 本层无单位 + // 末层兜底:消化剩余全部单位;其余层每层一个单位 + const u32 end = + (layer + 1 < kTransparentLayers) ? layer + 1 : total; + DrawTransparentRange(ctx, ctx.cmd, layer, end); + }) + .Write(m_HDRColor, RG::ResourceUse::ColorAttachment) + .Write(m_Velocity, RG::ResourceUse::ColorAttachment) + // depth 走写 use(WAW 边):保证透明段排在不透明段之后, + // barrier 覆盖透明段对不透明深度的测试读 + .Write(m_Depth, RG::ResourceUse::DepthAttachment) + // 场景色副本:透射折射采样源(translucent frag set2 binding 6) + .Read(m_SceneColor, RG::ResourceUse::ShaderRead, + vk::ShaderStageFlagBits::eFragment) + .Read(m_ShadowMaps[0], RG::ResourceUse::ShaderRead, + vk::ShaderStageFlagBits::eFragment) + .Read(m_ShadowMaps[1], RG::ResourceUse::ShaderRead, + vk::ShaderStageFlagBits::eFragment) + .Read(m_ShadowMaps[2], RG::ResourceUse::ShaderRead, + vk::ShaderStageFlagBits::eFragment); + } + } - vk::RenderingAttachmentInfo colorAttachments[2]; - colorAttachments[0].setImageView(ctx.View(m_HDRColor)) - .setImageLayout(vk::ImageLayout::eColorAttachmentOptimal) - .setLoadOp(vk::AttachmentLoadOp::eLoad) - .setStoreOp(vk::AttachmentStoreOp::eStore) - .setClearValue(clearValue); + u32 ForwardPass::TransparentUnitCount() const + { + u32 n = 0; + for (const auto& b : m_Frame.translucentView.batches) n += b.count; + return n; + } - // velocity:Load 不透明段写出的运动向量,玻璃只覆盖自己的像素 - 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::eLoad) - .setStoreOp(vk::AttachmentStoreOp::eStore) - .setClearValue(velocityClear); + void ForwardPass::DrawTransparentRange(const RG::RenderGraphContext& ctx, + vk::CommandBuffer cmd, + u32 start, u32 end) const + { + // HDR 颜色 + velocity 均 Load 承接(透明段叠加在前序内容上); + // 这两项的 clearValue 在 eLoad 下不生效,仅为结构完整 + vk::ClearValue clearValue; + clearValue.color = vk::ClearColorValue( + std::array{0.0f, 0.0f, 0.0f, 0.0f}); - vk::RenderingAttachmentInfo depthAttachment; - depthAttachment.setImageView(ctx.View(m_Depth)) - // 深度只读测试源:Load 承接不透明段深度, - // 透明段 depthWrite=FALSE 不产生写入, - // storeOp DontCare(下帧 entry 重新 Clear) - .setImageLayout(vk::ImageLayout::eDepthAttachmentOptimal) + vk::RenderingAttachmentInfo colorAttachments[2]; + colorAttachments[0].setImageView(ctx.View(m_HDRColor)) + .setImageLayout(vk::ImageLayout::eColorAttachmentOptimal) .setLoadOp(vk::AttachmentLoadOp::eLoad) - .setStoreOp(vk::AttachmentStoreOp::eDontCare) - .setClearValue(vk::ClearValue( - vk::ClearDepthStencilValue(1.0f, 0))); + .setStoreOp(vk::AttachmentStoreOp::eStore) + .setClearValue(clearValue); - std::array colorArray = { - colorAttachments[0], colorAttachments[1]}; + 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::eLoad) + .setStoreOp(vk::AttachmentStoreOp::eStore) + .setClearValue(velocityClear); - vk::RenderingInfo renderingInfo; - renderingInfo.setRenderArea({{0, 0}, ctx.extent}) - .setLayerCount(1) - .setColorAttachments(colorArray) - .setPDepthAttachment(&depthAttachment); + // 深度只读测试源:Load 承接不透明段深度,depthWrite=FALSE 不产生 + // 写入。storeOp 必须 eStore——透明段已拆成多层,后续层的 Load 依赖 + // 本层写回的有效深度内容(DontCare 会让内容在层间变 undefined)。 + vk::RenderingAttachmentInfo depthAttachment; + depthAttachment.setImageView(ctx.View(m_Depth)) + .setImageLayout(vk::ImageLayout::eDepthAttachmentOptimal) + .setLoadOp(vk::AttachmentLoadOp::eLoad) + .setStoreOp(vk::AttachmentStoreOp::eStore) + .setClearValue(vk::ClearValue( + vk::ClearDepthStencilValue(1.0f, 0))); - cmd.beginRendering(renderingInfo); - RecordSharedState(ctx, cmd, true); - // UE/Unity 语义:透明段只测不写深度。写深度会让"先画的玻璃" - // 剔掉后画但实际在它前面的玻璃(交叉半透明整块消失)。 - // 早期 depthWrite=FALSE 引发 NVIDIA ~5fps 病态的实验是在 - // frag 仍写 gl_FragDepth 的时期做的(禁 early-Z),归因存疑; - // gl_FragDepth 已删除,FALSE 若复现病态再另行隔离。 - cmd.setDepthWriteEnable(VK_FALSE); - // 单面玻璃:透明段剔除背面。不透明段 cullNone + depthWrite - // 无所谓(深度裁决),透明段 cullNone 会让闭合体(cube)的 - // 正反面片按 mesh 顺序乱序 over 混合 = "深度错乱";cull back - // 每像素至多一面。双面透明(树叶卡片)将来加材质开关 - cmd.setCullMode(vk::CullModeFlagBits::eBack); + std::array colorArray = { + colorAttachments[0], colorAttachments[1]}; - vk::ColorBlendEquationEXT premul; - premul.setSrcColorBlendFactor(vk::BlendFactor::eSrcAlpha) - .setDstColorBlendFactor(vk::BlendFactor::eOneMinusSrcAlpha) - .setColorBlendOp(vk::BlendOp::eAdd) - // alpha 穿透:玻璃只做场景内 rgb over(透出背景色), - // 不修改像素覆盖率——HDR 的 a 是"窗口透过率"(tonemap - // rgb *= a 预乘 + 透明窗口桌面合成),归背景专属。若让 - // 玻璃 over 混入 a(旧写法),a<1 背景上的玻璃会被拖进 - // 桌面混色链路 → 预乘拉黑 - .setSrcAlphaBlendFactor(vk::BlendFactor::eZero) - .setDstAlphaBlendFactor(vk::BlendFactor::eOne) - .setAlphaBlendOp(vk::BlendOp::eAdd); + vk::RenderingInfo renderingInfo; + renderingInfo.setRenderArea({{0, 0}, ctx.extent}) + .setLayerCount(1) + .setColorAttachments(colorArray) + .setPDepthAttachment(&depthAttachment); - const vk::ColorBlendEquationEXT direct = MakeDirectEquation(); - cmd.setColorBlendEnableEXT(0, - std::array{VK_TRUE, VK_FALSE}); - cmd.setColorBlendEquationEXT(0, - std::array{premul, direct}); - cmd.setColorWriteMaskEXT(0, - std::array{kColorWriteMask, kColorWriteMask}); + cmd.beginRendering(renderingInfo); + RecordSharedState(ctx, cmd, true); + // UE/Unity 语义:透明段只测不写深度。写深度会让"先画的玻璃" + // 剔掉后画但实际在它前面的玻璃(交叉半透明整块消失)。 + cmd.setDepthWriteEnable(VK_FALSE); + // 单面玻璃:透明段剔除背面。透明段 cullNone 会让闭合体(cube) + // 的正反面片按 mesh 顺序乱序 over 混合 = "深度错乱";cull back + // 每像素至多一面。双面透明(树叶卡片)将来加材质开关 + cmd.setCullMode(vk::CullModeFlagBits::eBack); - for (const auto& batch : m_Frame.translucentView.batches) { + vk::ColorBlendEquationEXT premul; + premul.setSrcColorBlendFactor(vk::BlendFactor::eSrcAlpha) + .setDstColorBlendFactor(vk::BlendFactor::eOneMinusSrcAlpha) + .setColorBlendOp(vk::BlendOp::eAdd) + // alpha 穿透:玻璃只做场景内 rgb over(透出背景色),不修改 + // 像素覆盖率——HDR 的 a 是"窗口透过率"(tonemap rgb *= a 预乘 + // + 透明窗口桌面合成),归背景专属 + .setSrcAlphaBlendFactor(vk::BlendFactor::eZero) + .setDstAlphaBlendFactor(vk::BlendFactor::eOne) + .setAlphaBlendOp(vk::BlendOp::eAdd); + + const vk::ColorBlendEquationEXT direct = MakeDirectEquation(); + cmd.setColorBlendEnableEXT(0, + std::array{VK_TRUE, VK_FALSE}); + cmd.setColorBlendEquationEXT(0, + std::array{premul, direct}); + cmd.setColorWriteMaskEXT(0, + std::array{kColorWriteMask, kColorWriteMask}); + + // 绘制单位区间 [start,end):批次内连续实例合并为一次 draw + u32 consumed = 0; + for (const auto& batch : m_Frame.translucentView.batches) { + const u32 bStart = consumed; + const u32 bEnd = consumed + batch.count; + const u32 s = std::max(start, bStart); + const u32 e = std::min(end, bEnd); + consumed = bEnd; + if (s < e) { cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, m_Gpu.forwardLayout, 1, batch.material->GetDescriptorSet(), {}); - batch.mesh->Draw(cmd, batch.count, batch.first, batch.lod); + batch.mesh->Draw(cmd, e - s, batch.first + (s - bStart), batch.lod); } + if (consumed >= end) break; + } - cmd.endRendering(); - }) - .Write(m_HDRColor, RG::ResourceUse::ColorAttachment) - .Write(m_Velocity, RG::ResourceUse::ColorAttachment) - // depth 走写 use(WAW 边):保证透明段排在不透明段之后, - // barrier 覆盖透明段对不透明深度的测试读(Read-Write hazard - // 由 src=Write 的内存域保证可见性) - .Write(m_Depth, RG::ResourceUse::DepthAttachment) - // 场景色副本:透射折射采样源(translucent frag set2 binding 6) - .Read(m_SceneColor, RG::ResourceUse::ShaderRead, - vk::ShaderStageFlagBits::eFragment) - .Read(m_ShadowMaps[0], RG::ResourceUse::ShaderRead, - vk::ShaderStageFlagBits::eFragment) - .Read(m_ShadowMaps[1], RG::ResourceUse::ShaderRead, - vk::ShaderStageFlagBits::eFragment) - .Read(m_ShadowMaps[2], RG::ResourceUse::ShaderRead, - vk::ShaderStageFlagBits::eFragment); + cmd.endRendering(); } void ForwardPass::OnTargetsRebuilt(RG::RenderGraph& rg) diff --git a/Engine/src/Renderer/Passes/FullscreenGpu.cpp b/Engine/src/Renderer/Passes/FullscreenGpu.cpp index d6964a0..77af3c3 100644 --- a/Engine/src/Renderer/Passes/FullscreenGpu.cpp +++ b/Engine/src/Renderer/Passes/FullscreenGpu.cpp @@ -169,7 +169,7 @@ namespace Engine::Passes cmd.setAlphaToOneEnableEXT(VK_FALSE); // ---- 颜色混合必须显式直写 ---- - // shader-object 路径混合是动态状态,会从上一个 pass(ForwardTrans + // shader-object 路径混合是动态状态,会从上一个 pass(ForwardTransL* // 的 premul 混合)残留:tonemap 输出被二次混合 → rgb = color×a² //(拖低 alpha 平方级变黑),且 alpha 被残留方程与 clear(a=1) 混成 // 恒 1 → 透明窗口永远不生效 diff --git a/Engine/src/Renderer/Passes/PhysicsDebugPass.cpp b/Engine/src/Renderer/Passes/PhysicsDebugPass.cpp index d6097f6..40bbc12 100644 --- a/Engine/src/Renderer/Passes/PhysicsDebugPass.cpp +++ b/Engine/src/Renderer/Passes/PhysicsDebugPass.cpp @@ -289,7 +289,7 @@ namespace Engine::Passes cmd.endRendering(); }) - // Write use(WAW 链):保证排在 Forward/ForwardTrans 之后、 + // Write use(WAW 链):保证排在 Forward/ForwardTransL* 之后、 // TAA(Read HDRColor)之前——调试图元进入 TAA/Bloom/Tonemap 主链路 .Write(m_HDRColor, RG::ResourceUse::ColorAttachment) .Write(m_Depth, RG::ResourceUse::DepthAttachment); diff --git a/Engine/src/Renderer/Renderer.cpp b/Engine/src/Renderer/Renderer.cpp index 39a29a5..e96d06e 100644 --- a/Engine/src/Renderer/Renderer.cpp +++ b/Engine/src/Renderer/Renderer.cpp @@ -426,9 +426,10 @@ namespace Engine "HDRColor", RG::TextureDesc{vk::Format::eR16G16B16A16Sfloat}); - // 场景色副本:不透明段快照(含天空盒背景),透射折射采样源。 - // 每帧一次 copyImage(Forward 后、ForwardTrans 前),独立于 - // HDRColor 生命周期(后者还会被透明段/TAA 继续写入) + // 场景色副本:透射折射采样源。ForwardPass 分层折射逐层刷新—— + // 不透明段快照一次(SceneColorCopy),此后每透明层绘制前各拷一次 + // (SceneColorCopyL*),使前层玻璃折射能采样到更远透明体 + //(玻璃见玻璃)。独立于 HDRColor 生命周期(后者仍被 TAA 继续写) m_SceneColor = m_RenderGraph->CreateTexture( "SceneColor", RG::TextureDesc{vk::Format::eR16G16B16A16Sfloat}); @@ -521,7 +522,7 @@ namespace Engine frame, m_HDRColor, m_Depth, *m_GizmoIconVB, *m_GizmoWireVB)); #endif // UE 式 GPU 拾取:请求帧把 id 画进 R32Uint + 拷贝请求像素进 - // host 暂存(深度 WAW 链尾,排在 Forward/ForwardTrans/PhysicsDebug + // host 暂存(深度 WAW 链尾,排在 Forward/ForwardTransL*/PhysicsDebug // 之后;App 层经 RequestPick/TakePickResult 消费) m_Passes.push_back(CreateScope( *m_Device, *m_Allocator, *m_ShaderManager, *m_DescriptorManager, diff --git a/Engine/src/Renderer/SceneRenderer.cpp b/Engine/src/Renderer/SceneRenderer.cpp index 903b749..6edbbef 100644 --- a/Engine/src/Renderer/SceneRenderer.cpp +++ b/Engine/src/Renderer/SceneRenderer.cpp @@ -810,6 +810,15 @@ namespace Engine // 阈值 10/20 倍半径,可调)。开关关闭 = 恒 LOD0 const f32 dist = glm::length(worldCenter - camTransform->GetWorldPosition()); + + // 透明排序键:视图空间深度 = -(view·p).z(沿相机前向 + // 的投影深度)。绝不能用欧氏距离——侧向偏移会被计入 + // "距离",使偏侧物体被误判为更远("正方体右上的甜甜圈 + // 在特定视角被排到后方"的根因)。投影深度只取沿视线 + // 分量,侧向偏移不干扰序 + const f32 viewDepth = + -(ubo.view * glm::vec4(worldCenter, 1.0f)).z; + u32 mainLod = 0; if (m_LodEnabled) { if (dist >= worldRadius * 20.0f) { @@ -828,44 +837,61 @@ namespace Engine instanceTruncated = true; return; } - // batch 查找:内建材质 = 数组寻址快路径 - //(Mesh::GetSlot 槽位 + LOD 槽);覆盖材质 = - // hash 寻址(键含材质变体:同 mesh 不同覆盖天然 - // 分批,同覆盖多实体仍合并——材质资产指针仅帧内 - // 有效,变体表每帧随 ViewScratch::Clear 清空) - const bool builtin = (resolved == mesh->GetMaterial()); - std::array* lodSlots = nullptr; - if (builtin) { - const u32 slot = mesh->GetSlot(); - if (slot >= s.lookup.size()) { - s.lookup.resize(slot + 1); - } - lodSlots = &s.lookup[slot]; - } - else { - const auto [vIt, inserted] = s.matVariants.emplace( - resolved, - static_cast(s.matVariants.size())); - const u64 key = - (static_cast(mesh->GetSlot()) << 32) | - static_cast(vIt->second); - lodSlots = &s.ovrLookup[key]; - } - // batchIdx 存储 +1 偏移(0 = 该槽位尚无 batch) - u32 batchIdx = (*lodSlots)[lod]; - if (batchIdx == 0) { - batchIdx = static_cast(s.result.batches.size()) + 1; - (*lodSlots)[lod] = batchIdx; - // first 是占位:真实基址在 appendView 聚合时 - // 回填(收集序下 batch 实例散布,见链表注释) + // 透明队列逐实例成批(不合并):透明段是"每层一个 + // 绘制单位"(ForwardPass kTransparentLayers 分层), + // 层序 = 混合/折射序,必须按实例深度逐实例排。若合并 + // 成多实例 batch,排序只能以批平均深度参与 → 批内 + // 实例无法各自定序,层序错。不透明/阴影视图合并 + //(批内顺序无混合语义,只为少绑状态) + const bool perInstance = (&s == &m_TranslucentScratch); + u32 bi; + if (perInstance) { + bi = static_cast(s.result.batches.size()); + // first 是占位:真实基址在 appendView 聚合时回填 s.result.batches.push_back( {mesh.get(), resolved, 0, 0, lod}); s.batchDistSum.push_back(0.0f); s.batchHead.push_back(ViewScratch::kNoInstance); } - // 实例归入自己的 batch(mesh 分组):收集序散布, - // 组内连接用链表维护(O(1) push,无 per-batch 分配) - const u32 bi = batchIdx - 1; + else { + // batch 查找:内建材质 = 数组寻址快路径 + //(Mesh::GetSlot 槽位 + LOD 槽);覆盖材质 = + // hash 寻址(键含材质变体:同 mesh 不同覆盖天然 + // 分批,同覆盖多实体仍合并——材质资产指针仅帧内 + // 有效,变体表每帧随 ViewScratch::Clear 清空) + const bool builtin = (resolved == mesh->GetMaterial()); + std::array* lodSlots = nullptr; + if (builtin) { + const u32 slot = mesh->GetSlot(); + if (slot >= s.lookup.size()) { + s.lookup.resize(slot + 1); + } + lodSlots = &s.lookup[slot]; + } + else { + const auto [vIt, inserted] = s.matVariants.emplace( + resolved, + static_cast(s.matVariants.size())); + const u64 key = + (static_cast(mesh->GetSlot()) << 32) | + static_cast(vIt->second); + lodSlots = &s.ovrLookup[key]; + } + // batchIdx 存储 +1 偏移(0 = 该槽位尚无 batch) + u32 batchIdx = (*lodSlots)[lod]; + if (batchIdx == 0) { + batchIdx = + static_cast(s.result.batches.size()) + 1; + (*lodSlots)[lod] = batchIdx; + s.result.batches.push_back( + {mesh.get(), resolved, 0, 0, lod}); + s.batchDistSum.push_back(0.0f); + s.batchHead.push_back(ViewScratch::kNoInstance); + } + bi = batchIdx - 1; + } + // 实例归入自己的 batch(收集序散布,组内连接用链表 + // 维护:O(1) push,无 per-batch 分配) s.result.batches[bi].count++; const u32 ii = static_cast(s.instances.size()); @@ -874,7 +900,7 @@ namespace Engine s.instanceNext.push_back(s.batchHead[bi]); s.batchHead[bi] = ii; // 头插:链序 = push 逆序(无妨, // 实例对齐性由 (矩阵,id) 成对保证) - s.batchDistSum[bi] += dist; // 透明排序用 + s.batchDistSum[bi] += viewDepth; // 透明排序键:视图深度 s.result.instanceCount++; ++totalInstances; }; @@ -939,9 +965,11 @@ namespace Engine // PackIndirect(m_Frame.shadowCascades[c], false); // } - // ---- 5. 透明批次排序:平均距离降序(远 → 近),连同实例列表同步重排。 - // 实例按链表逐 batch 搬运(收集序散布,不能按 (first,count) 切片); - // 搬完的新数组天然按 batch 连续 → 链表确定性重建(顺序遍历) + // ---- 5. 透明批次排序:视图深度降序(远 → 近),连同实例列表同步重排。 + // 透明队列逐实例成批(见 tryAppend),故 count=1、batchDistSum = 该 + // 实例的视图深度 → 批序 = 逐实例深度序。实例按链表逐 batch 搬运 + //(收集序散布,不能按 (first,count) 切片);搬完的新数组天然按 + // batch 连续 → 链表确定性重建(顺序遍历) { auto& tb = m_TranslucentScratch.result.batches; if (tb.size() > 1) { diff --git a/shaders/pbr/simple_shader_translucent.frag b/shaders/pbr/simple_shader_translucent.frag index ec638da..39dacf6 100644 --- a/shaders/pbr/simple_shader_translucent.frag +++ b/shaders/pbr/simple_shader_translucent.frag @@ -268,9 +268,10 @@ void main() { * mix(0.4, 1.0, shade0); // 阴影区环境同步衰减(简化遮蔽) // ---- 透射(KHR_materials_transmission):屏幕空间折射 ---- - // 采样源 = 不透明段快照(天空盒 + 不透明几何,Forward 后 - // ForwardTrans 前拷贝)。玻璃互相不可见(副本不含透明队列), - // 与 UE 屏幕空间透射同一限制。 + // 采样源 = 场景色副本,每层绘制前逐层刷新(ForwardPass 的分层折射: + // 透明队列远→近拆成至多 kTransparentLayers 层,层 L 绘制前把 HDRColor + // 拷进副本)。故层 L 玻璃的副本含全部更远透明体——前玻璃能折射出 + // 后玻璃(玻璃见玻璃),摆脱 UE/Unity 单快照"玻璃互相不可见"的局限。 vec3 transmitted = vec3(0.0); if (transmission > 0.0) { // 进入介质 eta = 1/ior;全内反射(TIR)退化为反射方向