新增程序化立方体与可碎墙验收:Model::CreateCube 五层链路 + WallFracture 碎片刚体群(命中碎裂/径向飞散/寿命清理)
This commit is contained in:
@@ -0,0 +1,276 @@
|
|||||||
|
//
|
||||||
|
// WallFracture.cpp:侧线A 验收脚本——可碎墙 + 碎片刚体群 + 寿命清理。
|
||||||
|
//
|
||||||
|
// WallFracture(挂墙实体:Static Box 刚体 + MeshRenderer 立方体):
|
||||||
|
// OnCollisionEnter(Dynamic 体以足够速度命中)→ 触发一次:
|
||||||
|
// 藏墙(MeshRenderer/RigidBody.active = false,PhysicsWorld 对账
|
||||||
|
// 自然消化:不渲染 + body 销毁)→ 按网格把墙体积切成 N 个碎片
|
||||||
|
// 实体(共享一份程序化立方体模型 + Dynamic Box 刚体),初速记入
|
||||||
|
// 待办表
|
||||||
|
// OnUpdate:碎片簿记——body 建好(下一帧 Step 对账,同 Bullet
|
||||||
|
// OnCreate 施速的时序契约)即统一施加径向初速(命中点→碎片方向 +
|
||||||
|
// 向上偏置);寿命到期统一 Destroy;总量上限防泄漏
|
||||||
|
//
|
||||||
|
// 碎片不挂脚本:单一 WallFracture 集中管理全部碎片的施速/寿命/
|
||||||
|
// 清理(一帧 spawn 几十个实体,每颗一个脚本实例纯属浪费——
|
||||||
|
// Unity 也用同一个 Explosion 脚本管一片 Fragments)。
|
||||||
|
//
|
||||||
|
// 覆盖的引擎设施:
|
||||||
|
// - Spawn()/Destroy():帧内即时创建 / 帧尾延迟销毁
|
||||||
|
// - 组件运行时装配:MeshRenderer(程序化 CreateCube 资产)+
|
||||||
|
// RigidBody(下一帧对账建 body;active=false 对账销毁)
|
||||||
|
// - OnCollisionEnter:命中点/法线 + 对方实线速度查询
|
||||||
|
// - SetLinearVelocity:body 建好后的初速注入
|
||||||
|
//
|
||||||
|
// 验收步骤:编辑器摆一堵墙(MeshRenderer = procedural://cube +
|
||||||
|
// RigidBody Static Box + 挂 WallFracture,缩放即墙尺寸)→ 场景里
|
||||||
|
// 任意实体挂 TestShoot → Play → RMB 飞行瞄准墙体 → 左键射击:
|
||||||
|
// 墙消失,碎片群从命中点径向飞散、翻滚、落地堆积,若干秒后清空。
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <Engine/Scripting/Script.h>
|
||||||
|
#include <Engine/Core/Application.h>
|
||||||
|
#include <Engine/Core/Types.h>
|
||||||
|
#include <Engine/Assets/AssetManager.h> // CreateCube(门面只前向声明)
|
||||||
|
#include <Engine/Physics/PhysicsWorld.h>
|
||||||
|
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
#include <glm/gtc/quaternion.hpp>
|
||||||
|
|
||||||
|
#include <random>
|
||||||
|
|
||||||
|
using namespace Engine; // u32/f32/Entity 等基础类型 + 门面(同 TestShoot)
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// bits(PhysicsWorld 键)→ 实体(还原位模式;用前自行 valid 校验)
|
||||||
|
ECS::Entity EntityFromBits(u32 bits) {
|
||||||
|
return static_cast<ECS::Entity>(bits);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 实体句柄 → bits(EntityBits() 只覆盖自身绑定实体,碎片簿记
|
||||||
|
// 需要任意实体的同款换算)
|
||||||
|
u32 BitsOf(const ECS::EntityHandle& e) {
|
||||||
|
return static_cast<u32>(
|
||||||
|
entt::to_integral(static_cast<entt::entity>(e)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 可碎墙(挂墙实体;触发后自管一整批碎片)----
|
||||||
|
class WallFracture final : public Engine::Scripting::ScriptBehavior {
|
||||||
|
public:
|
||||||
|
const char* Name() const override { return "WallFracture"; }
|
||||||
|
|
||||||
|
void DeclareParams(Engine::Scripting::ParamBuilder& pb) override {
|
||||||
|
// 碎片网格:每轴切几刀(总碎片 = x*y*z;占位档位 = 每碎片
|
||||||
|
// 一实体,网格 6*4*1=24 起步,中等规模才考虑 instanced draw)
|
||||||
|
pb.Field("GridX", &m_GridX, 6);
|
||||||
|
pb.Field("GridY", &m_GridY, 4);
|
||||||
|
pb.Field("GridZ", &m_GridZ, 1);
|
||||||
|
pb.Field("BurstSpeed", &m_BurstSpeed, 4.5f); // 径向初速 m/s
|
||||||
|
pb.Field("UpBias", &m_UpBias, 2.0f); // 向上偏置 m/s
|
||||||
|
pb.Field("Lifetime", &m_Lifetime, 6.0f); // 碎片寿命(秒)
|
||||||
|
pb.Field("MaxFragments", &m_MaxFragments, 96); // 活碎片上限
|
||||||
|
pb.Field("MinImpactSpeed", &m_MinImpactSpeed, 2.0f); // 触发阈值
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnCollisionEnter(u32 other, const glm::vec3& point,
|
||||||
|
const glm::vec3& normal) override {
|
||||||
|
(void)normal; // 法线留给将来定向喷射
|
||||||
|
|
||||||
|
if (m_Fired) return;
|
||||||
|
|
||||||
|
// 触发条件:对方是活的 Dynamic 刚体且速度够快(过滤
|
||||||
|
// 静止贴靠/角色轻碰——子弹/抛体必中)
|
||||||
|
auto& scene = App().GetScene();
|
||||||
|
const ECS::Entity otherE = EntityFromBits(other);
|
||||||
|
if (!scene.GetRegistry().valid(otherE)) return;
|
||||||
|
const auto* otherRb =
|
||||||
|
scene.GetRegistry().try_get<Engine::ECS::RigidBodyComponent>(
|
||||||
|
otherE);
|
||||||
|
if (!otherRb ||
|
||||||
|
otherRb->type !=
|
||||||
|
Engine::ECS::RigidBodyComponent::BodyType::Dynamic) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const f32 speed = glm::length(
|
||||||
|
App().GetPhysics().GetLinearVelocity(other));
|
||||||
|
if (speed < m_MinImpactSpeed) return;
|
||||||
|
|
||||||
|
Fire(point);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnUpdate(f32 dt) override {
|
||||||
|
ApplyPendingVelocities();
|
||||||
|
AgeAndCleanup(dt);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
// ---- 触发碎裂:藏墙 + 切网格 + spawn 碎片群 ----
|
||||||
|
void Fire(const glm::vec3& hitPoint) {
|
||||||
|
m_Fired = true;
|
||||||
|
|
||||||
|
// 藏墙:渲染/拾取跳过 + body 对账销毁(组件级 active,
|
||||||
|
// 与 Unity Renderer.enabled + Rigidbody 摘除同语义)
|
||||||
|
if (auto* mr = TryGetComponent<
|
||||||
|
Engine::ECS::MeshRendererComponent>()) {
|
||||||
|
mr->active = false;
|
||||||
|
}
|
||||||
|
if (auto* rb = TryGetComponent<
|
||||||
|
Engine::ECS::RigidBodyComponent>()) {
|
||||||
|
rb->active = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 墙几何:立方体模型半边长 0.5(单位体),墙尺寸 =
|
||||||
|
// Transform 世界缩放;碎片尺寸 = 墙尺寸 / 网格
|
||||||
|
const auto& wall = Transform();
|
||||||
|
const glm::vec3 wallPos = wall.GetWorldPosition();
|
||||||
|
const glm::quat wallRot = wall.GetWorldRotationQuat();
|
||||||
|
const glm::vec3 wallSize = wall.GetWorldScale();
|
||||||
|
const glm::ivec3 grid{m_GridX, m_GridY, m_GridZ};
|
||||||
|
const glm::vec3 fragSize = wallSize /
|
||||||
|
glm::vec3{std::max(1, m_GridX), std::max(1, m_GridY),
|
||||||
|
std::max(1, m_GridZ)};
|
||||||
|
|
||||||
|
// 共享一份程序化立方体(缓存去重:同参数全场景一把 mesh)
|
||||||
|
const Engine::ModelRef brick =
|
||||||
|
App().GetAssets().CreateCube(0.5f, 140, 92, 72);
|
||||||
|
|
||||||
|
for (int i = 0; i < grid.x; ++i) {
|
||||||
|
for (int j = 0; j < grid.y; ++j) {
|
||||||
|
for (int k = 0; k < grid.z; ++k) {
|
||||||
|
// 单元中心(墙局部)→ 世界(墙旋转/平移搬运;
|
||||||
|
// 加单元内抖动避免完美网格的假感)
|
||||||
|
const glm::vec3 cell{
|
||||||
|
(i + 0.5f) * fragSize.x - wallSize.x * 0.5f,
|
||||||
|
(j + 0.5f) * fragSize.y - wallSize.y * 0.5f,
|
||||||
|
(k + 0.5f) * fragSize.z - wallSize.z * 0.5f};
|
||||||
|
const glm::vec3 jitter{
|
||||||
|
Rand(-0.2f, 0.2f) * fragSize.x,
|
||||||
|
Rand(-0.2f, 0.2f) * fragSize.y,
|
||||||
|
Rand(-0.2f, 0.2f) * fragSize.z};
|
||||||
|
const glm::vec3 pos =
|
||||||
|
wallPos + wallRot * (cell + jitter);
|
||||||
|
|
||||||
|
auto frag = Spawn("fragment");
|
||||||
|
auto& tr = frag.GetComponent<
|
||||||
|
Engine::ECS::TransformComponent>();
|
||||||
|
tr.SetPosition(pos);
|
||||||
|
// 随机小倾角:落地堆积不齐整,视觉更像碎块
|
||||||
|
tr.SetRotationQuat(glm::quat(glm::vec3{
|
||||||
|
glm::radians(Rand(-30.0f, 30.0f)),
|
||||||
|
glm::radians(Rand(-180.0f, 180.0f)),
|
||||||
|
glm::radians(Rand(-30.0f, 30.0f))}));
|
||||||
|
tr.SetScale(fragSize);
|
||||||
|
|
||||||
|
auto& fmr =
|
||||||
|
frag.AddComponent<
|
||||||
|
Engine::ECS::MeshRendererComponent>();
|
||||||
|
fmr.model = brick;
|
||||||
|
|
||||||
|
// Dynamic Box:boxHalfExtents 默认 0.5(单位体)
|
||||||
|
// × Transform 缩放 = 恰好碎片尺寸,无需改字段
|
||||||
|
auto& frb =
|
||||||
|
frag.AddComponent<
|
||||||
|
Engine::ECS::RigidBodyComponent>();
|
||||||
|
frb.type =
|
||||||
|
Engine::ECS::RigidBodyComponent::BodyType::Dynamic;
|
||||||
|
frb.collider =
|
||||||
|
Engine::ECS::RigidBodyComponent::ColliderType::Box;
|
||||||
|
frb.mass = 0.4f;
|
||||||
|
frb.friction = 0.7f;
|
||||||
|
frb.restitution = 0.05f;
|
||||||
|
|
||||||
|
// 初速待办:body 下一帧建好,OnUpdate 统一施加
|
||||||
|
//(同 Bullet OnCreate 施速的时序契约)
|
||||||
|
glm::vec3 dir = pos - hitPoint;
|
||||||
|
const f32 len = glm::length(dir);
|
||||||
|
if (len < 1e-3f) {
|
||||||
|
dir = glm::vec3{Rand(-1.0f, 1.0f), 0.6f,
|
||||||
|
Rand(-1.0f, 1.0f)};
|
||||||
|
} else {
|
||||||
|
dir /= len;
|
||||||
|
}
|
||||||
|
const glm::vec3 vel =
|
||||||
|
dir * m_BurstSpeed * Rand(0.6f, 1.3f) +
|
||||||
|
glm::vec3{0.0f, m_UpBias * Rand(0.5f, 1.0f),
|
||||||
|
0.0f};
|
||||||
|
|
||||||
|
// 总量上限:超限立即回收最老碎片(防泄漏的
|
||||||
|
// 兜底闸;寿命清理才是常态回收)
|
||||||
|
const u32 bits = BitsOf(frag);
|
||||||
|
if (m_Fragments.size() >=
|
||||||
|
static_cast<size_t>(m_MaxFragments)) {
|
||||||
|
Destroy(EntityFromBits(m_Fragments.front().bits));
|
||||||
|
m_Fragments.erase(m_Fragments.begin());
|
||||||
|
}
|
||||||
|
m_Fragments.push_back({bits, vel, 0.0f});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 施速待办:body 建好(bodyValid)即注入,勿重复 ----
|
||||||
|
void ApplyPendingVelocities() {
|
||||||
|
auto& registry = App().GetScene().GetRegistry();
|
||||||
|
for (auto it = m_Fragments.begin();
|
||||||
|
it != m_Fragments.end();) {
|
||||||
|
if (it->applied) { ++it; continue; }
|
||||||
|
const ECS::Entity e = EntityFromBits(it->bits);
|
||||||
|
if (!registry.valid(e)) { // 实体没了(外部销毁)
|
||||||
|
it = m_Fragments.erase(it);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const auto* rb = registry.try_get<
|
||||||
|
Engine::ECS::RigidBodyComponent>(e);
|
||||||
|
if (rb && rb->bodyValid) {
|
||||||
|
App().GetPhysics().SetLinearVelocity(it->bits,
|
||||||
|
it->velocity);
|
||||||
|
it->applied = true;
|
||||||
|
}
|
||||||
|
++it; // body 未建:留待下一帧(对账晚一帧必到)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 寿命清理:到期 Destroy(延迟队列,遍历安全)----
|
||||||
|
void AgeAndCleanup(f32 dt) {
|
||||||
|
auto& registry = App().GetScene().GetRegistry();
|
||||||
|
for (auto it = m_Fragments.begin();
|
||||||
|
it != m_Fragments.end();) {
|
||||||
|
it->age += dt;
|
||||||
|
if (it->age < m_Lifetime) { ++it; continue; }
|
||||||
|
const ECS::Entity e = EntityFromBits(it->bits);
|
||||||
|
if (registry.valid(e)) {
|
||||||
|
Destroy(e);
|
||||||
|
}
|
||||||
|
it = m_Fragments.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static f32 Rand(f32 lo, f32 hi) {
|
||||||
|
thread_local std::mt19937 rng{std::random_device{}()};
|
||||||
|
return std::uniform_real_distribution<f32>(lo, hi)(rng);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Fragment {
|
||||||
|
u32 bits = 0; // PhysicsWorld 键(实体位模式)
|
||||||
|
glm::vec3 velocity{0.0f};// 待施初速(body 建好后注入)
|
||||||
|
f32 age = 0.0f; // 存活秒数(寿命清理用)
|
||||||
|
bool applied = false; // 初速已注入
|
||||||
|
};
|
||||||
|
|
||||||
|
int m_GridX = 6;
|
||||||
|
int m_GridY = 4;
|
||||||
|
int m_GridZ = 1;
|
||||||
|
f32 m_BurstSpeed = 4.5f;
|
||||||
|
f32 m_UpBias = 2.0f;
|
||||||
|
f32 m_Lifetime = 6.0f;
|
||||||
|
int m_MaxFragments = 96;
|
||||||
|
f32 m_MinImpactSpeed = 2.0f;
|
||||||
|
|
||||||
|
bool m_Fired = false; // 一次性触发保护
|
||||||
|
std::vector<Fragment> m_Fragments; // 活碎片簿记
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
ENGINE_REGISTER_SCRIPT(WallFracture)
|
||||||
@@ -213,6 +213,9 @@ namespace Engine
|
|||||||
// 半透明平面(alpha 0-255,透明绘制队列):同参数缓存去重
|
// 半透明平面(alpha 0-255,透明绘制队列):同参数缓存去重
|
||||||
ModelRef CreateTransparentPlane(f32 halfSize,
|
ModelRef CreateTransparentPlane(f32 halfSize,
|
||||||
u8 r, u8 g, u8 b, u8 a);
|
u8 r, u8 g, u8 b, u8 a);
|
||||||
|
// 程序化立方体(halfSize 为半边长):同参数缓存去重。
|
||||||
|
// 尺寸变化走实体 Transform 缩放,勿按尺寸炸缓存
|
||||||
|
ModelRef CreateCube(f32 halfSize, u8 r, u8 g, u8 b);
|
||||||
|
|
||||||
// 环境资产(HDR → IBL 预积分四件套)。路径缓存去重,
|
// 环境资产(HDR → IBL 预积分四件套)。路径缓存去重,
|
||||||
// 重复 Load 同一路径零成本(动态切换场景直接反复调用)
|
// 重复 Load 同一路径零成本(动态切换场景直接反复调用)
|
||||||
|
|||||||
@@ -42,6 +42,9 @@ namespace Engine::Assets {
|
|||||||
virtual Scope<Model> CreateTransparentPlane(f32 halfSize,
|
virtual Scope<Model> CreateTransparentPlane(f32 halfSize,
|
||||||
u8 r, u8 g, u8 b, u8 a) = 0;
|
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)
|
// 环境 HDR → IBL 预积分四件套(compute 生成,毫秒级;失败返回 nullptr)
|
||||||
virtual Scope<Cubemap> LoadEnvironment(const std::string& path) = 0;
|
virtual Scope<Cubemap> LoadEnvironment(const std::string& path) = 0;
|
||||||
|
|
||||||
|
|||||||
@@ -158,6 +158,15 @@ namespace Engine
|
|||||||
vk::DescriptorSetLayout textureLayout,
|
vk::DescriptorSetLayout textureLayout,
|
||||||
f32 halfSize, u8 r, u8 g, u8 b, u8 a);
|
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();
|
||||||
|
|
||||||
Model(const Model&) = delete;
|
Model(const Model&) = delete;
|
||||||
@@ -209,6 +218,13 @@ namespace Engine
|
|||||||
ProceduralTag, f32 halfSize, const std::array<u8, 4>& rgba,
|
ProceduralTag, f32 halfSize, const std::array<u8, 4>& rgba,
|
||||||
bool transparent);
|
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 由调用方填
|
// 空模型骨架(FromParsed 用):只建默认纹理,mesh 由调用方填
|
||||||
Model(Device& device, MemoryAllocator& allocator,
|
Model(Device& device, MemoryAllocator& allocator,
|
||||||
DescriptorManager& descriptorManager,
|
DescriptorManager& descriptorManager,
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ namespace Engine {
|
|||||||
Scope<Model> CreatePlane(f32 halfSize, u8 r, u8 g, u8 b) override;
|
Scope<Model> CreatePlane(f32 halfSize, u8 r, u8 g, u8 b) override;
|
||||||
Scope<Model> CreateTransparentPlane(f32 halfSize,
|
Scope<Model> CreateTransparentPlane(f32 halfSize,
|
||||||
u8 r, u8 g, u8 b, u8 a) override;
|
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) override;
|
||||||
Scope<Cubemap> LoadEnvironment(const std::string& path,
|
Scope<Cubemap> LoadEnvironment(const std::string& path,
|
||||||
DecodedEquirect&& decoded) override;
|
DecodedEquirect&& decoded) override;
|
||||||
|
|||||||
@@ -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)
|
EnvRef AssetManager::LoadEnvironment(const std::string& path)
|
||||||
{
|
{
|
||||||
// GPU 生成(compute 预积分)经接口发生在渲染层实现里
|
// GPU 生成(compute 预积分)经接口发生在渲染层实现里
|
||||||
|
|||||||
@@ -161,6 +161,89 @@ Scope<Model> Model::CreateTransparentPlane(
|
|||||||
true));
|
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 Model::ParseFromDisk(const std::string& path)
|
||||||
{
|
{
|
||||||
ParsedModel out;
|
ParsedModel out;
|
||||||
|
|||||||
@@ -95,6 +95,12 @@ namespace Engine {
|
|||||||
halfSize, r, g, b, a);
|
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)
|
Scope<Cubemap> VulkanAssetUploader::LoadEnvironment(const std::string& path)
|
||||||
{
|
{
|
||||||
// compute 预积分(毫秒级):HDR → cube + irradiance/prefilter/LUT
|
// compute 预积分(毫秒级):HDR → cube + irradiance/prefilter/LUT
|
||||||
|
|||||||
@@ -185,6 +185,23 @@ namespace Engine::Serialization {
|
|||||||
constexpr std::string_view kPlane = "procedural://plane/";
|
constexpr std::string_view kPlane = "procedural://plane/";
|
||||||
constexpr std::string_view kTPlane =
|
constexpr std::string_view kTPlane =
|
||||||
"procedural://transparent-plane/";
|
"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) {
|
if (path.rfind(kTPlane, 0) == 0) {
|
||||||
// "{halfSize}/{r},{g},{b},{a}"
|
// "{halfSize}/{r},{g},{b},{a}"
|
||||||
|
|||||||
Reference in New Issue
Block a user