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:
2026-10-02 20:08:54 +08:00
parent bfbb18f1a4
commit 1c4b6f37ff
21 changed files with 1589 additions and 31 deletions
@@ -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