新增程序化立方体与可碎墙验收:Model::CreateCube 五层链路 + WallFracture 碎片刚体群(命中碎裂/径向飞散/寿命清理)

This commit is contained in:
2026-10-08 22:28:49 +08:00
parent ed7cb417bd
commit d2701a3d9b
9 changed files with 415 additions and 0 deletions
@@ -213,6 +213,9 @@ namespace Engine
// 半透明平面(alpha 0-255,透明绘制队列):同参数缓存去重
ModelRef CreateTransparentPlane(f32 halfSize,
u8 r, u8 g, u8 b, u8 a);
// 程序化立方体(halfSize 为半边长):同参数缓存去重。
// 尺寸变化走实体 Transform 缩放,勿按尺寸炸缓存
ModelRef CreateCube(f32 halfSize, u8 r, u8 g, u8 b);
// 环境资产(HDR → IBL 预积分四件套)。路径缓存去重,
// 重复 Load 同一路径零成本(动态切换场景直接反复调用)
@@ -42,6 +42,9 @@ namespace Engine::Assets {
virtual Scope<Model> CreateTransparentPlane(f32 halfSize,
u8 r, u8 g, u8 b, u8 a) = 0;
// 程序化纯色立方体(halfSize 为半边长;占位几何/碎片模拟)
virtual Scope<Model> CreateCube(f32 halfSize, u8 r, u8 g, u8 b) = 0;
// 环境 HDR → IBL 预积分四件套(compute 生成,毫秒级;失败返回 nullptr)
virtual Scope<Cubemap> LoadEnvironment(const std::string& path) = 0;
+16
View File
@@ -158,6 +158,15 @@ namespace Engine
vk::DescriptorSetLayout textureLayout,
f32 halfSize, u8 r, u8 g, u8 b, u8 a);
// 程序化立方体(6 面 24 顶点纯色贴图,halfSize = 半边长):
// 通用占位几何(碎片模拟的墙/块等)。尺寸变化走实体 Transform
// 缩放——同 halfSize 同色共享一份缓存模型
struct CubeTag {};
static Scope<Model> CreateCube(Device& device, MemoryAllocator& allocator,
DescriptorManager& descriptorManager,
vk::DescriptorSetLayout textureLayout,
f32 halfSize, u8 r, u8 g, u8 b);
~Model();
Model(const Model&) = delete;
@@ -209,6 +218,13 @@ namespace Engine
ProceduralTag, f32 halfSize, const std::array<u8, 4>& rgba,
bool transparent);
// 程序化立方体骨架:只建默认纹理 + 纯色 albedo 覆盖,mesh 由
// 立方体路径填(6 面,顶点法线朝外)
Model(Device& device, MemoryAllocator& allocator,
DescriptorManager& descriptorManager,
vk::DescriptorSetLayout textureLayout,
CubeTag, f32 halfSize, const std::array<u8, 4>& rgba);
// 空模型骨架(FromParsed 用):只建默认纹理,mesh 由调用方填
Model(Device& device, MemoryAllocator& allocator,
DescriptorManager& descriptorManager,
@@ -37,6 +37,7 @@ namespace Engine {
Scope<Model> CreatePlane(f32 halfSize, u8 r, u8 g, u8 b) override;
Scope<Model> CreateTransparentPlane(f32 halfSize,
u8 r, u8 g, u8 b, u8 a) override;
Scope<Model> CreateCube(f32 halfSize, u8 r, u8 g, u8 b) override;
Scope<Cubemap> LoadEnvironment(const std::string& path) override;
Scope<Cubemap> LoadEnvironment(const std::string& path,
DecodedEquirect&& decoded) override;
+10
View File
@@ -202,6 +202,16 @@ namespace Engine::Assets {
});
}
ModelRef AssetManager::CreateCube(f32 halfSize, u8 r, u8 g, u8 b)
{
// 程序化资产复用同一缓存:键 = 伪路径 + 参数(与 plane 同约定)
std::string key = "procedural://cube/" + std::to_string(halfSize) +
"/" + std::to_string(r) + "," + std::to_string(g) +
"," + std::to_string(b);
return m_Models.Load(key,
[&](const std::string&) { return m_Uploader.CreateCube(halfSize, r, g, b); });
}
EnvRef AssetManager::LoadEnvironment(const std::string& path)
{
// GPU 生成(compute 预积分)经接口发生在渲染层实现里
+83
View File
@@ -161,6 +161,89 @@ Scope<Model> Model::CreateTransparentPlane(
true));
}
Scope<Model> Model::CreateCube(Device& device, MemoryAllocator& allocator,
DescriptorManager& descriptorManager,
vk::DescriptorSetLayout textureLayout,
f32 halfSize, u8 r, u8 g, u8 b)
{
return Scope<Model>(new Model(device, allocator, descriptorManager,
textureLayout, CubeTag{},
halfSize, std::array<u8, 4>{r, g, b, 255}));
}
// 程序化立方体:6 面 24 顶点(面法线朝外,CCW 从外看),36 索引。
// 结构镜像平面 ctor:默认纹理 → 纯色 albedo 覆盖 → 单 mesh
Model::Model(Device& device, MemoryAllocator& allocator,
DescriptorManager& descriptorManager,
vk::DescriptorSetLayout textureLayout,
CubeTag, f32 halfSize, const std::array<u8, 4>& rgba)
: m_Device(device)
, m_Allocator(allocator)
, m_DescriptorManager(descriptorManager)
, m_TextureLayout(textureLayout)
{
// 纯色贴图直供 albedo 槽;生命周期挂进纹理缓存(键含 rgba 值,
// 同色共享、异色互不冲突)——与平面 ctor 同一套
InitDefaultTextures();
Scope<Texture> tex = Texture::CreateSolidColor(device, allocator,
rgba[0], rgba[1], rgba[2]);
Texture* albedoOverride = tex.get();
const std::string texKey = "proc-cube:" + std::to_string(rgba[0]) + ',' +
std::to_string(rgba[1]) + ',' + std::to_string(rgba[2]) + ',' +
std::to_string(rgba[3]);
m_TextureCache.emplace(texKey, std::move(tex));
const f32 h = halfSize;
// 逐面 4 顶点(底左/底右/顶右/顶左,从面外看)+ 单位 UV
const glm::vec3 P[24] = {
// +Z(前)
{-h,-h, h}, { h,-h, h}, { h, h, h}, {-h, h, h},
// -Z(后)
{ h,-h,-h}, {-h,-h,-h}, {-h, h,-h}, { h, h,-h},
// +Y(上)
{-h, h, h}, { h, h, h}, { h, h,-h}, {-h, h,-h},
// -Y(下)
{-h,-h,-h}, { h,-h,-h}, { h,-h, h}, {-h,-h, h},
// +X(右)
{ h,-h, h}, { h,-h,-h}, { h, h,-h}, { h, h, h},
// -X(左)
{-h,-h,-h}, {-h,-h, h}, {-h, h, h}, {-h, h,-h},
};
const glm::vec3 N[6] = {
{0, 0, 1}, {0, 0, -1}, {0, 1, 0}, {0, -1, 0}, {1, 0, 0}, {-1, 0, 0},
};
const glm::vec2 UV[4] = {{0, 0}, {1, 0}, {1, 1}, {0, 1}};
std::vector<Vertex> vertices;
vertices.reserve(24);
for (u32 f = 0; f < 6; ++f) {
for (u32 i = 0; i < 4; ++i) {
vertices.push_back({P[f * 4 + i], N[f], UV[i]});
}
}
std::vector<u32> indices;
indices.reserve(36);
for (u32 f = 0; f < 6; ++f) {
const u32 b = f * 4;
const std::vector<u32> quad = {b, b + 1, b + 2, b, b + 2, b + 3};
indices.insert(indices.end(), quad.begin(), quad.end());
}
const std::vector<std::vector<u32>> lodIndices = {indices};
std::string paths[Material::kSlotCount] = {"", "", "", ""};
std::array<Texture*, Material::kSlotCount> overrides{};
overrides[Material::Albedo] = albedoOverride;
Material* mat = GetOrCreateMaterial(paths, MaterialParams{}, false,
overrides);
m_Meshes.push_back(CreateScope<Mesh>(
m_Device, m_Allocator, vertices, lodIndices, mat));
// 内建材质快照(与 m_Meshes 下标对齐,编辑器材质槽 UI 不越界)
m_MeshMaterialInfos.push_back(
{"Cube", {}, MaterialParams{}, false});
RebuildUnionBounds();
}
ParsedModel Model::ParseFromDisk(const std::string& path)
{
ParsedModel out;
@@ -95,6 +95,12 @@ namespace Engine {
halfSize, r, g, b, a);
}
Scope<Model> VulkanAssetUploader::CreateCube(f32 halfSize, u8 r, u8 g, u8 b)
{
return Model::CreateCube(m_Device, m_Allocator, m_Descriptors,
m_TextureLayout, halfSize, r, g, b);
}
Scope<Cubemap> VulkanAssetUploader::LoadEnvironment(const std::string& path)
{
// compute 预积分(毫秒级):HDR → cube + irradiance/prefilter/LUT
@@ -185,6 +185,23 @@ namespace Engine::Serialization {
constexpr std::string_view kPlane = "procedural://plane/";
constexpr std::string_view kTPlane =
"procedural://transparent-plane/";
constexpr std::string_view kCube = "procedural://cube/";
if (path.rfind(kCube, 0) == 0) {
// "{halfSize}/{r},{g},{b}"
const std::string rest = path.substr(kCube.size());
const auto slash = rest.find('/');
if (slash == std::string::npos) return {};
int c[3];
if (!ParseCsvInts(rest.substr(slash + 1), c)) return {};
try {
return assets.CreateCube(
std::stof(rest.substr(0, slash)),
static_cast<u8>(c[0]), static_cast<u8>(c[1]),
static_cast<u8>(c[2]));
}
catch (const std::exception&) { return {}; }
}
if (path.rfind(kTPlane, 0) == 0) {
// "{halfSize}/{r},{g},{b},{a}"