feat: 透明材质 alpha blending——独立队列 + 线性 over 混合 + 距离排序

透明来源:glTF alphaMode=BLEND(解析期读取,OBJ/FBX 恒不透明——
资产格式定义写死);MASK 后续演进。Material 加透明语义(入缓存键),
程序化测试资产 CreateTransparentPlane(RGBA 纯色纹理)。

SceneRenderer:透明材质实例收集进独立 scratch(仅主视图,不投
阴影——alpha 阴影后续演进),batch 按平均距离降序排序(远→近,
batchMats 同步重排),SSBO 扁平化顺序 = 排序后顺序。

ForwardPass 两段绘制:不透明(无混合 + 深度写)→ 透明(Shader
Object 动态状态切 SRC_ALPHA/ONE_MINUS_SRC_ALPHA 线性 over + 深度
只测不写);velocity 附件透明段直写(透明表面 velocity 即自身运动)。
frag alpha = albedo 贴图 a 直通,预乘在 tonemap 段(hard constraint
不变)。已知边界:TAA alpha 时域混合(透明边缘几帧渐显);同 batch
多实例不做实例级排序。
This commit is contained in:
2026-09-25 16:57:27 +08:00
parent 8dbfb9f554
commit b25fa29fe5
16 changed files with 213 additions and 27 deletions
+59 -7
View File
@@ -15,6 +15,9 @@
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <algorithm>
#include <numeric>
#include <tracy/Tracy.hpp>
#include <chrono>
@@ -350,23 +353,34 @@ namespace Engine
lodSlots[lod] = batchIdx;
s.result.batches.push_back({mesh.get(), 0, 0, lod});
s.batchMats.emplace_back();
s.batchDistSum.push_back(0.0f);
}
// 实例归入自己的 batch(mesh 分组),保证同 batch
// 实例在扁平化后连续——firstInstance 语义的前提
const u32 bi = batchIdx - 1;
s.result.batches[bi].count++;
s.batchMats[bi].push_back({t.World(), prevWorld});
s.batchDistSum[bi] += dist; // 透明排序用
s.result.instanceCount++;
++totalInstances;
};
tryAppend(m_MainScratch, mainFrustum, mainLod);
for (u32 c = 0; c < kMaxCascades; ++c) {
if (!cascVis[c]) continue; // 粗剔除已判不可见
// 级联降级:越远的级联覆盖范围越大,用越低的 LOD
//(阴影本来就是低频信息,误差可接受);开关关闭 = 恒 LOD0
tryAppend(m_CascScratch[c], cascFrustum[c],
m_LodEnabled ? std::min(c, kMeshLodCount - 1) : 0u);
// 透明材质 → 独立队列(主视图,混合绘制 + 排序;
// 不进阴影——alpha 阴影复杂度高收益低,注释后续演进)
const bool isTransparentMesh =
mesh->GetMaterial()->IsTransparent();
ViewScratch& mainTarget = isTransparentMesh
? m_TranslucentScratch : m_MainScratch;
tryAppend(mainTarget, mainFrustum, mainLod);
if (!isTransparentMesh) {
for (u32 c = 0; c < kMaxCascades; ++c) {
if (!cascVis[c]) continue; // 粗剔除已判不可见
// 级联降级:越远的级联覆盖范围越大,用越低的 LOD
//(阴影本来就是低频信息,误差可接受);开关关闭 = 恒 LOD0
tryAppend(m_CascScratch[c], cascFrustum[c],
m_LodEnabled ? std::min(c, kMeshLodCount - 1) : 0u);
}
}
}
});
@@ -393,6 +407,44 @@ namespace Engine
appendView(m_Frame.shadowCascades[c], m_CascScratch[c]);
}
// ---- 5. 透明批次排序:平均距离降序(远 → 近),连同实例列表同步重排
{
auto& tb = m_TranslucentScratch.result.batches;
if (tb.size() > 1) {
std::vector<u32> order(tb.size());
std::iota(order.begin(), order.end(), 0);
std::sort(order.begin(), order.end(), [&](u32 a, u32 b) {
return m_TranslucentScratch.batchDistSum[a] / tb[a].count >
m_TranslucentScratch.batchDistSum[b] / tb[b].count;
});
CullResult sorted;
sorted.instanceCount = m_TranslucentScratch.result.instanceCount;
std::vector<ViewScratch::InstanceMatrices> sortedMats;
std::vector<f32> sortedDist;
for (const u32 i : order) {
sorted.batches.push_back(tb[i]);
sortedDist.push_back(m_TranslucentScratch.batchDistSum[i]);
for (auto& m : m_TranslucentScratch.batchMats[i]) {
sortedMats.push_back(std::move(m));
}
}
m_TranslucentScratch.result = std::move(sorted);
m_TranslucentScratch.batchDistSum = std::move(sortedDist);
// batchMats 按新分组的 count 顺序切分重装(保留容量)
m_TranslucentScratch.batchMats.assign(
m_TranslucentScratch.result.batches.size(), {});
u32 offset = 0;
for (size_t i = 0; i < m_TranslucentScratch.result.batches.size(); ++i) {
const u32 n = m_TranslucentScratch.result.batches[i].count;
m_TranslucentScratch.batchMats[i].assign(
std::make_move_iterator(sortedMats.begin() + offset),
std::make_move_iterator(sortedMats.begin() + offset + n));
offset += n;
}
}
}
appendView(m_Frame.translucentView, m_TranslucentScratch);
// 帧末滚动时域状态:cur → prev(每帧重建,销毁的实体自动清出)
m_PrevWorlds.swap(m_CurWorlds);
m_CurWorlds.clear();