新增调试绘制体系:DebugDraw 服务 + 物理调试 pass + 脚本侧力学线

通用图元(线/箭头四翼头/球/胶囊/盒)瞬时+持久双缓冲聚合;
物理 pass 画刚体速度/重力/接触反力;角色 collider 与力学
线归位(引擎只画资产、脚本画行为);线宽按设备上限钳制
This commit is contained in:
2026-10-08 17:02:27 +08:00
parent ff955b9e63
commit 36b1fc0566
10 changed files with 903 additions and 44 deletions
+139
View File
@@ -0,0 +1,139 @@
//
// DebugDraw:引擎级通用调试图元服务(Unity Debug.DrawLine / UE
// DrawDebugHelpers 的对应物)。
//
// 分层定位:全局静态 API,任何层(脚本/物理/动画/AI)调用即收集;
// Renderer 每帧取走缓冲,PhysicsDebugPass 统一上传叠加渲染。引擎侧
// 只写这一次,之后一切新可视化 = 调用方加几行,不再碰引擎代码。
//
// 语义对齐 Unity/UE:
// duration <= 0 → 只画当前帧(调用方每帧重调,Unity DrawLine 同)
// duration > 0 → 存活 duration 秒(内部持久列表,期间无需重调;
// 固定步长系统用 2×步长 即可跨渲染帧无缝续接)
// depthTest=false → 透视遮挡直显(UE DrawDebugLine bPersistent 位)
// width → 线宽(需 wideLines 特性,设备不支持时钳回 1.0)
//
// 线程模型:主线程单写单读(脚本 OnUpdate 收集 → Renderer 上传 →
// EndFrame 翻页)。
// 预算:kMaxVerts 截断 + 一次告警(调试图元打满是滥用信号)。
//
#ifndef HELLOVULKAN_DEBUGDRAW_H
#define HELLOVULKAN_DEBUGDRAW_H
#include <Engine/Core/Types.h>
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
#include <vector>
namespace Engine::Debug {
// 顶点(28B):与 PhysicsDebugVertex 同布局——渲染 pass 复用同一
// 顶点输入态(pos vec3 + color vec4)
struct Vertex {
glm::vec3 pos;
glm::vec4 color;
};
// 渲染批次:同 (拓扑, 线宽, 深度测试) 的连续顶点段。调用方按
// 参数自动分组,draw 侧每批一次状态切换(动态状态零管线对象)
struct Batch {
bool tris; // true = triangleList / false = lineList
f32 width;
bool depthTest;
u32 first; // 顶点偏移(顶点数,非字节)
u32 count;
};
// 单帧收集结果(Renderer 每帧消费,勿手动修改)
struct DebugFrame {
std::vector<Vertex> verts;
std::vector<Batch> batches;
void Clear() { verts.clear(); batches.clear(); }
bool Empty() const { return batches.empty(); }
};
// 顶点预算(每渲染帧):调试线框足够用,溢出截断 + 告警一次
inline constexpr u32 kMaxVerts = 1 << 16;
// ---- 全局开关 ----
void SetEnabled(bool on);
bool IsEnabled();
void Clear(); // 清空瞬时 + 持久图元(PIE 停止/场景切换时调用)
// ---- 线类(全部世界空间)----
void Line(const glm::vec3& a, const glm::vec3& b,
const glm::vec4& color,
f32 width = 1.0f, f32 duration = 0.0f,
bool depthTest = true);
// Ray:from 出发沿单位方向 dirNorm 画 length 长(Unity DrawRay 同形)
void Ray(const glm::vec3& from, const glm::vec3& dirNorm, f32 length,
const glm::vec4& color,
f32 width = 1.0f, f32 duration = 0.0f, bool depthTest = true);
// 箭头:主干 + 两翼头(正交基自动构建,零向量方向安全退化成点)
void Arrow(const glm::vec3& from, const glm::vec3& to,
const glm::vec4& color, f32 headSize = 0.12f,
f32 width = 1.0f, f32 duration = 0.0f,
bool depthTest = true);
// 十字标记(Unity Debug.DrawCross 同位)
void Cross(const glm::vec3& pos, f32 size, const glm::vec4& color,
f32 width = 1.0f, f32 duration = 0.0f,
bool depthTest = true);
// ---- 形状线框 ----
// 三大圆经线圈(UE DrawDebugSphere 同形)
void WireSphere(const glm::vec3& center, f32 radius,
const glm::vec4& color, int segments = 16,
f32 width = 1.0f, f32 duration = 0.0f,
bool depthTest = true);
// 朝向 normal 的平面圆
void WireCircle(const glm::vec3& center, f32 radius,
const glm::vec3& normal, const glm::vec4& color,
int segments = 24,
f32 width = 1.0f, f32 duration = 0.0f,
bool depthTest = true);
// 轴对齐 / 旋转盒(rot 单位四元数 = AABB;UE DrawDebugBox 同形)
void WireBox(const glm::vec3& center, const glm::vec3& halfExtents,
const glm::vec4& color,
const glm::quat& rot = glm::quat(1.0f, 0.0f, 0.0f, 0.0f),
f32 width = 1.0f, f32 duration = 0.0f,
bool depthTest = true);
// 胶囊线框:轴向 local +Y(Jolt 惯例),halfHeight = 圆柱段半长
//(总高 = 2×(halfHeight+radius),与引擎 CharacterController 参数一致)
void WireCapsule(const glm::vec3& center, f32 radius, f32 halfHeight,
const glm::vec4& color,
const glm::quat& rot = glm::quat(1.0f, 0.0f, 0.0f, 0.0f),
f32 width = 1.0f, f32 duration = 0.0f,
bool depthTest = true);
// ---- 便捷色:0xRRGGBB → 线性 HDR vec4(可 >1 过 tonemap 依旧醒目)----
constexpr glm::vec4 Rgb(u32 hex, f32 alpha = 1.0f)
{
constexpr f32 kInv = 1.0f / 255.0f;
return {(static_cast<f32>((hex >> 16) & 0xFF)) * kInv,
(static_cast<f32>((hex >> 8) & 0xFF)) * kInv,
(static_cast<f32>(hex & 0xFF)) * kInv,
alpha};
}
// ---- Renderer 消费端(业务代码勿调用)----
// 取当前帧缓冲(禁用/空 = Empty)。Renderer 上传窗口调用
const DebugFrame& Frame();
// 翻页:清瞬时缓冲 + 清过期持久项。Renderer 上传完成后调用
void EndFrame();
} // namespace Engine::Debug
#endif //HELLOVULKAN_DEBUGDRAW_H
@@ -99,6 +99,9 @@ namespace Engine {
void Jump(u32 entityBits, f32 speed); // 垂直速度置 speed
bool IsCharacterGrounded(u32 entityBits) const;
glm::vec3 GetCharacterVelocity(u32 entityBits) const;
// 角色脚底位置(物理权威世界坐标):调试图元/特效锚点用它,
// 而不是 Transform().GetPosition()——后者在有父级时是本地坐标
glm::vec3 GetCharacterPosition(u32 entityBits) const;
private:
// 创建时缓存的参数(变更检测 → 重建 body)
@@ -1,25 +1,28 @@
//
// PhysicsDebugPass:物理调试图元叠加(collider 线框/约束/重力/速度/
// 加速度箭头)。
// PhysicsDebugPass:调试图元叠加 pass(双源)。
// 物理源 = PhysicsWorld 收集的 PhysicsDebugFrame(collider 线框/
// 约束,Application 注入帧缓冲指针)
// 通用源 = Engine::Debug 调试服务(脚本/任意层的 Line/Arrow/Wire*,
// Unity Debug.DrawLine 同位;批次自带线宽/深度测试/拓扑)
//
// 画在 HDRColor(Forward 之后、TAA 之前):调试图元进入场景主链路——
// TAA 时域累积抗锯齿、Bloom/Tonemap 正常消费,kDebugTransparent 诊断
// 模式下也可见。深度只测不写(被不透明几何正确遮挡),无混合直写
// (fullscreen pass 状态泄漏教训)。
//
// 数据源 = PhysicsWorld 每帧收集的 PhysicsDebugFrame(Application 把
// PhysicsWorld 的帧缓冲指针注入 Renderer)。空帧零绘制;debug 开关
// 全关时 PhysicsWorld 不收集 → 顶点数为 0 → 本 pass 自然零开销。
// 空帧零绘制:两源顶点数均 0 时提前短路。
//
#ifndef HELLOVULKAN_PHYSICSDEBUGPASS_H
#define HELLOVULKAN_PHYSICSDEBUGPASS_H
#include <Engine/Core/Types.h>
#include <Engine/Debug/DebugDraw.h>
#include <Engine/Renderer/Passes/RenderPass.h>
#include <Engine/Renderer/SceneTypes.h>
#include <Engine/Vulkan/UniformBuffer.h>
#include <Engine/Vulkan/ShaderManager.h>
#include <vector>
#include <vulkan/vulkan.hpp>
namespace Engine {
@@ -44,7 +47,8 @@ namespace Engine {
FrameSceneData& frame,
RG::TextureHandle hdrColor,
RG::TextureHandle depth,
UniformBuffer& lineVB, UniformBuffer& triVB);
UniformBuffer& lineVB, UniformBuffer& triVB,
UniformBuffer& debugVB);
~PhysicsDebugPass();
PhysicsDebugPass(const PhysicsDebugPass&) = delete;
@@ -52,23 +56,36 @@ namespace Engine {
void Register(RG::RenderGraph& rg) override;
// 每帧 Renderer::Render 里(该槽位 fence 等待后)上传当帧图元
void PrepareFrame(u32 frameSlot, const PhysicsDebugFrame& src);
// 每帧 Renderer::Render 里(该槽位 fence 等待后)上传当帧
// 图元。dbg 为 nullptr = 通用调试源禁用/空
void PrepareFrame(u32 frameSlot, const PhysicsDebugFrame& src,
const Debug::DebugFrame* dbg);
private:
// 一个可绘制段:独立拓扑/线宽/深度测试(动态状态逐批设置)
struct Span {
bool tris;
f32 width;
bool depthTest;
u32 first; // 顶点偏移(对应 VB 内顶点序号)
u32 count;
};
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_LineVB; // per-slot ring(物理源,Renderer 持有)
UniformBuffer& m_TriVB;
UniformBuffer& m_DebugVB; // 通用调试源 ring(Renderer 持有)
vk::PipelineLayout m_Layout; // 无描述符,push = viewProj 64B
ShaderManager::ShaderRef m_Vert, m_Frag; // 析构自动销毁
std::array<u32, kMaxFramesInFlight> m_LineCounts{};
std::array<u32, kMaxFramesInFlight> m_TriCounts{};
std::array<std::vector<Span>, kMaxFramesInFlight> m_Spans;
bool m_TruncationWarned = false;
};
+4
View File
@@ -48,6 +48,8 @@ public:
// 各向异性过滤:纹理采样器清晰度(掠射角材质锐利度)的硬件开关
bool IsSamplerAnisotropySupported() const { return m_SamplerAnisotropySupported; }
float MaxSamplerAnisotropy() const { return m_MaxSamplerAnisotropy; }
bool IsWideLinesSupported() const { return m_WideLinesSupported; }
float MaxLineWidth() const { return m_MaxLineWidth; }
private:
void CreateInstance();
@@ -81,6 +83,8 @@ private:
bool m_DrawIndirectCountSupported = false;
bool m_SamplerAnisotropySupported = false;
float m_MaxSamplerAnisotropy = 1.0f;
bool m_WideLinesSupported = false;
float m_MaxLineWidth = 1.0f; // 设备线宽上限(像素,limits.lineWidthRange[1])
std::vector<const char*> m_ValidationLayers = { "VK_LAYER_KHRONOS_validation" };
std::vector<const char*> m_DeviceExtensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME };