feat: Jolt 物理系统——刚体/碰撞体/物理调试图元 + 引擎时间单源
- PhysicsWorld:Jolt 集成,Dynamic/Kinematic/Static 刚体,姿态写回 Transform(Unity FixedUpdate 语义,吃 scaled dt,暂停自动冻结) - 碰撞体:Box(默认/手动 halfExtents)+ Mesh 碰撞体(Static→精确 MeshShape / Dynamic·Kinematic→ConvexHull,数据源 = Mesh ctor 缓存 的 CPU 位置副本,退化三角剔除,失败回退 Box 并告警) - PhysicsDebugPass:物理调试图元每帧上传绘制(debug_line shader) - Time:引擎时间单源(BeginFrame 落定 FrameTime,scaled/unscaled 分离) - JoltPhysics 以 submodule 接入(官方 url)
This commit is contained in:
@@ -1,4 +1,8 @@
|
|||||||
.idea/
|
.idea/
|
||||||
|
.trae/
|
||||||
|
|
||||||
|
# 运行日志(工作目录生成)
|
||||||
|
HelloVulkan.log
|
||||||
|
|
||||||
cmake-build-*/
|
cmake-build-*/
|
||||||
out/
|
out/
|
||||||
|
|||||||
@@ -26,3 +26,6 @@
|
|||||||
[submodule "Lib/meshoptimizer"]
|
[submodule "Lib/meshoptimizer"]
|
||||||
path = Lib/meshoptimizer
|
path = Lib/meshoptimizer
|
||||||
url = https://github.com/zeux/meshoptimizer.git
|
url = https://github.com/zeux/meshoptimizer.git
|
||||||
|
[submodule "Lib/JoltPhysics"]
|
||||||
|
path = Lib/JoltPhysics
|
||||||
|
url = https://github.com/jrouwe/JoltPhysics.git
|
||||||
|
|||||||
@@ -12,6 +12,23 @@ add_subdirectory(Lib/glfw)
|
|||||||
add_subdirectory(Lib/glm)
|
add_subdirectory(Lib/glm)
|
||||||
add_subdirectory(Lib/spdlog)
|
add_subdirectory(Lib/spdlog)
|
||||||
add_subdirectory(Lib/stb)
|
add_subdirectory(Lib/stb)
|
||||||
|
# meshoptimizer(submodule,LOD 网格简化;demo/gltfpack 默认关闭,install 不需要)
|
||||||
|
set(MESHOPT_INSTALL OFF CACHE BOOL "" FORCE)
|
||||||
|
add_subdirectory(Lib/meshoptimizer)
|
||||||
|
|
||||||
|
# ---- Jolt Physics(submodule v5.6,刚体/软体/角色/载具模拟)----
|
||||||
|
# 顶层 CMake 在 Build/ 子目录;只编 jolt 静态库,示例/测试目标默认全关
|
||||||
|
set(JPH_BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
|
||||||
|
set(INTERPROCEDURAL_OPTIMIZATION OFF CACHE BOOL "" FORCE) # LTO 拖慢开发迭代
|
||||||
|
set(OVERRIDE_CXX_FLAGS OFF CACHE BOOL "" FORCE) # 不动全局优化参数
|
||||||
|
set(FLOATING_POINT_EXCEPTIONS_ENABLED OFF CACHE BOOL "" FORCE) # NaN 进物理不炸,仅报警
|
||||||
|
# JPH_USE_CPU_COMPUTE 默认 ON,需要 DXC 把 Shaders/*.hlsl 编成 .spv(本机无
|
||||||
|
# 工具链);我们不使用 Jolt 的 GPU compute 路径,连同后端 shader 一并关掉
|
||||||
|
set(JPH_USE_CPU_COMPUTE OFF CACHE BOOL "" FORCE)
|
||||||
|
set(JPH_USE_DX12 OFF CACHE BOOL "" FORCE)
|
||||||
|
set(JPH_USE_VK OFF CACHE BOOL "" FORCE)
|
||||||
|
set(JPH_USE_MTL OFF CACHE BOOL "" FORCE)
|
||||||
|
add_subdirectory(Lib/JoltPhysics/Build)
|
||||||
|
|
||||||
# ---- Assimp(vendored submodule,模型导入;只编译需要的 importer) ----
|
# ---- Assimp(vendored submodule,模型导入;只编译需要的 importer) ----
|
||||||
set(BUILD_SHARED_LIBS OFF) # 静态链接
|
set(BUILD_SHARED_LIBS OFF) # 静态链接
|
||||||
|
|||||||
+34
-30
@@ -1,10 +1,8 @@
|
|||||||
file(GLOB_RECURSE ENGINE_SOURCES
|
# ============ 第三方库目标(Engine 的依赖) ============
|
||||||
src/*.cpp
|
|
||||||
)
|
|
||||||
|
|
||||||
# ImGui(docking 分支,vendored submodule)
|
# ImGui(docking 分支,vendored submodule)
|
||||||
set(IMGUI_DIR ${CMAKE_SOURCE_DIR}/Lib/imgui)
|
set(IMGUI_DIR ${CMAKE_SOURCE_DIR}/Lib/imgui)
|
||||||
set(IMGUI_SOURCES
|
add_library(imgui STATIC
|
||||||
${IMGUI_DIR}/imgui.cpp
|
${IMGUI_DIR}/imgui.cpp
|
||||||
${IMGUI_DIR}/imgui_draw.cpp
|
${IMGUI_DIR}/imgui_draw.cpp
|
||||||
${IMGUI_DIR}/imgui_tables.cpp
|
${IMGUI_DIR}/imgui_tables.cpp
|
||||||
@@ -13,44 +11,50 @@ set(IMGUI_SOURCES
|
|||||||
${IMGUI_DIR}/backends/imgui_impl_glfw.cpp
|
${IMGUI_DIR}/backends/imgui_impl_glfw.cpp
|
||||||
${IMGUI_DIR}/backends/imgui_impl_vulkan.cpp
|
${IMGUI_DIR}/backends/imgui_impl_vulkan.cpp
|
||||||
)
|
)
|
||||||
|
target_include_directories(imgui PUBLIC
|
||||||
|
${IMGUI_DIR}
|
||||||
|
${IMGUI_DIR}/backends)
|
||||||
|
# 两个 backend 头文件分别依赖 GLFW / Vulkan 头
|
||||||
|
target_link_libraries(imgui PUBLIC glfw Vulkan::Vulkan)
|
||||||
|
|
||||||
# 编译成静态库
|
# Tracy(CPU/GPU 帧分析)
|
||||||
add_library(Engine STATIC ${ENGINE_SOURCES} ${IMGUI_SOURCES})
|
add_library(tracy STATIC ${CMAKE_SOURCE_DIR}/Lib/tracy/public/TracyClient.cpp)
|
||||||
|
target_include_directories(tracy PUBLIC ${CMAKE_SOURCE_DIR}/Lib/tracy/public)
|
||||||
|
target_compile_definitions(tracy PUBLIC
|
||||||
|
TRACY_ENABLE
|
||||||
|
# 按需剖析:GPU context 宣告(GpuNewContext)与连接绑定——GUI 永远晚于
|
||||||
|
# 程序启动连接,没有这个宏宣告消息被丢弃,GPU 时间线永远空白
|
||||||
|
TRACY_ON_DEMAND)
|
||||||
|
find_package(Threads REQUIRED)
|
||||||
|
target_link_libraries(tracy PUBLIC Threads::Threads)
|
||||||
|
|
||||||
|
# ============ Engine ============
|
||||||
|
|
||||||
|
file(GLOB_RECURSE ENGINE_SOURCES CONFIGURE_DEPENDS src/*.cpp)
|
||||||
|
add_library(Engine STATIC ${ENGINE_SOURCES})
|
||||||
|
|
||||||
|
# PhysicsDebugDraw 派生 Jolt::DebugRendererSimple:Jolt 以 -fno-rtti 编译
|
||||||
|
#(有 key function 的基类 typeinfo 符号不存在),该 TU 必须同步关闭 RTTI,
|
||||||
|
# 否则派生类 typeinfo 引用悬空符号(链接期 undefined reference)
|
||||||
|
set_source_files_properties(src/Physics/PhysicsDebugDraw.cpp PROPERTIES
|
||||||
|
COMPILE_OPTIONS "-fno-rtti")
|
||||||
|
|
||||||
target_include_directories(Engine PUBLIC
|
target_include_directories(Engine PUBLIC
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/include
|
${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||||
|
# header-only 库:纯 include 路径
|
||||||
${CMAKE_SOURCE_DIR}/Lib/entt/src
|
${CMAKE_SOURCE_DIR}/Lib/entt/src
|
||||||
${CMAKE_SOURCE_DIR}/Lib/VulkanMemoryAllocator/include
|
${CMAKE_SOURCE_DIR}/Lib/VulkanMemoryAllocator/include)
|
||||||
${IMGUI_DIR}
|
|
||||||
${IMGUI_DIR}/backends
|
|
||||||
)
|
|
||||||
|
|
||||||
# 链接第三方库
|
|
||||||
target_link_libraries(Engine PUBLIC
|
target_link_libraries(Engine PUBLIC
|
||||||
glfw
|
glfw
|
||||||
|
glm::glm
|
||||||
Vulkan::Vulkan
|
Vulkan::Vulkan
|
||||||
spdlog::spdlog
|
spdlog::spdlog
|
||||||
stb
|
stb
|
||||||
assimp::assimp
|
assimp::assimp
|
||||||
)
|
meshoptimizer
|
||||||
|
Jolt::Jolt
|
||||||
# Tracy(CPU/GPU 帧分析):宏关闭时全部退化空操作,零开销
|
imgui
|
||||||
set(TRACY_DIR ${CMAKE_SOURCE_DIR}/Lib/tracy)
|
tracy)
|
||||||
target_include_directories(Engine PUBLIC ${TRACY_DIR}/public)
|
|
||||||
target_sources(Engine PRIVATE ${TRACY_DIR}/public/TracyClient.cpp)
|
|
||||||
target_compile_definitions(Engine PUBLIC TRACY_ENABLE)
|
|
||||||
# 按需剖析:GPU context 宣告(GpuNewContext)与连接绑定——GUI 永远晚于
|
|
||||||
# 程序启动连接,没有这个宏宣告消息被丢弃,GPU 时间线永远空白
|
|
||||||
target_compile_definitions(Engine PUBLIC TRACY_ON_DEMAND)
|
|
||||||
|
|
||||||
find_package(Threads REQUIRED)
|
|
||||||
target_link_libraries(Engine PUBLIC Threads::Threads)
|
|
||||||
|
|
||||||
# meshoptimizer(vendored,LOD 网格简化;submodule 机制故障改为直接 vendor)
|
|
||||||
file(GLOB MESHOPT_SOURCES ${CMAKE_SOURCE_DIR}/Lib/meshoptimizer/src/*.cpp)
|
|
||||||
add_library(meshopt STATIC ${MESHOPT_SOURCES})
|
|
||||||
target_include_directories(meshopt PUBLIC ${CMAKE_SOURCE_DIR}/Lib/meshoptimizer/src)
|
|
||||||
target_link_libraries(Engine PUBLIC meshopt)
|
|
||||||
|
|
||||||
target_compile_definitions(Engine PRIVATE VULKAN_HPP_DISPATCH_LOADER_DYNAMIC=1)
|
target_compile_definitions(Engine PRIVATE VULKAN_HPP_DISPATCH_LOADER_DYNAMIC=1)
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
//
|
||||||
|
// Time:帧时间系统(单源采集 → 帧契约 → 静态门面)。
|
||||||
|
//
|
||||||
|
// 数据流(镜像 FrameSceneData 的落帧模式):
|
||||||
|
// Application::Run 帧首采集真实 dt(steady_clock 单源,钳制卡顿)→
|
||||||
|
// Time::BeginFrame 落定当帧 FrameTime(业务当帧只读)→ 三轨消费:
|
||||||
|
// 游戏轨 Dt() = unscaledDt × timeScale(OnUpdate/动画/粒子)
|
||||||
|
// 真实轨 UnscaledDt() (编辑器相机/UI/FPS,暂停不受影响)
|
||||||
|
// 物理轨 accumulator (吃 Dt(),scale=0 天然冻结,编辑器对账仍跑)
|
||||||
|
//
|
||||||
|
// 对齐 UE/Unity:
|
||||||
|
// Time::Delta/UnscaledDelta ≈ Unity Time.deltaTime / unscaledDeltaTime
|
||||||
|
// Time::SetTimeScale(0) ≈ Unity timeScale=0 / UE GlobalTimeDilation=0
|
||||||
|
// FrameTime.elapsed ≈ Unity Time.time(scaled 累计)
|
||||||
|
// Time::Realtime() ≈ Unity realtimeSinceStartup
|
||||||
|
// TimerManager(UE SetTimer 语义)留二期,有真实消费场景再上。
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef HELLOVULKAN_TIME_H
|
||||||
|
#define HELLOVULKAN_TIME_H
|
||||||
|
|
||||||
|
#include <Engine/Core/Types.h>
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
namespace Engine {
|
||||||
|
|
||||||
|
// 帧时间契约:Run 帧首落定,当帧只读
|
||||||
|
struct FrameTime {
|
||||||
|
f64 unscaledDt = 0.0; // 真实帧时长(秒,f64 防 long-run 误差)
|
||||||
|
f32 timeScale = 1.0f; // 全局缩放(0 = 暂停)
|
||||||
|
u64 frameCount = 0;
|
||||||
|
f64 elapsed = 0.0; // 游戏累计(scaled)
|
||||||
|
f64 unscaledElapsed = 0.0; // 真实累计
|
||||||
|
|
||||||
|
f32 Dt() const { return static_cast<f32>(unscaledDt) * timeScale; }
|
||||||
|
f32 UnscaledDt() const { return static_cast<f32>(unscaledDt); }
|
||||||
|
f32 Milliseconds() const { return Dt() * 1000.0f; }
|
||||||
|
f32 UnscaledMilliseconds() const { return UnscaledDt() * 1000.0f; }
|
||||||
|
};
|
||||||
|
|
||||||
|
// 静态门面:任意深度代码免穿引用拿 Application(Unity Time 风格)
|
||||||
|
class Time {
|
||||||
|
public:
|
||||||
|
// ---- 消费端 ----
|
||||||
|
static const FrameTime& Current();
|
||||||
|
static f32 Delta(); // scaled dt(当帧)
|
||||||
|
static f32 UnscaledDelta();
|
||||||
|
static f64 Elapsed(); // scaled 累计(秒)
|
||||||
|
static f64 Realtime(); // 进程启动至今(真实秒,性能测量统一入口)
|
||||||
|
static f32 TimeScale();
|
||||||
|
static u64 FrameCount();
|
||||||
|
|
||||||
|
// ---- 控制意图(下一帧生效)----
|
||||||
|
static void SetTimeScale(f32 scale); // 0 = 暂停;负值钳为 0
|
||||||
|
|
||||||
|
// ---- 框架内部:Application::Run 帧首调用 ----
|
||||||
|
static void BeginFrame(f64 unscaledDt, u64 frameCount);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static FrameTime s_Current;
|
||||||
|
static f32 s_PendingScale;
|
||||||
|
static const std::chrono::steady_clock::time_point s_Startup;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Engine
|
||||||
|
|
||||||
|
#endif //HELLOVULKAN_TIME_H
|
||||||
@@ -134,6 +134,45 @@ namespace Engine::ECS {
|
|||||||
bool castShadow = false; // 方向光可投影(引擎取第一个 castShadow 方向光为 CSM 光源)
|
bool castShadow = false; // 方向光可投影(引擎取第一个 castShadow 方向光为 CSM 光源)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 刚体(Jolt 后端)。纯数据 POD——头文件零 Jolt 依赖,
|
||||||
|
// bodyId 存 JPH::BodyID 的裸位模式,翻译在 PhysicsWorld。
|
||||||
|
// 生命周期:PhysicsWorld::SyncScene 每帧对账(创建/销毁/参数同步),
|
||||||
|
// 动态体的 Transform 由物理写回(Writeback 标脏),静态/运动学的
|
||||||
|
// Transform 由编辑器驱动同步进 body。
|
||||||
|
// 扩展路线:软体/布料 = SoftBodyComponent(Jolt SoftBodySharedSettings),
|
||||||
|
// 角色 = CharacterControllerComponent(CharacterVirtual),载具 = VehicleComponent
|
||||||
|
// (VehicleConstraint)——都走 PhysicsWorld 门面,不复用本组件。
|
||||||
|
struct RigidBodyComponent {
|
||||||
|
enum class BodyType : u8 { Static = 0, Dynamic = 1, Kinematic = 2 };
|
||||||
|
// Mesh:用实体 MeshRenderer 的视觉网格建碰撞(Unity MeshCollider
|
||||||
|
// 语义)——Static → Jolt MeshShape(精确三角网格,不可移动);
|
||||||
|
// Dynamic/Kinematic → ConvexHullShape(凸包近似,MeshShape 太贵
|
||||||
|
// 不能动)。数据源 Mesh 的 CPU 位置副本,无 MeshRenderer 回退 Box
|
||||||
|
enum class ColliderType : u8 { Box = 0, Sphere = 1, Mesh = 2 };
|
||||||
|
|
||||||
|
BodyType type = BodyType::Dynamic; // Unity 语义:加 Rigidbody 即受物理驱动
|
||||||
|
ColliderType collider = ColliderType::Box;
|
||||||
|
|
||||||
|
// 碰撞体尺寸(× Transform 缩放,Unity BoxCollider.size 语义)——
|
||||||
|
// 程序化模型顶点烘焙尺寸,不能从 scale 反推
|
||||||
|
glm::vec3 boxHalfExtents{0.5f, 0.5f, 0.5f};
|
||||||
|
f32 sphereRadius = 0.5f;
|
||||||
|
|
||||||
|
// 动态体参数
|
||||||
|
f32 mass = 1.0f;
|
||||||
|
f32 gravityScale = 1.0f;
|
||||||
|
f32 friction = 0.5f;
|
||||||
|
f32 restitution = 0.2f;
|
||||||
|
|
||||||
|
// 传感器(Unity isTrigger 语义):只产生 Added/Removed 事件,无碰撞解算
|
||||||
|
bool isSensor = false;
|
||||||
|
|
||||||
|
// 运行时(PhysicsWorld 维护,勿手改)
|
||||||
|
u32 bodyId = 0; // JPH::BodyID 裸位
|
||||||
|
bool bodyValid = false;
|
||||||
|
glm::vec3 velocity{0.0f}; // 只读调试视图:线速度(m/s)
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace Engine::ECS
|
} // namespace Engine::ECS
|
||||||
|
|
||||||
#endif //HELLOVULKAN_COMPONENTS_H
|
#endif //HELLOVULKAN_COMPONENTS_H
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
//
|
||||||
|
// PhysicsDebugDraw:Jolt 调试图元收集器(JPH::DebugRendererSimple 派生)。
|
||||||
|
//
|
||||||
|
// 职责只有"收集":Jolt 各调试源(BodyManager::Draw 的形状线框/速度箭头、
|
||||||
|
// ConstraintManager::DrawConstraints、PhysicsWorld 手绘的重力/加速度箭头)
|
||||||
|
// 触发的 DrawLine/DrawTriangle 回调全部收进顶点数组;渲染侧
|
||||||
|
// (PhysicsDebugPass)每帧上传到 ring VB 绘制。收集器不理解 Vulkan,
|
||||||
|
// 渲染 pass 不理解 Jolt——图元契约见 PhysicsDebugTypes.h。
|
||||||
|
//
|
||||||
|
// 生命周期:PhysicsWorld 持有并安装为 JPH::DebugRenderer::sInstance;
|
||||||
|
// DebugRendererSimple 的构造即 DebugRenderer::Initialize()(预烘焙
|
||||||
|
// box/sphere/cylinder 等通用几何 batch),NextFrame() 每帧释放不再
|
||||||
|
// 引用的缓存几何。
|
||||||
|
//
|
||||||
|
// 扩展性(物理调试的唯一图元出口,新调试源零新通道):
|
||||||
|
// 软体布料 → DrawSettings 的 mDrawSoftBody* 开关原生生效
|
||||||
|
// 角色控制器 → CharacterVirtual::DebugDraw 走同一 sInstance
|
||||||
|
// 载具控制器 → VehicleConstraint::Draw 走同一 sInstance
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef HELLOVULKAN_PHYSICSDEBUGDRAW_H
|
||||||
|
#define HELLOVULKAN_PHYSICSDEBUGDRAW_H
|
||||||
|
|
||||||
|
#include <Engine/Physics/PhysicsDebugTypes.h>
|
||||||
|
|
||||||
|
#ifdef JPH_DEBUG_RENDERER
|
||||||
|
|
||||||
|
#include <Jolt/Jolt.h>
|
||||||
|
#include <Jolt/Renderer/DebugRendererSimple.h>
|
||||||
|
|
||||||
|
namespace Engine {
|
||||||
|
|
||||||
|
class PhysicsDebugDraw final : public JPH::DebugRendererSimple {
|
||||||
|
public:
|
||||||
|
// 析构交给基类:~DebugRenderer 自己断言并清 sInstance(JPH_ASSERT(sInstance == this)),
|
||||||
|
// 派生类手动清会让基类断言看到 nullptr 而失败
|
||||||
|
|
||||||
|
PhysicsDebugFrame& Frame() { return m_Frame; }
|
||||||
|
|
||||||
|
// 每次收集前调用:清空上一帧图元(容量保留,稳态零堆分配)
|
||||||
|
void BeginCollect() { m_Frame.Clear(); }
|
||||||
|
|
||||||
|
// ---- DebugRenderer 回调(基类其余绘制原语全部路由到这里)----
|
||||||
|
void DrawLine(JPH::RVec3Arg inFrom, JPH::RVec3Arg inTo,
|
||||||
|
JPH::ColorArg inColor) override;
|
||||||
|
void DrawTriangle(JPH::RVec3Arg inV1, JPH::RVec3Arg inV2,
|
||||||
|
JPH::RVec3Arg inV3, JPH::ColorArg inColor,
|
||||||
|
JPH::DebugRenderer::ECastShadow inCastShadow) override;
|
||||||
|
void DrawText3D(JPH::RVec3Arg, const JPH::string_view&,
|
||||||
|
JPH::ColorArg, float) override {
|
||||||
|
// 3D 文本暂不支持(遮挡 UI 噪音,需要时再挂 ImGui 调试层)
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static glm::vec3 ToGlm(JPH::RVec3Arg v)
|
||||||
|
{
|
||||||
|
return {static_cast<f32>(v.GetX()), static_cast<f32>(v.GetY()),
|
||||||
|
static_cast<f32>(v.GetZ())};
|
||||||
|
}
|
||||||
|
static glm::vec4 ToGlm(JPH::ColorArg c)
|
||||||
|
{
|
||||||
|
constexpr f32 kInv = 1.0f / 255.0f;
|
||||||
|
return {c.r * kInv, c.g * kInv, c.b * kInv, c.a * kInv};
|
||||||
|
}
|
||||||
|
|
||||||
|
PhysicsDebugFrame m_Frame;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Engine
|
||||||
|
|
||||||
|
#else // !JPH_DEBUG_RENDERER
|
||||||
|
|
||||||
|
// Distribution 构建:Jolt 调试渲染被整体裁剪(DEBUG_RENDERER_IN_* 均关),
|
||||||
|
// 收集器退化为空实现——所有消费方照常编译,零绘制零开销
|
||||||
|
namespace Engine {
|
||||||
|
|
||||||
|
class PhysicsDebugDraw {
|
||||||
|
public:
|
||||||
|
PhysicsDebugFrame& Frame() { return m_Frame; }
|
||||||
|
void BeginCollect() { m_Frame.Clear(); }
|
||||||
|
void NextFrame() {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
PhysicsDebugFrame m_Frame;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Engine
|
||||||
|
|
||||||
|
#endif // JPH_DEBUG_RENDERER
|
||||||
|
|
||||||
|
#endif //HELLOVULKAN_PHYSICSDEBUGDRAW_H
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
//
|
||||||
|
// PhysicsDebugTypes:物理调试可视化的数据契约(Jolt 零依赖)。
|
||||||
|
//
|
||||||
|
// PhysicsDebugDraw(收集器,Physics 层)生产、PhysicsDebugPass(渲染)
|
||||||
|
// 消费;渲染侧只 include 本头文件,不接触任何 Jolt 类型——分层约束
|
||||||
|
// 与 RigidBodyComponent 的"零 Jolt 依赖"一致。
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef HELLOVULKAN_PHYSICSDEBUGTYPES_H
|
||||||
|
#define HELLOVULKAN_PHYSICSDEBUGTYPES_H
|
||||||
|
|
||||||
|
#include <Engine/Core/Types.h>
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Engine {
|
||||||
|
|
||||||
|
// 图元顶点(28B):世界空间位置 + 线性 HDR 颜色(可 >1,过 tonemap
|
||||||
|
// 依旧醒目)。线段/三角形共用同一顶点格式,各走一条动态 VB
|
||||||
|
struct PhysicsDebugVertex {
|
||||||
|
glm::vec3 pos;
|
||||||
|
glm::vec4 color;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 单帧收集结果:PhysicsWorld 每个 Step 调用尾按 debugBits 重新收集
|
||||||
|
//(渲染帧率,收集间隔内数据保持稳定,多渲染帧复用同一份图元)
|
||||||
|
struct PhysicsDebugFrame {
|
||||||
|
std::vector<PhysicsDebugVertex> lines; // 线段顶点(成对)
|
||||||
|
std::vector<PhysicsDebugVertex> triangles; // 三角形顶点(三张一副)
|
||||||
|
|
||||||
|
void Clear() { lines.clear(); triangles.clear(); }
|
||||||
|
bool Empty() const { return lines.empty() && triangles.empty(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
// 可视化位掩码(ImGui 经 PhysicsWorld 门面读写;0 = 全关)
|
||||||
|
enum PhysicsDebugBits : u32 {
|
||||||
|
kPhyDbgColliders = 1 << 0, // collider 线框(Jolt BodyManager::Draw)
|
||||||
|
kPhyDbgConstraints = 1 << 1, // 约束(关节/软体约束,接入后生效)
|
||||||
|
kPhyDbgGravity = 1 << 2, // 重力箭头(手绘,Jolt 无现成 API)
|
||||||
|
kPhyDbgVelocity = 1 << 3, // 速度箭头(Jolt DrawSettings.mDrawVelocity)
|
||||||
|
kPhyDbgAccel = 1 << 4, // 加速度箭头(手绘,速度差分/定步长)
|
||||||
|
kPhyDbgDefault = kPhyDbgColliders | kPhyDbgGravity |
|
||||||
|
kPhyDbgVelocity | kPhyDbgAccel,
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Engine
|
||||||
|
|
||||||
|
#endif //HELLOVULKAN_PHYSICSDEBUGTYPES_H
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
//
|
||||||
|
// PhysicsEvents:碰撞事件数据契约(Jolt 零依赖)。
|
||||||
|
//
|
||||||
|
// PhysicsWorld 内部的 ContactListenerImpl 在步进中收集、帧尾入队;
|
||||||
|
// 消费方(App/游戏逻辑)只 include 本头文件,不接触任何 Jolt 类型
|
||||||
|
// ——分层约束与 RigidBodyComponent 的"零 Jolt 依赖"一致。
|
||||||
|
//
|
||||||
|
// 事件语义 = 最近一次物理步进(Step 开头清空队列,消费方每帧读取)。
|
||||||
|
// a/b 存实体位模式(entt::to_integral 的裸值),消费方用
|
||||||
|
// static_cast<entt::entity>(bits) 还原,并自行 registry.valid() 校验
|
||||||
|
// ——实体可能在事件产生后、消费前被删除。
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef HELLOVULKAN_PHYSICSEVENTS_H
|
||||||
|
#define HELLOVULKAN_PHYSICSEVENTS_H
|
||||||
|
|
||||||
|
#include <Engine/Core/Types.h>
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
|
||||||
|
namespace Engine {
|
||||||
|
|
||||||
|
struct CollisionEvent {
|
||||||
|
enum class Kind : u8 { Added = 0, Persisted = 1, Removed = 2 };
|
||||||
|
|
||||||
|
u32 a = 0; // 实体位模式(Body1;Jolt 保证 BodyID 1 < 2)
|
||||||
|
u32 b = 0; // 实体位模式(Body2)
|
||||||
|
Kind kind = Kind::Added;
|
||||||
|
glm::vec3 point{0.0f}; // 世界空间首个接触点(Removed 时无效)
|
||||||
|
glm::vec3 normal{0.0f}; // 世界空间法线(body2 沿此方向推出;Removed 时无效)
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Engine
|
||||||
|
|
||||||
|
#endif //HELLOVULKAN_PHYSICSEVENTS_H
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
//
|
||||||
|
// PhysicsRay:射线查询数据契约(Jolt 零依赖)。
|
||||||
|
//
|
||||||
|
// PhysicsWorld::RayCast(BodyInterface/NarrowPhaseQuery 门面)生产;
|
||||||
|
// 消费方(游戏查询/编辑器拾取)只 include 本头文件,不接触任何 Jolt
|
||||||
|
// 类型——分层约束与 PhysicsEvents.h 一致。
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef HELLOVULKAN_PHYSICSRAY_H
|
||||||
|
#define HELLOVULKAN_PHYSICSRAY_H
|
||||||
|
|
||||||
|
#include <Engine/Core/Types.h>
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
|
||||||
|
namespace Engine {
|
||||||
|
|
||||||
|
// 射线命中结果(最近命中单结果;编辑器拾取/点击选择够用,
|
||||||
|
// 穿透列表走 CastRayCollector 扩展,本契约保持最小)
|
||||||
|
struct RayHit {
|
||||||
|
u32 entityBits = 0; // 命中实体的位模式(消费方自行 valid 校验)
|
||||||
|
glm::vec3 point{0.0f}; // 世界空间命中点
|
||||||
|
glm::vec3 normal{0.0f}; // 世界空间表面法线(命中子形状)
|
||||||
|
f32 distance = 0.0f; // 命中距离(origin 起算)
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Engine
|
||||||
|
|
||||||
|
#endif //HELLOVULKAN_PHYSICSRAY_H
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
//
|
||||||
|
// PhysicsWorld:物理世界门面(Jolt 后端)。
|
||||||
|
//
|
||||||
|
// 镜像 SceneRenderer 的单向数据流:
|
||||||
|
// SyncScene(读 ECS 对账 body)→ PhysicsSystem::Update → Writeback(动态体 → Transform 标脏)
|
||||||
|
// 定步长 accumulator(1/60,最多 4 子步,积压丢弃防螺旋死亡)。
|
||||||
|
//
|
||||||
|
// 分层约束:
|
||||||
|
// - 组件层(Components.h)零 Jolt 依赖:bodyId 存 JPH::BodyID 裸位模式
|
||||||
|
// - App/编辑器只经 Application::GetPhysics() 访问开关,不接触 Jolt 头
|
||||||
|
//
|
||||||
|
// 可扩展路线(本门面是唯一入口,feature 各自分文件演进,禁止互相耦合):
|
||||||
|
// 软体/布料 → Jolt SoftBodySharedSettings/SoftBodyCreationSettings(原生支持),
|
||||||
|
// 扩展为 SyncSoftBodies/WritebackSoft(网格引用走 AssetRef)
|
||||||
|
// 角色控制器 → CharacterVirtual(运动学扫掠,非刚体,独立 Controller 类持有)
|
||||||
|
// 载具控制器 → VehicleConstraint(挂动态刚体 chassis 上,独立 Controller 类)
|
||||||
|
// 碰撞事件 → ContactListenerImpl(本类嵌套,已实现)+ GetEvents 帧尾队列
|
||||||
|
// 射线查询 → BodyInterface::RayCast 门面(编辑器拾取/游戏查询)
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef HELLOVULKAN_PHYSICSWORLD_H
|
||||||
|
#define HELLOVULKAN_PHYSICSWORLD_H
|
||||||
|
|
||||||
|
#include <Engine/Core/Types.h>
|
||||||
|
#include <Engine/Physics/PhysicsDebugDraw.h>
|
||||||
|
#include <Engine/Physics/PhysicsEvents.h>
|
||||||
|
#include <Engine/Physics/PhysicsRay.h>
|
||||||
|
|
||||||
|
#include <Jolt/Jolt.h>
|
||||||
|
#include <Jolt/Physics/PhysicsSystem.h>
|
||||||
|
#include <Jolt/Physics/Body/BodyID.h>
|
||||||
|
#include <Jolt/Core/JobSystemThreadPool.h>
|
||||||
|
#include <Jolt/Core/TempAllocator.h>
|
||||||
|
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
#include <mutex>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Engine {
|
||||||
|
|
||||||
|
namespace ECS { class Scene; struct RigidBodyComponent; struct TransformComponent; }
|
||||||
|
namespace Assets { class AssetManager; }
|
||||||
|
|
||||||
|
class PhysicsWorld {
|
||||||
|
public:
|
||||||
|
// assets:Mesh 碰撞体解析 modelRef(MeshShape/ConvexHull 数据源)
|
||||||
|
explicit PhysicsWorld(ECS::Scene& scene, Assets::AssetManager* assets);
|
||||||
|
~PhysicsWorld();
|
||||||
|
|
||||||
|
PhysicsWorld(const PhysicsWorld&) = delete;
|
||||||
|
PhysicsWorld& operator=(const PhysicsWorld&) = delete;
|
||||||
|
|
||||||
|
// 帧循环调用(OnUpdate 之前,Unity FixedUpdate→Update 语义)
|
||||||
|
void Step(f32 dt);
|
||||||
|
|
||||||
|
void SetEnabled(bool enabled) { m_Enabled = enabled; }
|
||||||
|
bool IsEnabled() const { return m_Enabled; }
|
||||||
|
|
||||||
|
// ---- 物理调试可视化(ImGui 开关;位掩码见 PhysicsDebugBits)----
|
||||||
|
// Step 尾部按位收集图元(渲染帧率),Renderer 每帧上传绘制
|
||||||
|
void SetDebugDrawBits(u32 bits) { m_DebugBits = bits; }
|
||||||
|
u32 GetDebugDrawBits() const { return m_DebugBits; }
|
||||||
|
PhysicsDebugFrame& GetDebugFrame() { return m_DebugDraw->Frame(); }
|
||||||
|
|
||||||
|
// ---- 碰撞事件(语义 = 最近一次步进;Step 开头清空)----
|
||||||
|
// 主线程在 Step 之外读取,与多线程回调互不重叠,无需加锁
|
||||||
|
const std::vector<CollisionEvent>& GetEvents() const {
|
||||||
|
return m_PendingEvents;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 射线查询(游戏查询/编辑器拾取;主线程调用,暂停时也可用)----
|
||||||
|
// dir 会被内部归一化;maxDist 限制射程。命中返回 true 并填 outHit
|
||||||
|
bool RayCast(const glm::vec3& origin, const glm::vec3& dir,
|
||||||
|
f32 maxDist, RayHit& outHit) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// 创建时缓存的参数(变更检测 → 重建 body)
|
||||||
|
struct BodyRecord {
|
||||||
|
JPH::BodyID id{};
|
||||||
|
u8 type = 0; // RigidBodyComponent::BodyType
|
||||||
|
u8 collider = 0; // RigidBodyComponent::ColliderType
|
||||||
|
glm::vec3 colliderSize{0.0f}; // 碰撞体尺寸缓存(box 半尺寸)
|
||||||
|
f32 radius = 0.0f; // 球半径缓存
|
||||||
|
f32 mass = 0.0f;
|
||||||
|
bool isSensor = false; // 传感器标记(变更 → 重建 body)
|
||||||
|
glm::vec3 lastPos{0.0f}; // 静态/运动体编辑器拖动检测
|
||||||
|
glm::vec3 lastRot{0.0f};
|
||||||
|
// 加速度箭头用:上一步速度(定步长差分)与平滑后的加速度
|
||||||
|
glm::vec3 prevVel{0.0f};
|
||||||
|
glm::vec3 lastAccel{0.0f};
|
||||||
|
};
|
||||||
|
|
||||||
|
void SyncScene(); // 对账:孤儿清理 / 创建 / 参数同步
|
||||||
|
void Writeback() const; // 动态体 → TransformComponent(标脏)
|
||||||
|
void UpdateAccels(f32 dt); // 速度差分 → BodyRecord.lastAccel(step 率)
|
||||||
|
void CollectDebugData(); // 按 m_DebugBits 收集调试图元(渲染帧率)
|
||||||
|
void CreateBody(u32 entityBits, ECS::RigidBodyComponent& rb,
|
||||||
|
ECS::TransformComponent& tr);
|
||||||
|
// Mesh 碰撞体:实体视觉网格 → Jolt 形状(Static=MeshShape /
|
||||||
|
// Dynamic=ConvexHull)。无 MeshRenderer 或构建失败返回 null(回退 Box)
|
||||||
|
JPH::RefConst<JPH::Shape> BuildMeshShape(u32 entityBits,
|
||||||
|
ECS::RigidBodyComponent& rb,
|
||||||
|
ECS::TransformComponent& tr);
|
||||||
|
void RemoveBody(u32 entityBits);
|
||||||
|
void QueueEvent(CollisionEvent&& ev); // 回调线程 append(多线程并发,加锁)
|
||||||
|
bool TryBitsByBodyId(const JPH::BodyID& id, u32& outBits) const;
|
||||||
|
|
||||||
|
ECS::Scene& m_Scene;
|
||||||
|
Assets::AssetManager* m_Assets = nullptr; // Mesh 碰撞体解析 modelRef
|
||||||
|
bool m_Enabled = true;
|
||||||
|
f32 m_Accumulator = 0.0f;
|
||||||
|
|
||||||
|
// 层配置实现类在 cpp;接口必须比 PhysicsSystem 活得久
|
||||||
|
Scope<JPH::BroadPhaseLayerInterface> m_BPLayerInterface;
|
||||||
|
Scope<JPH::ObjectVsBroadPhaseLayerFilter> m_ObjectVsBPLayerFilter;
|
||||||
|
Scope<JPH::ObjectLayerPairFilter> m_ObjectVsObjectLayerFilter;
|
||||||
|
|
||||||
|
Scope<JPH::TempAllocator> m_TempAllocator;
|
||||||
|
Scope<JPH::JobSystem> m_JobSystem;
|
||||||
|
Scope<JPH::PhysicsSystem> m_System;
|
||||||
|
|
||||||
|
std::unordered_map<u32, BodyRecord> m_Bodies; // entity 位模式 → body
|
||||||
|
|
||||||
|
// ---- 碰撞事件收集(多线程回调入队,主线程消费)----
|
||||||
|
class ContactListenerImpl; // cpp 定义(JPH::ContactListener 派生)
|
||||||
|
Scope<ContactListenerImpl> m_ContactListener;
|
||||||
|
std::mutex m_EventMutex; // 保护 m_PendingEvents(回调并发)
|
||||||
|
std::vector<CollisionEvent> m_PendingEvents; // 最近一次步进的事件
|
||||||
|
|
||||||
|
// ---- 物理调试可视化 ----
|
||||||
|
// 堆上延迟构造:DebugRendererSimple 构造即预烘焙通用几何(走 JPH
|
||||||
|
// 分配器),必须等 ctor 内 call_once 注册完分配器,见 PhysicsWorld.cpp
|
||||||
|
Scope<PhysicsDebugDraw> m_DebugDraw; // Jolt 全局调试渲染后端(收集器)
|
||||||
|
u32 m_DebugBits = 0; // PhysicsDebugBits(默认关,StatsPanel 开关)
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Engine
|
||||||
|
|
||||||
|
#endif //HELLOVULKAN_PHYSICSWORLD_H
|
||||||
@@ -67,7 +67,16 @@ namespace Engine
|
|||||||
};
|
};
|
||||||
const Bounds& GetBounds() const { return m_Bounds; }
|
const Bounds& GetBounds() const { return m_Bounds; }
|
||||||
|
|
||||||
|
// ---- CPU 位置副本(物理 MeshShape/ConvexHull 数据源)----
|
||||||
|
// GPU 上传后完整 Vertex 即弃,但物理碰撞仍需三角形:构造时抽
|
||||||
|
// 位置-only(12B/顶点,约为完整 Vertex 的 1/5)+ LOD0 索引。
|
||||||
|
const std::vector<glm::vec3>& GetCpuPositions() const { return m_CpuPositions; }
|
||||||
|
const std::vector<u32>& GetCpuIndices() const { return m_CpuIndices; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// 物理碰撞数据源(MeshShape 三角网格 / ConvexHull 点云)
|
||||||
|
std::vector<glm::vec3> m_CpuPositions;
|
||||||
|
std::vector<u32> m_CpuIndices; // LOD0(原始精度)
|
||||||
// 同一 index buffer 内连续排布的各级索引区间
|
// 同一 index buffer 内连续排布的各级索引区间
|
||||||
struct LodRange {
|
struct LodRange {
|
||||||
u32 firstIndex = 0;
|
u32 firstIndex = 0;
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
//
|
||||||
|
// PhysicsDebugPass:物理调试图元叠加(collider 线框/约束/重力/速度/
|
||||||
|
// 加速度箭头)。
|
||||||
|
//
|
||||||
|
// 画在 HDRColor(Forward 之后、TAA 之前):调试图元进入场景主链路——
|
||||||
|
// TAA 时域累积抗锯齿、Bloom/Tonemap 正常消费,kDebugTransparent 诊断
|
||||||
|
// 模式下也可见。深度只测不写(被不透明几何正确遮挡),无混合直写
|
||||||
|
// (fullscreen pass 状态泄漏教训)。
|
||||||
|
//
|
||||||
|
// 数据源 = PhysicsWorld 每帧收集的 PhysicsDebugFrame(Application 把
|
||||||
|
// PhysicsWorld 的帧缓冲指针注入 Renderer)。空帧零绘制;debug 开关
|
||||||
|
// 全关时 PhysicsWorld 不收集 → 顶点数为 0 → 本 pass 自然零开销。
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef HELLOVULKAN_PHYSICSDEBUGPASS_H
|
||||||
|
#define HELLOVULKAN_PHYSICSDEBUGPASS_H
|
||||||
|
|
||||||
|
#include <Engine/Core/Types.h>
|
||||||
|
#include <Engine/Renderer/Passes/RenderPass.h>
|
||||||
|
#include <Engine/Renderer/SceneTypes.h>
|
||||||
|
#include <Engine/Vulkan/UniformBuffer.h>
|
||||||
|
#include <vulkan/vulkan.hpp>
|
||||||
|
|
||||||
|
namespace Engine {
|
||||||
|
struct PhysicsDebugFrame; // PhysicsDebugTypes.h(Jolt 零依赖契约)
|
||||||
|
|
||||||
|
class Device;
|
||||||
|
class ShaderManager;
|
||||||
|
class DescriptorManager;
|
||||||
|
|
||||||
|
namespace Passes {
|
||||||
|
|
||||||
|
class PhysicsDebugPass final : public RenderPass {
|
||||||
|
public:
|
||||||
|
// 顶点预算(每帧槽位,按顶点数):物理调试几十个 body 的
|
||||||
|
// 线框 + 箭头远用不满;溢出截断并告警一次
|
||||||
|
static constexpr u32 kMaxLineVerts = 1 << 16;
|
||||||
|
static constexpr u32 kMaxTriVerts = 1 << 16;
|
||||||
|
|
||||||
|
PhysicsDebugPass(Device& device, MemoryAllocator& allocator,
|
||||||
|
ShaderManager& shaders,
|
||||||
|
DescriptorManager& descriptors,
|
||||||
|
FrameSceneData& frame,
|
||||||
|
RG::TextureHandle hdrColor,
|
||||||
|
RG::TextureHandle depth,
|
||||||
|
UniformBuffer& lineVB, UniformBuffer& triVB);
|
||||||
|
~PhysicsDebugPass();
|
||||||
|
|
||||||
|
PhysicsDebugPass(const PhysicsDebugPass&) = delete;
|
||||||
|
PhysicsDebugPass& operator=(const PhysicsDebugPass&) = delete;
|
||||||
|
|
||||||
|
void Register(RG::RenderGraph& rg) override;
|
||||||
|
|
||||||
|
// 每帧 Renderer::Render 里(该槽位 fence 等待后)上传当帧图元
|
||||||
|
void PrepareFrame(u32 frameSlot, const PhysicsDebugFrame& src);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Device& m_Device;
|
||||||
|
DescriptorManager& m_Descriptors;
|
||||||
|
FrameSceneData& m_Frame;
|
||||||
|
RG::TextureHandle m_HDRColor;
|
||||||
|
RG::TextureHandle m_Depth;
|
||||||
|
UniformBuffer& m_LineVB; // per-slot ring(Renderer 持有生命周期)
|
||||||
|
UniformBuffer& m_TriVB;
|
||||||
|
|
||||||
|
vk::PipelineLayout m_Layout; // 无描述符,push = viewProj 64B
|
||||||
|
vk::ShaderEXT m_Vert{}, m_Frag{};
|
||||||
|
|
||||||
|
std::array<u32, kMaxFramesInFlight> m_LineCounts{};
|
||||||
|
std::array<u32, kMaxFramesInFlight> m_TriCounts{};
|
||||||
|
bool m_TruncationWarned = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Passes
|
||||||
|
} // namespace Engine
|
||||||
|
|
||||||
|
#endif //HELLOVULKAN_PHYSICSDEBUGPASS_H
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
//
|
||||||
|
// Time.cpp:门面实现(静态快照 + 意图 pending,帧首落定)。
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <Engine/Core/Time.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace Engine {
|
||||||
|
|
||||||
|
FrameTime Time::s_Current{};
|
||||||
|
f32 Time::s_PendingScale = 1.0f;
|
||||||
|
const std::chrono::steady_clock::time_point Time::s_Startup =
|
||||||
|
std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
const FrameTime& Time::Current() { return s_Current; }
|
||||||
|
f32 Time::Delta() { return s_Current.Dt(); }
|
||||||
|
f32 Time::UnscaledDelta() { return s_Current.UnscaledDt(); }
|
||||||
|
f64 Time::Elapsed() { return s_Current.elapsed; }
|
||||||
|
u64 Time::FrameCount() { return s_Current.frameCount; }
|
||||||
|
f32 Time::TimeScale() { return s_PendingScale; }
|
||||||
|
|
||||||
|
f64 Time::Realtime() {
|
||||||
|
return std::chrono::duration<f64>(
|
||||||
|
std::chrono::steady_clock::now() - s_Startup).count();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Time::SetTimeScale(const f32 scale) {
|
||||||
|
s_PendingScale = std::max(scale, 0.0f); // 不允许负缩放
|
||||||
|
}
|
||||||
|
|
||||||
|
void Time::BeginFrame(const f64 unscaledDt, const u64 frameCount) {
|
||||||
|
s_Current.unscaledDt = unscaledDt;
|
||||||
|
s_Current.timeScale = s_PendingScale; // 意图落帧(当帧只读)
|
||||||
|
s_Current.frameCount = frameCount;
|
||||||
|
s_Current.unscaledElapsed += unscaledDt;
|
||||||
|
s_Current.elapsed += unscaledDt * s_PendingScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Engine
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
//
|
||||||
|
// PhysicsDebugDraw.cpp:回调实现(图元收集)。
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <Engine/Physics/PhysicsDebugDraw.h>
|
||||||
|
|
||||||
|
#ifdef JPH_DEBUG_RENDERER
|
||||||
|
|
||||||
|
namespace Engine {
|
||||||
|
|
||||||
|
void PhysicsDebugDraw::DrawLine(JPH::RVec3Arg inFrom, JPH::RVec3Arg inTo,
|
||||||
|
JPH::ColorArg inColor)
|
||||||
|
{
|
||||||
|
const glm::vec4 c = ToGlm(inColor);
|
||||||
|
m_Frame.lines.push_back({ToGlm(inFrom), c});
|
||||||
|
m_Frame.lines.push_back({ToGlm(inTo), c});
|
||||||
|
}
|
||||||
|
|
||||||
|
void PhysicsDebugDraw::DrawTriangle(JPH::RVec3Arg inV1, JPH::RVec3Arg inV2,
|
||||||
|
JPH::RVec3Arg inV3,
|
||||||
|
JPH::ColorArg inColor,
|
||||||
|
JPH::DebugRenderer::ECastShadow /*inCastShadow*/)
|
||||||
|
{
|
||||||
|
// 纯收集:实心面留待后续(线框模式下本回调不触发——
|
||||||
|
// DebugRendererSimple::DrawGeometry 的 Wireframe 分支走 DrawLine)
|
||||||
|
const glm::vec4 c = ToGlm(inColor);
|
||||||
|
m_Frame.triangles.push_back({ToGlm(inV1), c});
|
||||||
|
m_Frame.triangles.push_back({ToGlm(inV2), c});
|
||||||
|
m_Frame.triangles.push_back({ToGlm(inV3), c});
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Engine
|
||||||
|
|
||||||
|
#endif // JPH_DEBUG_RENDERER
|
||||||
@@ -0,0 +1,634 @@
|
|||||||
|
//
|
||||||
|
// PhysicsWorld.cpp:Jolt 后端实现。层配置/类型注册/对账/写回。
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <Engine/Physics/PhysicsWorld.h>
|
||||||
|
|
||||||
|
#include <Engine/Core/Log.h>
|
||||||
|
#include <Engine/ECS/Components.h>
|
||||||
|
#include <Engine/ECS/Scene.h>
|
||||||
|
|
||||||
|
#include <Jolt/RegisterTypes.h>
|
||||||
|
#include <Jolt/Core/Factory.h>
|
||||||
|
#include <Jolt/Physics/Body/BodyCreationSettings.h>
|
||||||
|
#include <Jolt/Physics/Body/BodyInterface.h>
|
||||||
|
#include <Jolt/Physics/Collision/Shape/BoxShape.h>
|
||||||
|
#include <Jolt/Physics/Collision/Shape/SphereShape.h>
|
||||||
|
#include <Jolt/Physics/Collision/Shape/MeshShape.h>
|
||||||
|
#include <Jolt/Physics/Collision/Shape/ConvexHullShape.h>
|
||||||
|
|
||||||
|
#include <Engine/Assets/AssetManager.h>
|
||||||
|
#include <Engine/Renderer/Model.h>
|
||||||
|
|
||||||
|
#include <glm/gtc/quaternion.hpp>
|
||||||
|
#include <Jolt/Physics/Collision/ContactListener.h>
|
||||||
|
#include <Jolt/Physics/Collision/NarrowPhaseQuery.h>
|
||||||
|
#include <Jolt/Physics/Collision/CastResult.h>
|
||||||
|
#include <Jolt/Physics/Collision/RayCast.h>
|
||||||
|
#include <Jolt/Physics/Body/BodyLock.h>
|
||||||
|
|
||||||
|
#include <glm/gtc/quaternion.hpp>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <mutex>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
namespace Engine {
|
||||||
|
namespace {
|
||||||
|
// ---- 层配置:MVP 两层(非移动/移动),扩软体/传感器时再分层 ----
|
||||||
|
constexpr JPH::ObjectLayer LAYER_NON_MOVING{0};
|
||||||
|
constexpr JPH::ObjectLayer LAYER_MOVING{1};
|
||||||
|
constexpr JPH::BroadPhaseLayer BP_NON_MOVING{0};
|
||||||
|
constexpr JPH::BroadPhaseLayer BP_MOVING{1};
|
||||||
|
|
||||||
|
// ObjectLayer → BroadPhaseLayer 映射
|
||||||
|
class BPLayerInterfaceImpl final : public JPH::BroadPhaseLayerInterface {
|
||||||
|
public:
|
||||||
|
JPH::uint GetNumBroadPhaseLayers() const override { return 2; }
|
||||||
|
|
||||||
|
JPH::BroadPhaseLayer GetBroadPhaseLayer(JPH::ObjectLayer layer) const override {
|
||||||
|
return layer == LAYER_NON_MOVING ? BP_NON_MOVING : BP_MOVING;
|
||||||
|
}
|
||||||
|
#if defined(JPH_EXTERNAL_PROFILE) || defined(JPH_PROFILE_ENABLED)
|
||||||
|
const char *GetBroadPhaseLayerName(JPH::BroadPhaseLayer) const override {
|
||||||
|
return "BP";
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
class ObjectVsBPLayerFilterImpl final : public JPH::ObjectVsBroadPhaseLayerFilter {
|
||||||
|
public:
|
||||||
|
bool ShouldCollide(JPH::ObjectLayer, JPH::BroadPhaseLayer) const override {
|
||||||
|
return true; // MVP 全放行
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class ObjectLayerPairFilterImpl final : public JPH::ObjectLayerPairFilter {
|
||||||
|
public:
|
||||||
|
bool ShouldCollide(JPH::ObjectLayer a, JPH::ObjectLayer b) const override {
|
||||||
|
switch (a) {
|
||||||
|
case LAYER_NON_MOVING: return b == LAYER_MOVING;
|
||||||
|
case LAYER_MOVING: return true; // 动体与静/动都碰
|
||||||
|
default: return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 引擎 Compose() 的旋转合成是 Ry·Rx·Rz(YXZ 序,见 Components.h)——
|
||||||
|
// 物理写回必须按同约定分解欧拉角,否则体素会随写回帧帧漂移。
|
||||||
|
// R = Ry(y)·Rx(x)·Rz(z) 的矩阵展开(0 基):
|
||||||
|
// M12 = -sx → x = asin(-M12);否则 y = atan2(M02, M22),z = atan2(M10, M11)
|
||||||
|
glm::vec3 QuatToEulerYXZ(const JPH::Quat &q) {
|
||||||
|
const glm::quat gq(q.GetW(), q.GetX(), q.GetY(), q.GetZ());
|
||||||
|
const glm::mat3 m = glm::mat3(gq); // glm 列主序:m[col][row]
|
||||||
|
// 数学记法 M[row][col]:M12 = m[2][1],M02 = m[2][0],M10 = m[0][1] …
|
||||||
|
const f32 m12 = m[2][1];
|
||||||
|
const f32 m02 = m[2][0];
|
||||||
|
const f32 m22 = m[2][2];
|
||||||
|
const f32 m10 = m[0][1];
|
||||||
|
const f32 m11 = m[1][1];
|
||||||
|
const f32 m01 = m[1][0];
|
||||||
|
|
||||||
|
const f32 sinX = std::clamp(-m12, -1.0f, 1.0f);
|
||||||
|
const f32 x = std::asin(sinX);
|
||||||
|
if (std::abs(sinX) < 0.9999f) {
|
||||||
|
return {
|
||||||
|
glm::degrees(x),
|
||||||
|
glm::degrees(std::atan2(m02, m22)),
|
||||||
|
glm::degrees(std::atan2(m10, m11))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// 万向锁(x=±90°):z 并入 y,取 z=0,y = atan2(M01, M02)
|
||||||
|
return {
|
||||||
|
glm::degrees(x),
|
||||||
|
glm::degrees(std::atan2(m01, m02)),
|
||||||
|
0.0f
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 与 QuatToEulerYXZ 互逆:q = qy(y)·qx(x)·qz(z) ≡ Ry·Rx·Rz
|
||||||
|
//(不能用 glm::quat(euler) ctor——那是 ZYX 航向序,语义不同)
|
||||||
|
JPH::Quat EulerYXZToQuat(const glm::vec3 &eulerDeg) {
|
||||||
|
const glm::quat qx = glm::angleAxis(glm::radians(eulerDeg.x), glm::vec3(1, 0, 0));
|
||||||
|
const glm::quat qy = glm::angleAxis(glm::radians(eulerDeg.y), glm::vec3(0, 1, 0));
|
||||||
|
const glm::quat qz = glm::angleAxis(glm::radians(eulerDeg.z), glm::vec3(0, 0, 1));
|
||||||
|
const glm::quat q = qy * qx * qz;
|
||||||
|
return {q.x, q.y, q.z, q.w};
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
// ---- 碰撞事件监听:回调在 Update 内多线程并发,只读 body、加锁入队 ----
|
||||||
|
|
||||||
|
class PhysicsWorld::ContactListenerImpl final : public JPH::ContactListener {
|
||||||
|
public:
|
||||||
|
explicit ContactListenerImpl(PhysicsWorld &owner) : m_Owner(owner) {}
|
||||||
|
|
||||||
|
void OnContactAdded(const JPH::Body &b1, const JPH::Body &b2,
|
||||||
|
const JPH::ContactManifold &manifold,
|
||||||
|
JPH::ContactSettings &) override {
|
||||||
|
Record(CollisionEvent::Kind::Added, b1, b2, manifold);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnContactPersisted(const JPH::Body &b1, const JPH::Body &b2,
|
||||||
|
const JPH::ContactManifold &manifold,
|
||||||
|
JPH::ContactSettings &) override {
|
||||||
|
Record(CollisionEvent::Kind::Persisted, b1, b2, manifold);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnContactRemoved(const JPH::SubShapeIDPair &pair) override {
|
||||||
|
// body 可能已销毁(GetUserData 不可用)→ BodyID 反查实体;
|
||||||
|
// 主线程阻塞在 Update 内,m_Bodies 此刻稳定
|
||||||
|
u32 a = 0, b = 0;
|
||||||
|
if (!m_Owner.TryBitsByBodyId(pair.GetBody1ID(), a) ||
|
||||||
|
!m_Owner.TryBitsByBodyId(pair.GetBody2ID(), b)) {
|
||||||
|
return; // 一端已随实体移除,事件无意义
|
||||||
|
}
|
||||||
|
m_Owner.QueueEvent({a, b, CollisionEvent::Kind::Removed, {}, {}});
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void Record(CollisionEvent::Kind kind, const JPH::Body &b1,
|
||||||
|
const JPH::Body &b2, const JPH::ContactManifold &manifold) {
|
||||||
|
CollisionEvent ev;
|
||||||
|
ev.kind = kind;
|
||||||
|
ev.a = static_cast<u32>(b1.GetUserData());
|
||||||
|
ev.b = static_cast<u32>(b2.GetUserData());
|
||||||
|
if (!manifold.mRelativeContactPointsOn1.empty()) {
|
||||||
|
const JPH::RVec3 p = manifold.GetWorldSpaceContactPointOn1(0);
|
||||||
|
ev.point = {p.GetX(), p.GetY(), p.GetZ()};
|
||||||
|
}
|
||||||
|
const JPH::Vec3 n = manifold.mWorldSpaceNormal;
|
||||||
|
ev.normal = {n.GetX(), n.GetY(), n.GetZ()};
|
||||||
|
m_Owner.QueueEvent(std::move(ev));
|
||||||
|
}
|
||||||
|
|
||||||
|
PhysicsWorld &m_Owner;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---- 生命周期 ----
|
||||||
|
|
||||||
|
PhysicsWorld::PhysicsWorld(ECS::Scene &scene, Assets::AssetManager *assets)
|
||||||
|
: m_Scene(scene), m_Assets(assets) {
|
||||||
|
static std::once_flag s_JoltOnce;
|
||||||
|
std::call_once(s_JoltOnce, [] {
|
||||||
|
JPH::RegisterDefaultAllocator();
|
||||||
|
JPH::Factory::sInstance = new JPH::Factory();
|
||||||
|
JPH::RegisterTypes();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 调试收集器必须在分配器注册之后构造:DebugRendererSimple 构造即
|
||||||
|
// Initialize() 预烘焙通用几何(STLAllocator 走 JPH 函数指针),若作
|
||||||
|
// 直接成员会先于 call_once 构造 → 函数指针未注册 → SIGSEGV at 0x0
|
||||||
|
m_DebugDraw = CreateScope<PhysicsDebugDraw>();
|
||||||
|
|
||||||
|
m_TempAllocator = CreateScope<JPH::TempAllocatorImpl>(16 * 1024 * 1024);
|
||||||
|
const u32 threads = std::max(1u, std::thread::hardware_concurrency() - 1);
|
||||||
|
m_JobSystem = CreateScope<JPH::JobSystemThreadPool>(
|
||||||
|
JPH::cMaxPhysicsJobs, JPH::cMaxPhysicsBarriers, static_cast<int>(threads));
|
||||||
|
|
||||||
|
m_BPLayerInterface = CreateScope<BPLayerInterfaceImpl>();
|
||||||
|
m_ObjectVsBPLayerFilter = CreateScope<ObjectVsBPLayerFilterImpl>();
|
||||||
|
m_ObjectVsObjectLayerFilter = CreateScope<ObjectLayerPairFilterImpl>();
|
||||||
|
|
||||||
|
m_System = CreateScope<JPH::PhysicsSystem>();
|
||||||
|
m_System->Init(4096, 0, 16384, 4096,
|
||||||
|
*m_BPLayerInterface, *m_ObjectVsBPLayerFilter,
|
||||||
|
*m_ObjectVsObjectLayerFilter);
|
||||||
|
m_System->SetGravity(JPH::Vec3(0.0f, -9.81f, 0.0f));
|
||||||
|
|
||||||
|
// 碰撞事件监听(ctor 不走 JPH 分配器,仍保持"分配器后构造"纪律)
|
||||||
|
m_ContactListener = CreateScope<ContactListenerImpl>(*this);
|
||||||
|
m_System->SetContactListener(m_ContactListener.get());
|
||||||
|
|
||||||
|
#ifdef JPH_DEBUG_RENDERER
|
||||||
|
// 调试收集器安装为 Jolt 全局渲染后端(DrawBodies/约束/软体全走这里)
|
||||||
|
JPH::DebugRenderer::sInstance = m_DebugDraw.get();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ENGINE_INFO("PhysicsWorld initialized (Jolt {} threads)", threads);
|
||||||
|
}
|
||||||
|
|
||||||
|
PhysicsWorld::~PhysicsWorld() {
|
||||||
|
// 先析构调试收集器:~DebugRenderer 会断言 sInstance==this 并清空、
|
||||||
|
// 释放烘焙几何——必须先于 UnregisterTypes
|
||||||
|
m_DebugDraw.reset();
|
||||||
|
m_Bodies.clear();
|
||||||
|
m_ContactListener.reset(); // 监听器先于 system 析构(持 m_Owner 引用)
|
||||||
|
m_System.reset();
|
||||||
|
// 单世界假设:进程退出时统一反注册(多世界时改为引用计数)
|
||||||
|
JPH::UnregisterTypes();
|
||||||
|
delete JPH::Factory::sInstance;
|
||||||
|
JPH::Factory::sInstance = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 步进:定步长 accumulator ----
|
||||||
|
|
||||||
|
void PhysicsWorld::Step(f32 dt) {
|
||||||
|
if (!m_Enabled) {
|
||||||
|
m_Accumulator = 0.0f;
|
||||||
|
m_DebugDraw->Frame().Clear(); // 物理关:清残留图元(防旧帧闪烁)
|
||||||
|
m_PendingEvents.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 事件队列每渲染帧清一次 → GetEvents 语义 = 最近一次步进的事件
|
||||||
|
m_PendingEvents.clear();
|
||||||
|
SyncScene(); // 每帧对账(编辑器改参数/加删实体即时生效)
|
||||||
|
|
||||||
|
constexpr f32 kFixedDt = 1.0f / 60.0f;
|
||||||
|
constexpr int kMaxSubSteps = 4;
|
||||||
|
|
||||||
|
m_Accumulator += dt;
|
||||||
|
int steps = static_cast<int>(m_Accumulator / kFixedDt);
|
||||||
|
if (steps > 0) {
|
||||||
|
steps = std::min(steps, kMaxSubSteps);
|
||||||
|
m_Accumulator -= static_cast<f32>(steps) * kFixedDt;
|
||||||
|
// 丢弃多余积压(卡顿后不追帧,防螺旋死亡)
|
||||||
|
m_Accumulator = std::min(m_Accumulator, kFixedDt);
|
||||||
|
|
||||||
|
m_System->Update(kFixedDt, steps, m_TempAllocator.get(), m_JobSystem.get());
|
||||||
|
Writeback();
|
||||||
|
UpdateAccels(kFixedDt); // 速度差分 → 加速度(step 率采样一次)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调试收集按渲染帧率跑(无步进帧也重画,箭头不闪);全关时清空
|
||||||
|
if (m_DebugBits != 0) {
|
||||||
|
CollectDebugData();
|
||||||
|
} else {
|
||||||
|
m_DebugDraw->Frame().Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 物理调试可视化 ----
|
||||||
|
|
||||||
|
void PhysicsWorld::UpdateAccels(f32 dt) {
|
||||||
|
auto &bi = m_System->GetBodyInterface();
|
||||||
|
for (auto &[bits, rec]: m_Bodies) {
|
||||||
|
if (rec.type != static_cast<u8>(ECS::RigidBodyComponent::BodyType::Dynamic)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const JPH::Vec3 v = bi.GetLinearVelocity(rec.id);
|
||||||
|
const glm::vec3 gv(v.GetX(), v.GetY(), v.GetZ());
|
||||||
|
const glm::vec3 instAccel = (gv - rec.prevVel) / dt;
|
||||||
|
// 轻度 EMA:积分噪声被滤掉,碰撞冲量的单步尖峰仍然保留
|
||||||
|
rec.lastAccel = glm::mix(rec.lastAccel, instAccel, 0.35f);
|
||||||
|
rec.prevVel = gv;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PhysicsWorld::CollectDebugData() {
|
||||||
|
m_DebugDraw->BeginCollect();
|
||||||
|
|
||||||
|
// 1) Jolt 原生:collider 线框 + 速度箭头(软体 mDrawSoftBody* 后续加这里)
|
||||||
|
if ((m_DebugBits & (kPhyDbgColliders | kPhyDbgVelocity)) != 0) {
|
||||||
|
JPH::BodyManager::DrawSettings ds;
|
||||||
|
ds.mDrawShape = (m_DebugBits & kPhyDbgColliders) != 0;
|
||||||
|
ds.mDrawShapeWireframe = true;
|
||||||
|
ds.mDrawVelocity = (m_DebugBits & kPhyDbgVelocity) != 0;
|
||||||
|
m_System->DrawBodies(ds, m_DebugDraw.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) 约束(关节;软体/载具约束接入后自动生效)
|
||||||
|
if ((m_DebugBits & kPhyDbgConstraints) != 0) {
|
||||||
|
m_System->DrawConstraints(m_DebugDraw.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) 手绘箭头(Jolt 无现成 API):重力 = 常量方向指示(长度固定),
|
||||||
|
// 加速度 = 速度差分(方向/长度随值,静止噪声阈值截断)
|
||||||
|
auto &bi = m_System->GetBodyInterface();
|
||||||
|
const JPH::Vec3 gravity = m_System->GetGravity();
|
||||||
|
constexpr f32 kGravityLen = 1.0f; // 世界单位
|
||||||
|
constexpr f32 kAccelScale = 0.05f;
|
||||||
|
constexpr f32 kAccelMaxLen = 2.0f;
|
||||||
|
const JPH::Color kGravityColor(220, 60, 60); // 红:重力
|
||||||
|
const JPH::Color kAccelColor(255, 170, 40); // 橙:加速度
|
||||||
|
|
||||||
|
for (auto &[bits, rec]: m_Bodies) {
|
||||||
|
if (rec.type != static_cast<u8>(ECS::RigidBodyComponent::BodyType::Dynamic)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const JPH::RVec3 com = bi.GetCenterOfMassPosition(rec.id);
|
||||||
|
|
||||||
|
if ((m_DebugBits & kPhyDbgGravity) != 0) {
|
||||||
|
const JPH::Vec3 g = gravity * bi.GetGravityFactor(rec.id);
|
||||||
|
const f32 len = g.Length();
|
||||||
|
if (len > 1e-4f) {
|
||||||
|
const JPH::RVec3 tip = com + JPH::RVec3(g * (kGravityLen / len));
|
||||||
|
m_DebugDraw->DrawArrow(com, tip, kGravityColor, kGravityLen * 0.12f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((m_DebugBits & kPhyDbgAccel) != 0) {
|
||||||
|
const f32 mag = glm::length(rec.lastAccel);
|
||||||
|
if (mag > 0.05f) {
|
||||||
|
const glm::vec3 dir = rec.lastAccel / mag;
|
||||||
|
const f32 len = std::min(mag * kAccelScale, kAccelMaxLen);
|
||||||
|
const JPH::RVec3 tip =
|
||||||
|
com + JPH::RVec3(dir.x, dir.y, dir.z) * len;
|
||||||
|
m_DebugDraw->DrawArrow(
|
||||||
|
com, tip, kAccelColor, std::max(len * 0.15f, 0.05f));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 释放上一帧缓存不再引用的通用几何 batch(DebugRenderer 内部池)
|
||||||
|
m_DebugDraw->NextFrame();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 对账 ----
|
||||||
|
|
||||||
|
void PhysicsWorld::SyncScene() {
|
||||||
|
auto ® = m_Scene.GetRegistry();
|
||||||
|
auto &bi = m_System->GetBodyInterface();
|
||||||
|
|
||||||
|
// 1. 孤儿清理:实体销毁 / 组件被移除 → 移除 body
|
||||||
|
for (auto it = m_Bodies.begin(); it != m_Bodies.end();) {
|
||||||
|
const auto e = static_cast<entt::entity>(it->first);
|
||||||
|
if (!reg.valid(e) ||
|
||||||
|
!reg.all_of<ECS::RigidBodyComponent, ECS::TransformComponent>(e)) {
|
||||||
|
bi.RemoveBody(it->second.id);
|
||||||
|
bi.DestroyBody(it->second.id);
|
||||||
|
it = m_Bodies.erase(it);
|
||||||
|
} else {
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 遍历:缺失 → 创建;参数变更 → 重建;静态/运动学拖动 → 同步进 body
|
||||||
|
auto view = reg.view<ECS::RigidBodyComponent, ECS::TransformComponent>();
|
||||||
|
for (auto [e, rb, tr]: view.each()) {
|
||||||
|
const u32 bits = static_cast<u32>(entt::to_integral(e));
|
||||||
|
const auto found = m_Bodies.find(bits);
|
||||||
|
if (found == m_Bodies.end() || !rb.bodyValid) {
|
||||||
|
if (found != m_Bodies.end()) {
|
||||||
|
bi.RemoveBody(found->second.id);
|
||||||
|
bi.DestroyBody(found->second.id);
|
||||||
|
m_Bodies.erase(found);
|
||||||
|
}
|
||||||
|
CreateBody(bits, rb, tr);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto &rec = found->second; // 非 const:静态/运动体拖动时更新 lastPos/lastRot
|
||||||
|
if (rec.type != static_cast<u8>(rb.type) ||
|
||||||
|
rec.collider != static_cast<u8>(rb.collider) ||
|
||||||
|
rec.colliderSize != rb.boxHalfExtents ||
|
||||||
|
rec.radius != rb.sphereRadius ||
|
||||||
|
rec.mass != rb.mass ||
|
||||||
|
rec.isSensor != rb.isSensor) {
|
||||||
|
// 类型/碰撞体/质量变更 → 重建(简单正确,重置速度可接受)
|
||||||
|
bi.RemoveBody(rec.id);
|
||||||
|
bi.DestroyBody(rec.id);
|
||||||
|
m_Bodies.erase(found);
|
||||||
|
rb.bodyValid = false;
|
||||||
|
CreateBody(bits, rb, tr);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const JPH::BodyID id(rec.id);
|
||||||
|
bi.SetFriction(id, rb.friction);
|
||||||
|
bi.SetRestitution(id, rb.restitution);
|
||||||
|
if (rb.type == ECS::RigidBodyComponent::BodyType::Dynamic) {
|
||||||
|
bi.SetGravityFactor(id, rb.gravityScale);
|
||||||
|
} else if (rec.lastPos != tr.GetPosition() ||
|
||||||
|
rec.lastRot != tr.GetRotation()) {
|
||||||
|
// 编辑器拖动静态/运动学体 → 传送 body
|
||||||
|
bi.SetPositionAndRotation(id,
|
||||||
|
JPH::RVec3(tr.GetPosition().x, tr.GetPosition().y, tr.GetPosition().z),
|
||||||
|
EulerYXZToQuat(tr.GetRotation()),
|
||||||
|
JPH::EActivation::DontActivate);
|
||||||
|
rec.lastPos = tr.GetPosition();
|
||||||
|
rec.lastRot = tr.GetRotation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Mesh 碰撞体:视觉网格 → Jolt 形状 ----
|
||||||
|
|
||||||
|
JPH::RefConst<JPH::Shape> PhysicsWorld::BuildMeshShape(
|
||||||
|
u32 entityBits, ECS::RigidBodyComponent &rb, ECS::TransformComponent &tr) {
|
||||||
|
// 同实体 MeshRenderer 提供网格;缺失 → 回退(CreateBody 判 null 走 Box)
|
||||||
|
auto *mc = m_Scene.GetRegistry().try_get<ECS::MeshRendererComponent>(
|
||||||
|
static_cast<entt::entity>(entityBits));
|
||||||
|
if (m_Assets == nullptr || mc == nullptr) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
const Model *model = m_Assets->GetModel(mc->model);
|
||||||
|
if (model == nullptr) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 缩放烘进顶点:Jolt shape 无缩放语义,body 只吃 pos/rot
|
||||||
|
const glm::vec3 s = tr.GetScale();
|
||||||
|
|
||||||
|
// 动态/运动体 → ConvexHull(MeshShape 三角网格太贵,Jolt 不允许动)
|
||||||
|
if (rb.type != ECS::RigidBodyComponent::BodyType::Static) {
|
||||||
|
JPH::ConvexHullShapeSettings cs;
|
||||||
|
for (const auto &mesh: model->GetMeshes()) {
|
||||||
|
for (const auto &p: mesh->GetCpuPositions()) {
|
||||||
|
cs.mPoints.emplace_back(p.x * s.x, p.y * s.y, p.z * s.z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cs.mPoints.size() < 4) {
|
||||||
|
return nullptr; // 凸包至少 4 点
|
||||||
|
}
|
||||||
|
const JPH::Shape::ShapeResult res = cs.Create();
|
||||||
|
if (res.HasError()) {
|
||||||
|
ENGINE_WARN("[Physics] ConvexHull build failed: {}",
|
||||||
|
res.GetError().c_str());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return res.Get();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 静态体 → MeshShape 精确三角网格(各 mesh 顶点拼接,索引加基址偏移)
|
||||||
|
JPH::VertexList verts;
|
||||||
|
JPH::IndexedTriangleList tris;
|
||||||
|
u32 base = 0;
|
||||||
|
for (const auto &mesh: model->GetMeshes()) {
|
||||||
|
const auto &pos = mesh->GetCpuPositions();
|
||||||
|
const auto &idx = mesh->GetCpuIndices();
|
||||||
|
verts.reserve(verts.size() + pos.size());
|
||||||
|
for (const auto &p: pos) {
|
||||||
|
verts.push_back(JPH::Float3(p.x * s.x, p.y * s.y, p.z * s.z));
|
||||||
|
}
|
||||||
|
tris.reserve(tris.size() + idx.size() / 3);
|
||||||
|
for (size_t i = 0; i + 2 < idx.size(); i += 3) {
|
||||||
|
// 退化三角(两索引相同)剔除:MeshShape 构建会因退化报错
|
||||||
|
if (idx[i] != idx[i + 1] && idx[i] != idx[i + 2] &&
|
||||||
|
idx[i + 1] != idx[i + 2]) {
|
||||||
|
tris.emplace_back(idx[i], idx[i + 1], idx[i + 2],
|
||||||
|
static_cast<u32>(0), base);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
base += static_cast<u32>(pos.size());
|
||||||
|
}
|
||||||
|
if (tris.empty()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
JPH::MeshShapeSettings ms(std::move(verts), std::move(tris));
|
||||||
|
const JPH::Shape::ShapeResult res = ms.Create();
|
||||||
|
if (res.HasError()) {
|
||||||
|
ENGINE_WARN("[Physics] MeshShape build failed: {}",
|
||||||
|
res.GetError().c_str());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return res.Get();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PhysicsWorld::CreateBody(u32 entityBits, ECS::RigidBodyComponent &rb,
|
||||||
|
ECS::TransformComponent &tr) {
|
||||||
|
auto &bi = m_System->GetBodyInterface();
|
||||||
|
|
||||||
|
// 碰撞体:组件显式尺寸 × Transform 缩放(形状按单位尺度烹饪 +
|
||||||
|
// ScaledShape 的优化留给 ShapeCache 阶段;MVP 直接带缩放建形状)
|
||||||
|
JPH::RefConst<JPH::Shape> shape;
|
||||||
|
if (rb.collider == ECS::RigidBodyComponent::ColliderType::Sphere) {
|
||||||
|
const f32 scale = std::max({tr.GetScale().x, tr.GetScale().y, tr.GetScale().z});
|
||||||
|
shape = new JPH::SphereShape(std::max(rb.sphereRadius * scale, 0.01f));
|
||||||
|
} else if (rb.collider == ECS::RigidBodyComponent::ColliderType::Mesh) {
|
||||||
|
// Mesh 碰撞体:实体 MeshRenderer 的视觉网格(Unity MeshCollider
|
||||||
|
// 语义)。缩放烘进顶点(Jolt shape 无缩放);Static → MeshShape
|
||||||
|
// 精确三角网格,Dynamic/Kinematic → ConvexHull(MeshShape 太贵
|
||||||
|
// 不能动)。无 MeshRenderer/空网格回退 Box 并告警
|
||||||
|
shape = BuildMeshShape(entityBits, rb, tr);
|
||||||
|
if (shape == nullptr) {
|
||||||
|
shape = new JPH::BoxShape(JPH::Vec3(
|
||||||
|
std::max(rb.boxHalfExtents.x * tr.GetScale().x, 0.01f),
|
||||||
|
std::max(rb.boxHalfExtents.y * tr.GetScale().y, 0.01f),
|
||||||
|
std::max(rb.boxHalfExtents.z * tr.GetScale().z, 0.01f)));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
shape = new JPH::BoxShape(JPH::Vec3(
|
||||||
|
std::max(rb.boxHalfExtents.x * tr.GetScale().x, 0.01f),
|
||||||
|
std::max(rb.boxHalfExtents.y * tr.GetScale().y, 0.01f),
|
||||||
|
std::max(rb.boxHalfExtents.z * tr.GetScale().z, 0.01f)));
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool dynamic = rb.type == ECS::RigidBodyComponent::BodyType::Dynamic;
|
||||||
|
const bool kinematic = rb.type == ECS::RigidBodyComponent::BodyType::Kinematic;
|
||||||
|
JPH::BodyCreationSettings settings(
|
||||||
|
shape,
|
||||||
|
JPH::RVec3(tr.GetPosition().x, tr.GetPosition().y, tr.GetPosition().z),
|
||||||
|
EulerYXZToQuat(tr.GetRotation()),
|
||||||
|
dynamic
|
||||||
|
? JPH::EMotionType::Dynamic
|
||||||
|
: (kinematic
|
||||||
|
? JPH::EMotionType::Kinematic
|
||||||
|
: JPH::EMotionType::Static),
|
||||||
|
(dynamic || kinematic) ? LAYER_MOVING : LAYER_NON_MOVING);
|
||||||
|
|
||||||
|
if (dynamic) {
|
||||||
|
settings.mAllowSleeping = true;
|
||||||
|
settings.mGravityFactor = rb.gravityScale;
|
||||||
|
settings.mLinearDamping = 0.05f;
|
||||||
|
settings.mAngularDamping = 0.05f;
|
||||||
|
settings.mOverrideMassProperties = JPH::EOverrideMassProperties::CalculateInertia;
|
||||||
|
settings.mMassPropertiesOverride.mMass = std::max(rb.mass, 1e-3f);
|
||||||
|
}
|
||||||
|
settings.mFriction = rb.friction;
|
||||||
|
settings.mRestitution = rb.restitution;
|
||||||
|
settings.mUserData = entityBits; // 事件回调:Body → 实体位模式反查
|
||||||
|
settings.mIsSensor = rb.isSensor; // 传感器:只收 Added/Removed,不解算
|
||||||
|
|
||||||
|
const JPH::BodyID id = bi.CreateAndAddBody(
|
||||||
|
settings, dynamic ? JPH::EActivation::Activate : JPH::EActivation::DontActivate);
|
||||||
|
|
||||||
|
BodyRecord rec;
|
||||||
|
rec.id = id;
|
||||||
|
rec.type = static_cast<u8>(rb.type);
|
||||||
|
rec.collider = static_cast<u8>(rb.collider);
|
||||||
|
rec.colliderSize = rb.boxHalfExtents;
|
||||||
|
rec.radius = rb.sphereRadius;
|
||||||
|
rec.mass = rb.mass;
|
||||||
|
rec.isSensor = rb.isSensor;
|
||||||
|
rec.lastPos = tr.GetPosition();
|
||||||
|
rec.lastRot = tr.GetRotation();
|
||||||
|
m_Bodies[entityBits] = rec;
|
||||||
|
|
||||||
|
rb.bodyId = id.GetIndexAndSequenceNumber();
|
||||||
|
rb.bodyValid = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PhysicsWorld::RemoveBody(const u32 entityBits) {
|
||||||
|
const auto found = m_Bodies.find(entityBits);
|
||||||
|
if (found == m_Bodies.end()) return;
|
||||||
|
auto &bi = m_System->GetBodyInterface();
|
||||||
|
bi.RemoveBody(found->second.id);
|
||||||
|
bi.DestroyBody(found->second.id);
|
||||||
|
m_Bodies.erase(found);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 碰撞事件队列 ----
|
||||||
|
|
||||||
|
void PhysicsWorld::QueueEvent(CollisionEvent &&ev) {
|
||||||
|
std::lock_guard lock(m_EventMutex);
|
||||||
|
m_PendingEvents.push_back(std::move(ev));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PhysicsWorld::TryBitsByBodyId(const JPH::BodyID &id, u32 &outBits) const {
|
||||||
|
for (const auto &[bits, rec]: m_Bodies) {
|
||||||
|
if (rec.id == id) {
|
||||||
|
outBits = bits;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 射线查询 ----
|
||||||
|
|
||||||
|
bool PhysicsWorld::RayCast(const glm::vec3 &origin, const glm::vec3 &dir,
|
||||||
|
const f32 maxDist, RayHit &outHit) const {
|
||||||
|
const f32 len = glm::length(dir);
|
||||||
|
if (len < 1e-8f) return false;
|
||||||
|
|
||||||
|
// RRayCast 的 direction 长度 = 射线长度(超出部分不报告命中)
|
||||||
|
const JPH::RRayCast ray(
|
||||||
|
JPH::RVec3(origin.x, origin.y, origin.z),
|
||||||
|
JPH::Vec3(dir.x / len, dir.y / len, dir.z / len) * maxDist);
|
||||||
|
|
||||||
|
JPH::RayCastResult hit;
|
||||||
|
if (!m_System->GetNarrowPhaseQuery().CastRay(ray, hit)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 法线需读 body(locking 接口,主线程安全);body 已销毁则丢弃
|
||||||
|
const JPH::RVec3 point = ray.GetPointOnRay(hit.mFraction);
|
||||||
|
JPH::BodyLockRead lock(m_System->GetBodyLockInterface(), hit.mBodyID);
|
||||||
|
if (!lock.Succeeded()) return false;
|
||||||
|
const JPH::Vec3 n =
|
||||||
|
lock.GetBody().GetWorldSpaceSurfaceNormal(hit.mSubShapeID2, point);
|
||||||
|
|
||||||
|
u32 bits = 0;
|
||||||
|
if (!TryBitsByBodyId(hit.mBodyID, bits)) return false;
|
||||||
|
|
||||||
|
outHit.entityBits = bits;
|
||||||
|
outHit.point = {point.GetX(), point.GetY(), point.GetZ()};
|
||||||
|
outHit.normal = {n.GetX(), n.GetY(), n.GetZ()};
|
||||||
|
outHit.distance = hit.mFraction * maxDist;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 写回:动态体姿态 → Transform(标脏,帧循环统一 Compose)----
|
||||||
|
|
||||||
|
void PhysicsWorld::Writeback() const {
|
||||||
|
auto ® = m_Scene.GetRegistry();
|
||||||
|
const auto &bi = m_System->GetBodyInterface();
|
||||||
|
|
||||||
|
const auto view = reg.view<ECS::RigidBodyComponent, ECS::TransformComponent>();
|
||||||
|
for (auto [e, rb, tr]: view.each()) {
|
||||||
|
if (!rb.bodyValid ||
|
||||||
|
rb.type != ECS::RigidBodyComponent::BodyType::Dynamic) {
|
||||||
|
continue; // 静态/运动学体的 Transform 由编辑器驱动
|
||||||
|
}
|
||||||
|
const JPH::BodyID id(rb.bodyId);
|
||||||
|
const JPH::RVec3 pos = bi.GetPosition(id);
|
||||||
|
const JPH::Quat rot = bi.GetRotation(id);
|
||||||
|
tr.SetPosition(glm::vec3(pos.GetX(), pos.GetY(), pos.GetZ()));
|
||||||
|
tr.SetRotation(QuatToEulerYXZ(rot));
|
||||||
|
|
||||||
|
const JPH::Vec3 vel = bi.GetLinearVelocity(id);
|
||||||
|
rb.velocity = glm::vec3(vel.GetX(), vel.GetY(), vel.GetZ());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace Engine
|
||||||
@@ -35,6 +35,14 @@ namespace Engine
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CPU 位置副本:物理 MeshShape/ConvexHull 数据源(GPU 上传后
|
||||||
|
// 完整 Vertex 不再保留,见 Mesh.h)
|
||||||
|
m_CpuPositions.reserve(vertices.size());
|
||||||
|
for (const auto& v : vertices) {
|
||||||
|
m_CpuPositions.push_back(v.position);
|
||||||
|
}
|
||||||
|
m_CpuIndices = lods[0];
|
||||||
|
|
||||||
u64 vbSize = sizeof(Vertex) * vertices.size();
|
u64 vbSize = sizeof(Vertex) * vertices.size();
|
||||||
m_VertexBuffer = CreateScope<Buffer>(
|
m_VertexBuffer = CreateScope<Buffer>(
|
||||||
device, allocator, vbSize,
|
device, allocator, vbSize,
|
||||||
|
|||||||
@@ -0,0 +1,250 @@
|
|||||||
|
//
|
||||||
|
// PhysicsDebugPass.cpp:pass 装配(shader object + ring VB 上传)与录制。
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <Engine/Renderer/Passes/PhysicsDebugPass.h>
|
||||||
|
#include <Engine/Physics/PhysicsDebugTypes.h>
|
||||||
|
#include <Engine/Vulkan/Device.h>
|
||||||
|
#include <Engine/Vulkan/DescriptorManager.h>
|
||||||
|
#include <Engine/Vulkan/ShaderManager.h>
|
||||||
|
#include <Engine/Core/Log.h>
|
||||||
|
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
|
||||||
|
#include <tracy/Tracy.hpp>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstddef>
|
||||||
|
|
||||||
|
namespace Engine::Passes
|
||||||
|
{
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
// 顶点布局:location0 = pos(vec3),location1 = color(vec4)
|
||||||
|
//(setVertexInputEXT 走 2EXT 结构,与 Vertex.h 同一套)
|
||||||
|
vk::VertexInputBindingDescription2EXT MakeBinding()
|
||||||
|
{
|
||||||
|
vk::VertexInputBindingDescription2EXT b;
|
||||||
|
b.setBinding(0)
|
||||||
|
.setStride(sizeof(PhysicsDebugVertex))
|
||||||
|
.setInputRate(vk::VertexInputRate::eVertex)
|
||||||
|
.setDivisor(1); // 0 需 zeroDivisor feature(VUID-04798)
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<vk::VertexInputAttributeDescription2EXT, 2> MakeAttributes()
|
||||||
|
{
|
||||||
|
std::array<vk::VertexInputAttributeDescription2EXT, 2> a;
|
||||||
|
a[0].setLocation(0).setBinding(0)
|
||||||
|
.setFormat(vk::Format::eR32G32B32Sfloat)
|
||||||
|
.setOffset(offsetof(PhysicsDebugVertex, pos));
|
||||||
|
a[1].setLocation(1).setBinding(0)
|
||||||
|
.setFormat(vk::Format::eR32G32B32A32Sfloat)
|
||||||
|
.setOffset(offsetof(PhysicsDebugVertex, color));
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 直写方程:(ONE, ZERO) → dst = src,数学上等价于无混合
|
||||||
|
vk::ColorBlendEquationEXT MakeDirectEquation()
|
||||||
|
{
|
||||||
|
vk::ColorBlendEquationEXT eq;
|
||||||
|
eq.setSrcColorBlendFactor(vk::BlendFactor::eOne)
|
||||||
|
.setDstColorBlendFactor(vk::BlendFactor::eZero)
|
||||||
|
.setColorBlendOp(vk::BlendOp::eAdd)
|
||||||
|
.setSrcAlphaBlendFactor(vk::BlendFactor::eOne)
|
||||||
|
.setDstAlphaBlendFactor(vk::BlendFactor::eZero)
|
||||||
|
.setAlphaBlendOp(vk::BlendOp::eAdd);
|
||||||
|
return eq;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
PhysicsDebugPass::PhysicsDebugPass(Device& device, MemoryAllocator& /*allocator*/,
|
||||||
|
ShaderManager& shaders,
|
||||||
|
DescriptorManager& descriptors,
|
||||||
|
FrameSceneData& frame,
|
||||||
|
RG::TextureHandle hdrColor,
|
||||||
|
RG::TextureHandle depth,
|
||||||
|
UniformBuffer& lineVB, UniformBuffer& triVB)
|
||||||
|
: m_Device(device), m_Descriptors(descriptors), m_Frame(frame),
|
||||||
|
m_HDRColor(hdrColor), m_Depth(depth), m_LineVB(lineVB), m_TriVB(triVB)
|
||||||
|
{
|
||||||
|
// 无描述符 set:viewProj 走 push constant(shader 侧声明一致)
|
||||||
|
m_Layout = descriptors.CreatePipelineLayout(
|
||||||
|
std::vector<vk::DescriptorSetLayout>{}, sizeof(glm::mat4));
|
||||||
|
|
||||||
|
const std::string shaderDir = "shaders/";
|
||||||
|
auto vert = shaders.CreateFromFile(
|
||||||
|
shaderDir + "debug_line.vert.spv",
|
||||||
|
vk::ShaderStageFlagBits::eVertex,
|
||||||
|
std::vector<vk::DescriptorSetLayout>{},
|
||||||
|
sizeof(glm::mat4));
|
||||||
|
auto frag = shaders.CreateFromFile(
|
||||||
|
shaderDir + "debug_line.frag.spv",
|
||||||
|
vk::ShaderStageFlagBits::eFragment,
|
||||||
|
std::vector<vk::DescriptorSetLayout>{},
|
||||||
|
sizeof(glm::mat4));
|
||||||
|
|
||||||
|
m_Vert = vert.Handle;
|
||||||
|
m_Frag = frag.Handle;
|
||||||
|
if (!m_Vert || !m_Frag) {
|
||||||
|
ENGINE_FATAL("PhysicsDebugPass: failed to create shader objects");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PhysicsDebugPass::~PhysicsDebugPass()
|
||||||
|
{
|
||||||
|
const auto& vkDevice = m_Device.GetDevice();
|
||||||
|
|
||||||
|
if (m_Vert) vkDevice.destroyShaderEXT(m_Vert);
|
||||||
|
if (m_Frag) vkDevice.destroyShaderEXT(m_Frag);
|
||||||
|
vkDevice.destroyPipelineLayout(m_Layout);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PhysicsDebugPass::PrepareFrame(u32 frameSlot,
|
||||||
|
const PhysicsDebugFrame& src)
|
||||||
|
{
|
||||||
|
// 预算截断:物理调试不应打满(打满说明该收敛可视化了)
|
||||||
|
const u32 lineVerts = std::min<u32>(
|
||||||
|
static_cast<u32>(src.lines.size()), kMaxLineVerts);
|
||||||
|
const u32 triVerts = std::min<u32>(
|
||||||
|
static_cast<u32>(src.triangles.size()), kMaxTriVerts);
|
||||||
|
if ((src.lines.size() > kMaxLineVerts ||
|
||||||
|
src.triangles.size() > kMaxTriVerts) && !m_TruncationWarned) {
|
||||||
|
m_TruncationWarned = true;
|
||||||
|
ENGINE_WARN("PhysicsDebugPass: vertex budget exceeded "
|
||||||
|
"(lines {} / tris {}), clamped", src.lines.size(),
|
||||||
|
src.triangles.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
// fence 已等(Renderer::Render 的 WaitFence 之后调用),
|
||||||
|
// 写-读竞争关闭——与 UBO/SSBO 上传同一窗口语义
|
||||||
|
if (lineVerts > 0) {
|
||||||
|
m_LineVB.Update(frameSlot, src.lines.data(),
|
||||||
|
static_cast<u64>(lineVerts) *
|
||||||
|
sizeof(PhysicsDebugVertex));
|
||||||
|
}
|
||||||
|
if (triVerts > 0) {
|
||||||
|
m_TriVB.Update(frameSlot, src.triangles.data(),
|
||||||
|
static_cast<u64>(triVerts) *
|
||||||
|
sizeof(PhysicsDebugVertex));
|
||||||
|
}
|
||||||
|
m_LineCounts[frameSlot] = lineVerts;
|
||||||
|
m_TriCounts[frameSlot] = triVerts;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PhysicsDebugPass::Register(RG::RenderGraph& rg)
|
||||||
|
{
|
||||||
|
rg.AddPass("PhysicsDebug", [this](RG::RenderGraphContext& ctx) {
|
||||||
|
const u32 lineCount = m_LineCounts[ctx.frame.frameSlot];
|
||||||
|
const u32 triCount = m_TriCounts[ctx.frame.frameSlot];
|
||||||
|
if (lineCount == 0 && triCount == 0) return; // 空帧零开销
|
||||||
|
|
||||||
|
ZoneScopedN("PhysicsDebug");
|
||||||
|
const vk::CommandBuffer cmd = ctx.cmd;
|
||||||
|
|
||||||
|
// HDR:Load 承接 Forward 结果叠加;深度:Load 承接不透明深度
|
||||||
|
// 做测试(只测不写),DontCare——下帧 Forward 重新 Clear
|
||||||
|
vk::RenderingAttachmentInfo color;
|
||||||
|
color.setImageView(ctx.View(m_HDRColor))
|
||||||
|
.setImageLayout(vk::ImageLayout::eColorAttachmentOptimal)
|
||||||
|
.setLoadOp(vk::AttachmentLoadOp::eLoad)
|
||||||
|
.setStoreOp(vk::AttachmentStoreOp::eStore);
|
||||||
|
|
||||||
|
vk::RenderingAttachmentInfo depth;
|
||||||
|
depth.setImageView(ctx.View(m_Depth))
|
||||||
|
.setImageLayout(vk::ImageLayout::eDepthAttachmentOptimal)
|
||||||
|
.setLoadOp(vk::AttachmentLoadOp::eLoad)
|
||||||
|
.setStoreOp(vk::AttachmentStoreOp::eDontCare);
|
||||||
|
|
||||||
|
vk::RenderingInfo renderingInfo;
|
||||||
|
renderingInfo.setRenderArea({{0, 0}, ctx.extent})
|
||||||
|
.setLayerCount(1)
|
||||||
|
.setColorAttachments(color)
|
||||||
|
.setPDepthAttachment(&depth);
|
||||||
|
|
||||||
|
cmd.beginRendering(renderingInfo);
|
||||||
|
|
||||||
|
const std::array<vk::ShaderStageFlagBits, 2> stages = {
|
||||||
|
vk::ShaderStageFlagBits::eVertex,
|
||||||
|
vk::ShaderStageFlagBits::eFragment
|
||||||
|
};
|
||||||
|
const std::array<vk::ShaderEXT, 2> shaders = {m_Vert, m_Frag};
|
||||||
|
cmd.bindShadersEXT(stages, shaders);
|
||||||
|
|
||||||
|
// layout 的 push range 是 vertex|fragment(CreatePipelineLayout 默认),
|
||||||
|
// vkCmdPushConstants 的 stageFlags 必须覆盖 range 全部 stage(VUID-01796)
|
||||||
|
cmd.pushConstants<glm::mat4>(
|
||||||
|
m_Layout,
|
||||||
|
vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment,
|
||||||
|
0, m_Frame.ubo.proj * m_Frame.ubo.view);
|
||||||
|
|
||||||
|
// ---- 动态状态(shader object 无管线对象,全量显式设置)----
|
||||||
|
cmd.setRasterizerDiscardEnable(VK_FALSE);
|
||||||
|
|
||||||
|
cmd.setDepthTestEnable(VK_TRUE);
|
||||||
|
cmd.setDepthCompareOp(vk::CompareOp::eLessOrEqual);
|
||||||
|
cmd.setDepthWriteEnable(VK_FALSE); // 调试图元不污染场景深度
|
||||||
|
cmd.setDepthBoundsTestEnable(VK_FALSE);
|
||||||
|
cmd.setStencilTestEnable(VK_FALSE);
|
||||||
|
cmd.setDepthBiasEnable(VK_FALSE);
|
||||||
|
|
||||||
|
cmd.setPolygonModeEXT(vk::PolygonMode::eFill);
|
||||||
|
cmd.setCullMode(vk::CullModeFlagBits::eNone);
|
||||||
|
cmd.setFrontFace(vk::FrontFace::eCounterClockwise);
|
||||||
|
cmd.setPrimitiveRestartEnable(VK_FALSE);
|
||||||
|
|
||||||
|
const vk::VertexInputBindingDescription2EXT binding = MakeBinding();
|
||||||
|
const std::array<vk::VertexInputAttributeDescription2EXT, 2>
|
||||||
|
attributes = MakeAttributes();
|
||||||
|
cmd.setVertexInputEXT(binding, attributes);
|
||||||
|
|
||||||
|
cmd.setRasterizationSamplesEXT(vk::SampleCountFlagBits::e1);
|
||||||
|
constexpr vk::SampleMask sampleMask = 0xFFFFFFFF;
|
||||||
|
cmd.setSampleMaskEXT(vk::SampleCountFlagBits::e1, sampleMask);
|
||||||
|
cmd.setAlphaToCoverageEnableEXT(VK_FALSE);
|
||||||
|
cmd.setAlphaToOneEnableEXT(VK_FALSE);
|
||||||
|
|
||||||
|
const vk::Viewport viewport(
|
||||||
|
0.0f, 0.0f, static_cast<float>(ctx.extent.width),
|
||||||
|
static_cast<float>(ctx.extent.height), 0.0f, 1.0f);
|
||||||
|
cmd.setViewportWithCount(viewport);
|
||||||
|
cmd.setScissorWithCount(vk::Rect2D({0, 0}, ctx.extent));
|
||||||
|
|
||||||
|
// 无混合直写(ArrayProxyNoTemporaries:具名局部变量传入)
|
||||||
|
constexpr vk::Bool32 blendEnable = VK_FALSE;
|
||||||
|
const vk::ColorBlendEquationEXT direct = MakeDirectEquation();
|
||||||
|
constexpr vk::ColorComponentFlags colorWriteMask =
|
||||||
|
vk::ColorComponentFlagBits::eR |
|
||||||
|
vk::ColorComponentFlagBits::eG |
|
||||||
|
vk::ColorComponentFlagBits::eB |
|
||||||
|
vk::ColorComponentFlagBits::eA;
|
||||||
|
cmd.setColorBlendEnableEXT(0, blendEnable);
|
||||||
|
cmd.setColorBlendEquationEXT(0, direct);
|
||||||
|
cmd.setColorWriteMaskEXT(0, colorWriteMask);
|
||||||
|
|
||||||
|
// 线段与三角形各一条流:换流 = 换 VB 绑定 + 拓扑
|
||||||
|
cmd.setLineWidth(1.0f); // 线拓扑必须显式设置(VUID-08617)
|
||||||
|
if (lineCount > 0) {
|
||||||
|
cmd.setPrimitiveTopology(vk::PrimitiveTopology::eLineList);
|
||||||
|
const std::array<vk::Buffer, 1> bufs{m_LineVB.Get(ctx.frame.frameSlot)};
|
||||||
|
const std::array<vk::DeviceSize, 1> offs{0};
|
||||||
|
cmd.bindVertexBuffers(0, bufs, offs);
|
||||||
|
cmd.draw(lineCount, 1, 0, 0);
|
||||||
|
}
|
||||||
|
if (triCount > 0) {
|
||||||
|
cmd.setPrimitiveTopology(vk::PrimitiveTopology::eTriangleList);
|
||||||
|
const std::array<vk::Buffer, 1> bufs{m_TriVB.Get(ctx.frame.frameSlot)};
|
||||||
|
const std::array<vk::DeviceSize, 1> offs{0};
|
||||||
|
cmd.bindVertexBuffers(0, bufs, offs);
|
||||||
|
cmd.draw(triCount, 1, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.endRendering();
|
||||||
|
})
|
||||||
|
// Write use(WAW 链):保证排在 Forward/ForwardTrans 之后、
|
||||||
|
// TAA(Read HDRColor)之前——调试图元进入 TAA/Bloom/Tonemap 主链路
|
||||||
|
.Write(m_HDRColor, RG::ResourceUse::ColorAttachment)
|
||||||
|
.Write(m_Depth, RG::ResourceUse::DepthAttachment);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Engine::Passes
|
||||||
Submodule
+1
Submodule Lib/JoltPhysics added at 5830c342b9
@@ -0,0 +1,10 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
layout(location = 0) in vec4 inColor;
|
||||||
|
layout(location = 0) out vec4 outColor;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
// HDR 的 a = 窗口透过率语义(透明窗口与桌面合成):调试图元
|
||||||
|
// 永远不透明(a=1),rgb 直写线性 HDR 值
|
||||||
|
outColor = vec4(inColor.rgb, 1.0);
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
// 物理调试图元:世界空间线段/三角形,颜色透传(unlit)。
|
||||||
|
// viewProj 经 push constant 下发(proj × view,与 Forward 同源),
|
||||||
|
// 无描述符 set——调试图元不依赖任何纹理/缓冲。
|
||||||
|
|
||||||
|
layout(location = 0) in vec3 inPos;
|
||||||
|
layout(location = 1) in vec4 inColor;
|
||||||
|
|
||||||
|
layout(location = 0) out vec4 outColor;
|
||||||
|
|
||||||
|
layout(push_constant) uniform PC {
|
||||||
|
mat4 viewProj;
|
||||||
|
} pc;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
gl_Position = pc.viewProj * vec4(inPos, 1.0);
|
||||||
|
outColor = inColor;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user