新增调试绘制体系:DebugDraw 服务 + 物理调试 pass + 脚本侧力学线
通用图元(线/箭头四翼头/球/胶囊/盒)瞬时+持久双缓冲聚合; 物理 pass 画刚体速度/重力/接触反力;角色 collider 与力学 线归位(引擎只画资产、脚本画行为);线宽按设备上限钳制
This commit is contained in:
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -0,0 +1,359 @@
|
||||
//
|
||||
// DebugDraw.cpp:通用调试图元服务实现。
|
||||
//
|
||||
// 结构:瞬时缓冲(duration=0,调用方每帧重画)+ 持久列表(duration>0,
|
||||
// 按到期时间淘汰)→ Frame() 按 (拓扑,线宽,深度测试) 键聚合为连续顶点段
|
||||
// → Renderer 上传 → EndFrame() 翻页。聚合器不缓存跨帧顶点,持久项每帧
|
||||
// 重拷贝(调试工具量级,< 数千顶点,非热路径)。
|
||||
//
|
||||
|
||||
#include <Engine/Debug/DebugDraw.h>
|
||||
#include <Engine/Core/Log.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <chrono>
|
||||
|
||||
namespace Engine::Debug {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr f32 kPi = 3.14159265358979f;
|
||||
|
||||
// 瞬时批次:Draw 调用直接追加(批次键匹配复用,保留容量)
|
||||
struct TransientBatch {
|
||||
f32 width;
|
||||
bool depthTest;
|
||||
std::vector<Vertex> verts;
|
||||
};
|
||||
|
||||
// 持久项:快照顶点 + 到期时刻(steady_clock 秒)
|
||||
struct Persistent {
|
||||
f32 width;
|
||||
bool depthTest;
|
||||
std::vector<Vertex> verts;
|
||||
f64 expiry;
|
||||
};
|
||||
|
||||
bool g_Enabled = true;
|
||||
bool g_Warned = false;
|
||||
std::vector<TransientBatch> g_Transient;
|
||||
std::vector<Persistent> g_Persistent;
|
||||
DebugFrame g_Frame;
|
||||
|
||||
using Clock = std::chrono::steady_clock;
|
||||
|
||||
f64 NowSec()
|
||||
{
|
||||
return std::chrono::duration<f64>(Clock::now().time_since_epoch())
|
||||
.count();
|
||||
}
|
||||
|
||||
TransientBatch& BatchFor(f32 width, bool depthTest)
|
||||
{
|
||||
for (auto& b : g_Transient) {
|
||||
if (b.depthTest == depthTest &&
|
||||
std::abs(b.width - width) < 1e-3f) {
|
||||
return b;
|
||||
}
|
||||
}
|
||||
g_Transient.push_back(TransientBatch{width, depthTest, {}});
|
||||
return g_Transient.back();
|
||||
}
|
||||
|
||||
void LineImpl(const glm::vec3& a, const glm::vec3& b,
|
||||
const glm::vec4& color, f32 width, f32 duration,
|
||||
bool depthTest)
|
||||
{
|
||||
if (!g_Enabled) return;
|
||||
if (duration > 0.0f) {
|
||||
Persistent p;
|
||||
p.width = width;
|
||||
p.depthTest = depthTest;
|
||||
p.expiry = NowSec() + static_cast<f64>(duration);
|
||||
p.verts.push_back({a, color});
|
||||
p.verts.push_back({b, color});
|
||||
g_Persistent.push_back(std::move(p));
|
||||
} else {
|
||||
auto& batch = BatchFor(width, depthTest);
|
||||
batch.verts.push_back({a, color});
|
||||
batch.verts.push_back({b, color});
|
||||
}
|
||||
}
|
||||
|
||||
// 圆折线发射器:center + u·cosθ + v·sinθ(u/v 已含半径)
|
||||
void CircleImpl(const glm::vec3& center, const glm::vec3& u,
|
||||
const glm::vec3& v, const glm::vec4& color,
|
||||
int segments, f32 width, f32 duration,
|
||||
bool depthTest)
|
||||
{
|
||||
glm::vec3 prev = center + u;
|
||||
for (int i = 1; i <= segments; ++i) {
|
||||
const f32 t =
|
||||
static_cast<f32>(i) / static_cast<f32>(segments) * 2.0f * kPi;
|
||||
const glm::vec3 p = center + u * std::cos(t) + v * std::sin(t);
|
||||
LineImpl(prev, p, color, width, duration, depthTest);
|
||||
prev = p;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void SetEnabled(bool on)
|
||||
{
|
||||
g_Enabled = on;
|
||||
if (!on) {
|
||||
Clear();
|
||||
}
|
||||
}
|
||||
|
||||
bool IsEnabled() { return g_Enabled; }
|
||||
|
||||
void Clear()
|
||||
{
|
||||
g_Transient.clear();
|
||||
g_Persistent.clear();
|
||||
g_Frame.Clear();
|
||||
}
|
||||
|
||||
void Line(const glm::vec3& a, const glm::vec3& b, const glm::vec4& color,
|
||||
f32 width, f32 duration, bool depthTest)
|
||||
{
|
||||
LineImpl(a, b, color, width, duration, depthTest);
|
||||
}
|
||||
|
||||
void Ray(const glm::vec3& from, const glm::vec3& dirNorm, f32 length,
|
||||
const glm::vec4& color, f32 width, f32 duration, bool depthTest)
|
||||
{
|
||||
LineImpl(from, from + dirNorm * length, color, width, duration,
|
||||
depthTest);
|
||||
}
|
||||
|
||||
void Arrow(const glm::vec3& from, const glm::vec3& to,
|
||||
const glm::vec4& color, f32 headSize, f32 width, f32 duration,
|
||||
bool depthTest)
|
||||
{
|
||||
const glm::vec3 d = to - from;
|
||||
const f32 lenSq = glm::dot(d, d);
|
||||
if (lenSq < 1e-12f) {
|
||||
// 零长退化:画十字点标(而不是未定义方向的箭头)
|
||||
Cross(from, headSize * 0.5f, color, width, duration, depthTest);
|
||||
return;
|
||||
}
|
||||
LineImpl(from, to, color, width, duration, depthTest);
|
||||
|
||||
// 箭头四翼:轴周围 90° 均布两对 V(60° 张角)。单 V 在视线与
|
||||
// 翼面共面时退化成一条线;四翼保证任意视角至少一对正对相机
|
||||
const glm::vec3 dir = d * (1.0f / std::sqrt(lenSq));
|
||||
const glm::vec3 up =
|
||||
std::abs(dir.y) < 0.99f ? glm::vec3(0.0f, 1.0f, 0.0f)
|
||||
: glm::vec3(1.0f, 0.0f, 0.0f);
|
||||
const glm::vec3 side = glm::normalize(glm::cross(dir, up));
|
||||
const glm::vec3 bit = glm::normalize(glm::cross(dir, side));
|
||||
const glm::vec3 heads[4] = {
|
||||
glm::normalize(dir + side * 0.6f),
|
||||
glm::normalize(dir - side * 0.6f),
|
||||
glm::normalize(dir + bit * 0.6f),
|
||||
glm::normalize(dir - bit * 0.6f),
|
||||
};
|
||||
for (const glm::vec3& h : heads) {
|
||||
LineImpl(to, to - h * headSize, color, width, duration, depthTest);
|
||||
}
|
||||
}
|
||||
|
||||
void Cross(const glm::vec3& pos, f32 size, const glm::vec4& color,
|
||||
f32 width, f32 duration, bool depthTest)
|
||||
{
|
||||
const glm::vec3 s(size, size, size);
|
||||
LineImpl(pos - glm::vec3(size, 0, 0), pos + glm::vec3(size, 0, 0),
|
||||
color, width, duration, depthTest);
|
||||
LineImpl(pos - glm::vec3(0, size, 0), pos + glm::vec3(0, size, 0),
|
||||
color, width, duration, depthTest);
|
||||
LineImpl(pos - glm::vec3(0, 0, size), pos + glm::vec3(0, 0, size),
|
||||
color, width, duration, depthTest);
|
||||
(void)s;
|
||||
}
|
||||
|
||||
void WireSphere(const glm::vec3& center, f32 radius,
|
||||
const glm::vec4& color, int segments, f32 width,
|
||||
f32 duration, bool depthTest)
|
||||
{
|
||||
// 三大圆:XY / XZ / YZ 平面
|
||||
CircleImpl(center, {radius, 0, 0}, {0, radius, 0}, color, segments,
|
||||
width, duration, depthTest);
|
||||
CircleImpl(center, {radius, 0, 0}, {0, 0, radius}, color, segments,
|
||||
width, duration, depthTest);
|
||||
CircleImpl(center, {0, radius, 0}, {0, 0, radius}, color, segments,
|
||||
width, duration, depthTest);
|
||||
}
|
||||
|
||||
void WireCircle(const glm::vec3& center, f32 radius,
|
||||
const glm::vec3& normal, const glm::vec4& color,
|
||||
int segments, f32 width, f32 duration, bool depthTest)
|
||||
{
|
||||
const f32 nSq = glm::dot(normal, normal);
|
||||
if (nSq < 1e-12f) return;
|
||||
const glm::vec3 n = normal * (1.0f / std::sqrt(nSq));
|
||||
const glm::vec3 up = std::abs(n.y) < 0.99f
|
||||
? glm::vec3(0.0f, 1.0f, 0.0f)
|
||||
: glm::vec3(1.0f, 0.0f, 0.0f);
|
||||
const glm::vec3 u = glm::normalize(glm::cross(n, up)) * radius;
|
||||
const glm::vec3 v = glm::normalize(glm::cross(n, u / radius)) * radius;
|
||||
CircleImpl(center, u, v, color, segments, width, duration, depthTest);
|
||||
}
|
||||
|
||||
void WireBox(const glm::vec3& center, const glm::vec3& halfExtents,
|
||||
const glm::vec4& color, const glm::quat& rot, f32 width,
|
||||
f32 duration, bool depthTest)
|
||||
{
|
||||
glm::vec3 corner[8];
|
||||
int ci = 0;
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
const glm::vec3 local(
|
||||
(i & 1) ? halfExtents.x : -halfExtents.x,
|
||||
(i & 2) ? halfExtents.y : -halfExtents.y,
|
||||
(i & 4) ? halfExtents.z : -halfExtents.z);
|
||||
corner[ci++] = center + rot * local;
|
||||
}
|
||||
// 12 条边
|
||||
static constexpr int kEdges[12][2] = {
|
||||
{0, 1}, {2, 3}, {4, 5}, {6, 7}, // X 向
|
||||
{0, 2}, {1, 3}, {4, 6}, {5, 7}, // Y 向
|
||||
{0, 4}, {1, 5}, {2, 6}, {3, 7}, // Z 向
|
||||
};
|
||||
for (const auto& e : kEdges) {
|
||||
LineImpl(corner[e[0]], corner[e[1]], color, width, duration,
|
||||
depthTest);
|
||||
}
|
||||
}
|
||||
|
||||
void WireCapsule(const glm::vec3& center, f32 radius, f32 halfHeight,
|
||||
const glm::vec4& color, const glm::quat& rot, f32 width,
|
||||
f32 duration, bool depthTest)
|
||||
{
|
||||
// 轴向 local +Y:两端圆 + 4 侧线 + 两盖各 2 半圆(X-Y / Z-Y 平面)
|
||||
auto W = [&](const glm::vec3& local) { return center + rot * local; };
|
||||
|
||||
auto CircleY = [&](f32 y) {
|
||||
glm::vec3 prev;
|
||||
for (int i = 0; i <= 16; ++i) {
|
||||
const f32 t = static_cast<f32>(i) / 16.0f * 2.0f * kPi;
|
||||
const glm::vec3 p = W({radius * std::cos(t), y,
|
||||
radius * std::sin(t)});
|
||||
if (i > 0) {
|
||||
LineImpl(prev, p, color, width, duration, depthTest);
|
||||
}
|
||||
prev = p;
|
||||
}
|
||||
};
|
||||
CircleY(halfHeight);
|
||||
CircleY(-halfHeight);
|
||||
|
||||
LineImpl(W({radius, halfHeight, 0}), W({radius, -halfHeight, 0}),
|
||||
color, width, duration, depthTest);
|
||||
LineImpl(W({-radius, halfHeight, 0}), W({-radius, -halfHeight, 0}),
|
||||
color, width, duration, depthTest);
|
||||
LineImpl(W({0, halfHeight, radius}), W({0, -halfHeight, radius}),
|
||||
color, width, duration, depthTest);
|
||||
LineImpl(W({0, halfHeight, -radius}), W({0, -halfHeight, -radius}),
|
||||
color, width, duration, depthTest);
|
||||
|
||||
auto CapArc = [&](f32 cap, f32 xz) {
|
||||
// xz: 半圆所在竖直平面(1 = X-Y 面,0 = Z-Y 面)
|
||||
glm::vec3 prev;
|
||||
for (int i = 0; i <= 8; ++i) {
|
||||
const f32 a = static_cast<f32>(i) / 8.0f * kPi;
|
||||
const f32 radial = radius * std::cos(a);
|
||||
const f32 axial = cap * (halfHeight + radius * std::sin(a));
|
||||
const glm::vec3 p =
|
||||
W({radial * xz, axial, radial * (1.0f - xz)});
|
||||
if (i > 0) {
|
||||
LineImpl(prev, p, color, width, duration, depthTest);
|
||||
}
|
||||
prev = p;
|
||||
}
|
||||
};
|
||||
CapArc(1.0f, 1.0f);
|
||||
CapArc(-1.0f, 1.0f);
|
||||
CapArc(1.0f, 0.0f);
|
||||
CapArc(-1.0f, 0.0f);
|
||||
}
|
||||
|
||||
const DebugFrame& Frame()
|
||||
{
|
||||
g_Frame.Clear();
|
||||
if (!g_Enabled) {
|
||||
return g_Frame;
|
||||
}
|
||||
|
||||
// ---- 按 (拓扑=线, 线宽, 深度) 聚合:瞬时 + 未过期持久项 ----
|
||||
// groups = 聚合组暂存(键少量,线性扫)→ 展开为连续顶点段
|
||||
std::vector<std::pair<Batch, std::vector<Vertex>>> groups;
|
||||
auto findGroup = [&](f32 w, bool d) -> std::vector<Vertex>& {
|
||||
for (auto& [b, vs] : groups) {
|
||||
if (!b.tris && b.depthTest == d &&
|
||||
std::abs(b.width - w) < 1e-3f) {
|
||||
return vs;
|
||||
}
|
||||
}
|
||||
groups.push_back({Batch{false, w, d, 0, 0}, {}});
|
||||
return groups.back().second;
|
||||
};
|
||||
|
||||
for (const auto& t : g_Transient) {
|
||||
findGroup(t.width, t.depthTest)
|
||||
.insert(findGroup(t.width, t.depthTest).end(),
|
||||
t.verts.begin(), t.verts.end());
|
||||
}
|
||||
const f64 now = NowSec();
|
||||
for (const auto& p : g_Persistent) {
|
||||
if (p.expiry > now) {
|
||||
findGroup(p.width, p.depthTest)
|
||||
.insert(findGroup(p.width, p.depthTest).end(),
|
||||
p.verts.begin(), p.verts.end());
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 展开 + 预算截断 ----
|
||||
u64 total = 0;
|
||||
for (const auto& [b, vs] : groups) {
|
||||
total += vs.size();
|
||||
}
|
||||
const u64 cap = kMaxVerts;
|
||||
if (total > cap && !g_Warned) {
|
||||
g_Warned = true;
|
||||
ENGINE_WARN("DebugDraw: vertex budget exceeded ({} > {}), "
|
||||
"clamped",
|
||||
total, cap);
|
||||
}
|
||||
for (auto& [b, vs] : groups) {
|
||||
if (g_Frame.verts.size() >= cap) break;
|
||||
b.first = static_cast<u32>(g_Frame.verts.size());
|
||||
const u64 room = cap - g_Frame.verts.size();
|
||||
const u64 n = std::min<u64>(vs.size(), room);
|
||||
g_Frame.verts.insert(g_Frame.verts.end(), vs.begin(),
|
||||
vs.begin() + static_cast<i64>(n));
|
||||
b.count = static_cast<u32>(n);
|
||||
if (b.count > 0) {
|
||||
g_Frame.batches.push_back(b);
|
||||
}
|
||||
}
|
||||
return g_Frame;
|
||||
}
|
||||
|
||||
void EndFrame()
|
||||
{
|
||||
g_Frame.Clear();
|
||||
const f64 now = NowSec();
|
||||
g_Persistent.erase(std::remove_if(g_Persistent.begin(),
|
||||
g_Persistent.end(),
|
||||
[now](const Persistent& p) {
|
||||
return p.expiry <= now;
|
||||
}),
|
||||
g_Persistent.end());
|
||||
for (auto& t : g_Transient) {
|
||||
t.verts.clear(); // 保留批次与容量(稳态零重分配)
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Engine::Debug
|
||||
@@ -3,6 +3,7 @@
|
||||
//
|
||||
|
||||
#include <Engine/Physics/PhysicsWorld.h>
|
||||
#include <Engine/Debug/DebugDraw.h>
|
||||
|
||||
#include <Engine/Core/Log.h>
|
||||
#include <Engine/ECS/Components.h>
|
||||
@@ -274,12 +275,14 @@ namespace Engine {
|
||||
void PhysicsWorld::CollectDebugData() {
|
||||
m_DebugDraw->BeginCollect();
|
||||
|
||||
// 1) Jolt 原生:collider 线框 + 速度箭头(软体 mDrawSoftBody* 后续加这里)
|
||||
if ((m_DebugBits & (kPhyDbgColliders | kPhyDbgVelocity)) != 0) {
|
||||
// 1) Jolt 原生:collider 线框(软体 mDrawSoftBody* 后续加这里)。
|
||||
// 速度箭头不交 Jolt 画——其 BodyManager::DrawBodies 无零速保
|
||||
// 护,静止刚体的零向量箭头退化成 +X 方向(本次修复的突兀箭
|
||||
// 头)→ 统一在下方手画并加阈值
|
||||
if ((m_DebugBits & kPhyDbgColliders) != 0) {
|
||||
JPH::BodyManager::DrawSettings ds;
|
||||
ds.mDrawShape = (m_DebugBits & kPhyDbgColliders) != 0;
|
||||
ds.mDrawShape = true;
|
||||
ds.mDrawShapeWireframe = true;
|
||||
ds.mDrawVelocity = (m_DebugBits & kPhyDbgVelocity) != 0;
|
||||
m_System->DrawBodies(ds, m_DebugDraw.get());
|
||||
}
|
||||
|
||||
@@ -289,39 +292,86 @@ namespace Engine {
|
||||
}
|
||||
|
||||
// 3) 手绘箭头(Jolt 无现成 API):重力 = 常量方向指示(长度固定),
|
||||
// 加速度 = 速度差分(方向/长度随值,静止噪声阈值截断)
|
||||
// 加速度 = 速度差分(方向/长度随值,静止噪声阈值截断)。
|
||||
// 走引擎通用 DebugDraw 服务。收集按渲染帧率跑(Step 末尾
|
||||
// 无步进帧也重画,每帧全量重建)→ duration 必须 0(当帧瞬
|
||||
// 时):此前沿用 step 率时代的 2×步长续期,高帧率下每根箭
|
||||
// 头叠 4-5 个滞后副本,物体一动副本脱锚拖尾——表现为断断
|
||||
// 续续乱跳
|
||||
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); // 橙:加速度
|
||||
constexpr f32 kVelScale = 0.2f; // 速度箭头比例(单位/速度)
|
||||
constexpr f32 kDbgDur = 0.0f; // 渲染帧率收集 → 瞬时图元
|
||||
constexpr glm::vec4 kGravityColor = Debug::Rgb(0xDC3C3C); // 红:重力
|
||||
constexpr glm::vec4 kAccelColor = Debug::Rgb(0xE0640A); // 深橙:加速度
|
||||
constexpr glm::vec4 kVelColor = Debug::Rgb(0x5ADC5A); // 绿:速度
|
||||
constexpr glm::vec4 kContactColor = Debug::Rgb(0xD9C33C); // 黄:接触反力
|
||||
auto ToG = [](const JPH::RVec3Arg v) {
|
||||
return glm::vec3{static_cast<f32>(v.GetX()),
|
||||
static_cast<f32>(v.GetY()),
|
||||
static_cast<f32>(v.GetZ())};
|
||||
};
|
||||
|
||||
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);
|
||||
const glm::vec3 com = ToG(bi.GetCenterOfMassPosition(rec.id));
|
||||
|
||||
// 速度箭头(绿):零速阈值——Jolt 原生 DrawBodies 无零速保
|
||||
// 护(静止零向量箭头退化成 +X),此处阈值手画
|
||||
if ((m_DebugBits & kPhyDbgVelocity) != 0) {
|
||||
const JPH::Vec3 v = bi.GetLinearVelocity(rec.id);
|
||||
if (v.LengthSq() > 1e-4f) { // >0.01 m/s 才画
|
||||
Debug::Arrow(com, com + ToG(JPH::RVec3(v * kVelScale)),
|
||||
kVelColor,
|
||||
/*headSize*/ 0.12f, /*width*/ 2.0f,
|
||||
/*duration*/ kDbgDur, /*depthTest*/ false);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
Debug::Arrow(
|
||||
com,
|
||||
com + ToG(JPH::RVec3(g * (kGravityLen / len))),
|
||||
kGravityColor,
|
||||
/*headSize*/ 0.12f, /*width*/ 2.0f,
|
||||
/*duration*/ kDbgDur, /*depthTest*/ false);
|
||||
}
|
||||
}
|
||||
|
||||
if ((m_DebugBits & kPhyDbgAccel) != 0) {
|
||||
// 接触合力(比力 F/m = a − g,从测量加速度反推):静置
|
||||
// = +g 纯法向(与重力箭头等长反向 = 力平衡);滑动出现
|
||||
// 水平分量(摩擦);自由落体 ≈ 0 不画。长度按重力箭头
|
||||
// 同一比例归一 → 静置红下黄上视觉等长
|
||||
const JPH::Vec3 gB = gravity * bi.GetGravityFactor(rec.id);
|
||||
const glm::vec3 spec = rec.lastAccel -
|
||||
glm::vec3(gB.GetX(), gB.GetY(), gB.GetZ());
|
||||
const f32 smag = glm::length(spec);
|
||||
if (smag > 0.2f && gravity.Length() > 1e-4f) {
|
||||
const f32 slen = std::min(
|
||||
smag * kGravityLen / gravity.Length(), kAccelMaxLen);
|
||||
Debug::Arrow(com, com + (spec / smag) * slen,
|
||||
kContactColor,
|
||||
/*headSize*/ 0.12f, /*width*/ 2.0f,
|
||||
/*duration*/ kDbgDur, /*depthTest*/ false);
|
||||
}
|
||||
|
||||
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));
|
||||
Debug::Arrow(com, com + (rec.lastAccel / mag) * len,
|
||||
kAccelColor,
|
||||
/*headSize*/ 0.12f,
|
||||
/*width*/ 2.0f,
|
||||
/*duration*/ kDbgDur, /*depthTest*/ false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -334,6 +384,10 @@ namespace Engine {
|
||||
}
|
||||
}
|
||||
|
||||
// 角色运动类线条(速度/加速度)一律归用户脚本画(Unity
|
||||
// CharacterController 同语义:引擎只画 collider,移动调试归
|
||||
// 移动逻辑的拥有者);物理侧不保留任何角色箭头
|
||||
|
||||
// 释放上一帧缓存不再引用的通用几何 batch(DebugRenderer 内部池)
|
||||
m_DebugDraw->NextFrame();
|
||||
}
|
||||
@@ -675,7 +729,12 @@ namespace Engine {
|
||||
if (tr == nullptr) {
|
||||
continue;
|
||||
}
|
||||
entry.cc->Update(dt, gravity, *m_TempAllocator);
|
||||
// 每角色重力倍率(组件直读:Inspector 调完下个物理步即生效,
|
||||
// 不触发重建——重力只是 Update 的入参,不是形状参数)
|
||||
const auto *cc = reg.try_get<ECS::CharacterControllerComponent>(
|
||||
static_cast<entt::entity>(bits));
|
||||
const f32 gscale = cc != nullptr ? cc->gravityScale : 1.0f;
|
||||
entry.cc->Update(dt, gravity * gscale, *m_TempAllocator);
|
||||
const glm::vec3 p = entry.cc->GetPosition(); // 原点 = 脚底(世界系)
|
||||
// 写回走 world→local 换算(层级实体挂父下仍正确;标脏 →
|
||||
// UpdateTransforms 重算 world)
|
||||
@@ -1047,6 +1106,12 @@ namespace Engine {
|
||||
: glm::vec3{0.0f};
|
||||
}
|
||||
|
||||
glm::vec3 PhysicsWorld::GetCharacterPosition(const u32 entityBits) const {
|
||||
const auto found = m_Characters.find(entityBits);
|
||||
return found != m_Characters.end() ? found->second.cc->GetPosition()
|
||||
: glm::vec3{0.0f};
|
||||
}
|
||||
|
||||
// ---- 写回:动态体姿态 → Transform(标脏,帧循环统一 Compose)----
|
||||
|
||||
void PhysicsWorld::Writeback() {
|
||||
|
||||
@@ -64,9 +64,11 @@ namespace Engine::Passes
|
||||
FrameSceneData& frame,
|
||||
RG::TextureHandle hdrColor,
|
||||
RG::TextureHandle depth,
|
||||
UniformBuffer& lineVB, UniformBuffer& triVB)
|
||||
UniformBuffer& lineVB, UniformBuffer& triVB,
|
||||
UniformBuffer& debugVB)
|
||||
: m_Device(device), m_Descriptors(descriptors), m_Frame(frame),
|
||||
m_HDRColor(hdrColor), m_Depth(depth), m_LineVB(lineVB), m_TriVB(triVB)
|
||||
m_HDRColor(hdrColor), m_Depth(depth), m_LineVB(lineVB), m_TriVB(triVB),
|
||||
m_DebugVB(debugVB)
|
||||
{
|
||||
// 无描述符 set:viewProj 走 push constant(shader 侧声明一致)
|
||||
m_Layout = descriptors.CreatePipelineLayout(
|
||||
@@ -98,7 +100,8 @@ namespace Engine::Passes
|
||||
}
|
||||
|
||||
void PhysicsDebugPass::PrepareFrame(u32 frameSlot,
|
||||
const PhysicsDebugFrame& src)
|
||||
const PhysicsDebugFrame& src,
|
||||
const Debug::DebugFrame* dbg)
|
||||
{
|
||||
// 预算截断:物理调试不应打满(打满说明该收敛可视化了)
|
||||
const u32 lineVerts = std::min<u32>(
|
||||
@@ -115,18 +118,40 @@ namespace Engine::Passes
|
||||
|
||||
// fence 已等(Renderer::Render 的 WaitFence 之后调用),
|
||||
// 写-读竞争关闭——与 UBO/SSBO 上传同一窗口语义
|
||||
m_Spans[frameSlot].clear();
|
||||
if (lineVerts > 0) {
|
||||
m_LineVB.Update(frameSlot, src.lines.data(),
|
||||
static_cast<u64>(lineVerts) *
|
||||
sizeof(PhysicsDebugVertex));
|
||||
m_Spans[frameSlot].push_back({false, 1.0f, true, 0, lineVerts});
|
||||
}
|
||||
if (triVerts > 0) {
|
||||
m_TriVB.Update(frameSlot, src.triangles.data(),
|
||||
static_cast<u64>(triVerts) *
|
||||
sizeof(PhysicsDebugVertex));
|
||||
m_Spans[frameSlot].push_back({true, 1.0f, true, 0, triVerts});
|
||||
}
|
||||
m_LineCounts[frameSlot] = lineVerts;
|
||||
m_TriCounts[frameSlot] = triVerts;
|
||||
|
||||
// 通用调试源(独立 ring VB,与物理源隔离):批次按收集侧的
|
||||
// (拓扑, 线宽, 深度) 原样展开为绘制段;超预算部分截断
|
||||
if (dbg != nullptr && !dbg->Empty() && !dbg->verts.empty()) {
|
||||
const u32 total = static_cast<u32>(dbg->verts.size());
|
||||
const u32 n = std::min<u32>(total, Debug::kMaxVerts);
|
||||
m_DebugVB.Update(frameSlot, dbg->verts.data(),
|
||||
static_cast<u64>(n) * sizeof(Debug::Vertex));
|
||||
u32 consumed = 0;
|
||||
for (const Debug::Batch& b : dbg->batches) {
|
||||
if (consumed >= n) break;
|
||||
const u32 take = std::min<u32>(b.count, n - consumed);
|
||||
if (take > 0) {
|
||||
m_Spans[frameSlot].push_back(
|
||||
{b.tris, b.width, b.depthTest, consumed, take});
|
||||
}
|
||||
consumed += take;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsDebugPass::Register(RG::RenderGraph& rg)
|
||||
@@ -134,7 +159,10 @@ namespace Engine::Passes
|
||||
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; // 空帧零开销
|
||||
const std::vector<Span>& spans = m_Spans[ctx.frame.frameSlot];
|
||||
if (lineCount == 0 && triCount == 0 && spans.empty()) {
|
||||
return; // 空帧零开销
|
||||
}
|
||||
|
||||
ZoneScopedN("PhysicsDebug");
|
||||
const vk::CommandBuffer cmd = ctx.cmd;
|
||||
@@ -236,6 +264,29 @@ namespace Engine::Passes
|
||||
cmd.draw(triCount, 1, 0, 0);
|
||||
}
|
||||
|
||||
// 通用调试源批次:逐批动态状态(拓扑/线宽/深度测试)。
|
||||
// 线宽 >1 需 wideLines 特性,不支持时钳回 1.0(VUID-07937)
|
||||
if (!spans.empty()) {
|
||||
const std::array<vk::Buffer, 1> bufs{m_DebugVB.Get(ctx.frame.frameSlot)};
|
||||
const std::array<vk::DeviceSize, 1> offs{0};
|
||||
cmd.bindVertexBuffers(0, bufs, offs);
|
||||
for (const Span& s : spans) {
|
||||
cmd.setPrimitiveTopology(
|
||||
s.tris ? vk::PrimitiveTopology::eTriangleList
|
||||
: vk::PrimitiveTopology::eLineList);
|
||||
// 线宽单位 = 像素,设备范围 [1.0, lineWidthRange[1]]:
|
||||
// 低于下限的值(如 0.18)是校验错误,驱动按 1px 处
|
||||
// 理——必须钳进范围;wideLines 不支持则恒 1px
|
||||
cmd.setLineWidth(m_Device.IsWideLinesSupported()
|
||||
? std::clamp(s.width, 1.0f,
|
||||
m_Device.MaxLineWidth())
|
||||
: 1.0f);
|
||||
cmd.setDepthTestEnable(s.depthTest ? VK_TRUE : VK_FALSE);
|
||||
cmd.draw(s.count, 1, s.first, 0);
|
||||
}
|
||||
cmd.setLineWidth(1.0f); // 恢复默认,防状态外泄
|
||||
}
|
||||
|
||||
cmd.endRendering();
|
||||
})
|
||||
// Write use(WAW 链):保证排在 Forward/ForwardTrans 之后、
|
||||
|
||||
@@ -170,6 +170,12 @@ void Device::PickPhysicalDevice()
|
||||
m_SamplerAnisotropySupported = features2.features.samplerAnisotropy == VK_TRUE;
|
||||
m_MaxSamplerAnisotropy = props.limits.maxSamplerAnisotropy;
|
||||
|
||||
// 宽线(core 特性位;调试图元 >1 线宽必需,桌面 GPU 普遍支持)
|
||||
m_WideLinesSupported = features2.features.wideLines == VK_TRUE;
|
||||
// 线宽设备范围(像素):[0]=下限(普遍 1.0),[1]=上限——debug 线
|
||||
// 宽钳制用。wideLines 不支持时只有 1.0 合法
|
||||
m_MaxLineWidth = props.limits.lineWidthRange[1];
|
||||
|
||||
// core 特性:MDI(drawCount>1)必需
|
||||
m_MultiDrawIndirectSupported = features2.features.multiDrawIndirect == VK_TRUE;
|
||||
// 1.2 特性:drawIndexedIndirectCount 必需(VUID-04445)
|
||||
@@ -192,6 +198,8 @@ void Device::PickPhysicalDevice()
|
||||
ENGINE_INFO(" Dynamic Rendering Local Read (1.4): {}", localReadSupported ? "Yes" : "No");
|
||||
ENGINE_INFO(" Push Descriptor (1.4): {}", pushDescriptorSupported ? "Yes" : "No");
|
||||
ENGINE_INFO(" Shader Object (EXT): {}", m_ShaderObjectSupported ? "Yes" : "No");
|
||||
ENGINE_INFO(" Wide Lines (1.0 core): {}",
|
||||
m_WideLinesSupported ? "Yes" : "No");
|
||||
ENGINE_INFO(" Multi Draw Indirect (1.0 core): {}",
|
||||
m_MultiDrawIndirectSupported ? "Yes" : "No");
|
||||
ENGINE_INFO(" Draw Indirect Count (1.2): {}",
|
||||
@@ -261,6 +269,8 @@ void Device::CreateLogicalDevice()
|
||||
features.setMultiDrawIndirect(m_MultiDrawIndirectSupported ? VK_TRUE : VK_FALSE);
|
||||
// 纹理各向异性过滤(材质掠射角清晰度)
|
||||
features.setSamplerAnisotropy(m_SamplerAnisotropySupported ? VK_TRUE : VK_FALSE);
|
||||
// 宽线(调试图元线宽 >1;不支持时 pass 侧钳回 1.0)
|
||||
features.setWideLines(m_WideLinesSupported ? VK_TRUE : VK_FALSE);
|
||||
|
||||
// ========================================================
|
||||
// 启用 1.4 特性链
|
||||
|
||||
Reference in New Issue
Block a user