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

默认 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,
// 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; } // 序列化只读
@@ -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;
@@ -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 自动完成
@@ -150,7 +150,8 @@ namespace Engine {
std::vector<u32> instanceNext; // 与 instances 平行:同 batch 下一实例
// 每 batch 实例链头(与 result.batches 平行;kNoInstance = 空)
std::vector<u32> batchHead;
// 每 batch 实例距离累加(透明排序用:平均距离降序 = 远→近)
// 每 batch 实例视图深度累加(透明排序用:平均深度降序 = 远→近;
// 透明队列逐实例成批故 count=1,等价逐实例深度)
std::vector<f32> batchDistSum;
// batch 查找:数组寻址(下标 = Mesh::GetSlot(),值 = 每 LOD 的
// batchIdx+1,0 = 未用)——逐实例 hash 在大场景是毫秒级开销
+1 -1
View File
@@ -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)。
+171 -104
View File
@@ -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 <array>
#include <span>
#include <algorithm>
#include <string>
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<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];
// 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<float, 4>{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<float, 4>{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<float, 4>{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<vk::RenderingAttachmentInfo, 2> colorArray = {
colorAttachments[0], colorAttachments[1]};
vk::ClearValue velocityClear;
velocityClear.color = vk::ClearColorValue(
std::array<float, 4>{0.f, 0.f, 0.f, 0.f});
colorAttachments[1].setImageView(ctx.View(m_Velocity))
.setImageLayout(vk::ImageLayout::eColorAttachmentOptimal)
.setLoadOp(vk::AttachmentLoadOp::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<vk::RenderingAttachmentInfo, 2> 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::Bool32, 2>{VK_TRUE, VK_FALSE});
cmd.setColorBlendEquationEXT(0,
std::array<vk::ColorBlendEquationEXT, 2>{premul, direct});
cmd.setColorWriteMaskEXT(0,
std::array<vk::ColorComponentFlags, 2>{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::Bool32, 2>{VK_TRUE, VK_FALSE});
cmd.setColorBlendEquationEXT(0,
std::array<vk::ColorBlendEquationEXT, 2>{premul, direct});
cmd.setColorWriteMaskEXT(0,
std::array<vk::ColorComponentFlags, 2>{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)
+1 -1
View File
@@ -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 → 透明窗口永远不生效
@@ -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);
+5 -4
View File
@@ -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<Passes::HitProxyPass>(
*m_Device, *m_Allocator, *m_ShaderManager, *m_DescriptorManager,
+65 -37
View File
@@ -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<u32, kMeshLodCount>* 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<u32>(s.matVariants.size()));
const u64 key =
(static_cast<u64>(mesh->GetSlot()) << 32) |
static_cast<u64>(vIt->second);
lodSlots = &s.ovrLookup[key];
}
// batchIdx 存储 +1 偏移(0 = 该槽位尚无 batch)
u32 batchIdx = (*lodSlots)[lod];
if (batchIdx == 0) {
batchIdx = static_cast<u32>(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<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);
}
// 实例归入自己的 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<u32, kMeshLodCount>* 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<u32>(s.matVariants.size()));
const u64 key =
(static_cast<u64>(mesh->GetSlot()) << 32) |
static_cast<u64>(vIt->second);
lodSlots = &s.ovrLookup[key];
}
// batchIdx 存储 +1 偏移(0 = 该槽位尚无 batch)
u32 batchIdx = (*lodSlots)[lod];
if (batchIdx == 0) {
batchIdx =
static_cast<u32>(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<u32>(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) {
+4 -3
View File
@@ -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)退化为反射方向