透明分层屏幕空间折射 + 透明排序修正 + 默认清屏色归零

默认 ClearColor 0.1 → 0(策划阶段结束,无用)。

透明分层折射(玻璃见玻璃):透明段原来单个 ForwardTrans pass,折射
采样源 SceneColor 副本只在不透明段后快照一次(不含任何透明体)→ 前
玻璃折射不出后玻璃。改为按绘制单位远→近拆成至多 kTransparentLayers
层,每层绘制前刷新 SceneColor 副本,使层 L 玻璃采样含所有更远透明体。
RenderGraph pass 编译期定序,故层数固定;超出并入末层。深度 storeOp
改 eStore(多层需 Load 承接有效深度内容)。shader 不改。

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