阶段性合并:动画重定向、碎裂共享库、第三人称玩法、编辑器面板与 vendored ufbx
- 动画:HumanoidRig 三层架构(standard 标准槽 / dialects 方言映射 / mapper 配对), 指/趾统一走世界增量两遍式烘焙;AnimationAttach 拆为纯算法 + facade;修正 HumanoidBasis 行走基朝向与跨骨架去重、根运动通道 - 物理:CharacterController 角色互碰撞 / shapeOffset / 接触事件;PhysicsWorld 接触合成与 字符冲量判定 - 渲染与材质:Model 多材质按材质拆子 mesh 与贴图路径解析、DDS 直传、GLB 内嵌纹理落盘、 不透明通道 alpha 固定 1.0、glTF FlipUVs 对齐;SceneSerializer 支持 meshIndex 与材质覆盖 - 脚本:FractureLib 抽为共享库(HostView 桥接)、WallFracture 参数直绑、新增 ThirdPersonCamera / Follower / WASDMove 玩法;移除 GameDirector / PlayerController / Target - 编辑器:新增 GamepadPanel、LogPanel(LogRing),改进 Inspector / 材质 / 统计 / 视口面板 - 依赖:vendored ufbx(FBX 5.x/6.x/7.x 全版本解析,替代 assimp 对 FBX 6.1 的拒读)
This commit is contained in:
@@ -16,6 +16,7 @@
|
||||
#define HELLOVULKAN_ANIMATIONATTACH_H
|
||||
|
||||
#include <Engine/Core/Types.h>
|
||||
#include <Engine/Renderer/Model.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -51,6 +52,16 @@ namespace Engine {
|
||||
AttachResult AttachClipsToModel(Model& target, ParsedModel&& src,
|
||||
const std::string& sourcePath);
|
||||
|
||||
// 纯算法核心(headless 探针可直测):轨道重映射进 dstNodes
|
||||
// 坐标系,产出的 clips 追加到 outExtra。幂等/命名冲突检查
|
||||
// 基于 existingClips。AttachClipsToModel 只是
|
||||
// "取 nodes/clips + 回填" 的薄封装
|
||||
AttachResult AttachClipsToNodes(
|
||||
const std::vector<ModelNode>& dstNodes,
|
||||
const std::vector<AnimationClip>& existingClips,
|
||||
ParsedModel&& src, const std::string& sourcePath,
|
||||
std::vector<AnimationClip>& outExtra);
|
||||
|
||||
} // namespace Animation
|
||||
|
||||
|
||||
|
||||
@@ -64,15 +64,19 @@ namespace Engine::Animation {
|
||||
// outGlobals 尺寸 = model.GetNodes().size(),父序 DFS 累乘。
|
||||
// (crossfade 双层 = EvaluateLayers 的 count==2 特例,同一套
|
||||
// 加权混合数学,消掉历史双份代码)
|
||||
// inPlace = 丢弃动画子树根节点的位移(Unity in-place 语义:
|
||||
// 根运动开关关闭,原地播放,旋转保留)
|
||||
void Evaluate(const Model& model, const AnimationClip& cur,
|
||||
f32 curTime, const CrossfadeState& xf,
|
||||
std::vector<glm::mat4>& outGlobals);
|
||||
std::vector<glm::mat4>& outGlobals,
|
||||
bool inPlace = false);
|
||||
|
||||
// N 层加权混合(BlendTree / 状态机过渡的通用入口):
|
||||
// 逐层采样局部 TRS → 权重归一化混合(pos/scl lerp、rot 渐进
|
||||
// slerp)→ global 累乘。全空/全零权重 = rest 姿态
|
||||
void EvaluateLayers(const Model& model, const PoseLayer* layers,
|
||||
u32 count, std::vector<glm::mat4>& outGlobals);
|
||||
u32 count, std::vector<glm::mat4>& outGlobals,
|
||||
bool inPlace = false);
|
||||
|
||||
// 绑定姿势:全节点 untouched = localTransform 原样累乘。
|
||||
// 与渲染 identity palette 的表现一致(无动画/组件禁用实体的
|
||||
|
||||
@@ -1,13 +1,24 @@
|
||||
//
|
||||
// HumanoidRig:Unity Avatar 语义的跨骨架重定向映射。
|
||||
//
|
||||
// 资产组装现实:角色模型与动画 clip 是两套独立资产,骨骼命名完全
|
||||
// 不同源(英文/Mixamo/UE/Blender/MMD 日文)。Unity Humanoid 的解法
|
||||
// 是把任意命名骨架映射到标准人形槽位(Avatar bone mapping),动画
|
||||
// 经槽位转换后即可驱动任意人形角色。
|
||||
// 架构分层(对齐 Unity Mecanim / UE IK Retargeter 的做法):
|
||||
//
|
||||
// 这里实现其中枢:名字归一化 → 槽位识别(关键词表,en/Mixamo/MMD)
|
||||
// → 源/目标骨架间生成 node 下标映射对。同名直映是映射的特例。
|
||||
// ┌─ standard ─ 标准人形拓扑:HumanoidSlot 52 槽(主干 6 + 臂链
|
||||
// │ 4×2 + 指骨 15×2 + 腿链 3×2 + 趾 2),带侧别/镜像/
|
||||
// │ 指骨结构化访问。重定向的"标准空间"。
|
||||
// ├─ dialects ─ 命名方言数据表:各来源(Mixamo/Rigify/UE/MMD)的
|
||||
// │ "槽位 → 归一化骨名"候选模式,纯数据无特判。命名
|
||||
// │ 习惯差异全部收敛在这层(Unity 把 token 规则编成
|
||||
// │ 数据表、UE 用链名映射,同思想:识别=查表)。
|
||||
// └─ mapper ── 归一化(NormalizeLower → side 抽取 → 分隔符剥离)
|
||||
// + 查表识别(MatchHumanoidSlot)+ 跨骨架配对
|
||||
// (AutoMapHumanoid:同名直映优先,余下槽位一对一)。
|
||||
//
|
||||
// 动画重定向本身在 AnimationAttach:src 动画经映射对烘焙到 dst
|
||||
// 骨架(两遍式 T-pose 参考搬运),本模块只负责"名字 → 槽位 → 配对"。
|
||||
//
|
||||
// 资产组装现实:角色模型与动画 clip 是两套独立资产,骨骼命名完全
|
||||
// 不同源。同名直映是映射的特例(同源拆分资产零损耗)。
|
||||
//
|
||||
|
||||
#ifndef HELLOVULKAN_HUMANOIDRIG_H
|
||||
@@ -16,13 +27,24 @@
|
||||
#include <Engine/Core/Types.h>
|
||||
#include <Engine/Renderer/Model.h>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace Engine::Animation {
|
||||
|
||||
// 标准人形槽位(最小主干集,不含手指/表情——主干驱动即可行走)
|
||||
// ---- standard:标准人形槽位(Unity Avatar 52 骨拓扑)----
|
||||
//
|
||||
// 布局保证算术可推导(镜像/指骨反查都走位运算友好的分段):
|
||||
// [0..5] 主干:Hips→Spine→Chest→UpperChest→Neck→Head
|
||||
// [6..9] 左臂链:Shoulder→UpperArm→LowerArm→Hand
|
||||
// [10..24] 左指骨:5 指 × 3 段(Thumb/Index/Middle/Ring/Pinky,
|
||||
// 段 1=近节 → 3=末节,与 Mixamo HandThumb1-3、Rigify
|
||||
// .01-.03、UE Proximal/Distal 对齐)
|
||||
// [25..28] 左腿链:UpperLeg→LowerLeg→Foot→Toes
|
||||
// [29..51] 右侧镜像(+23 偏移,镜像布局与左完全同构)
|
||||
enum class HumanoidSlot : u32 {
|
||||
Hips = 0,
|
||||
Spine,
|
||||
@@ -34,38 +56,113 @@ namespace Engine::Animation {
|
||||
LeftUpperArm,
|
||||
LeftLowerArm,
|
||||
LeftHand,
|
||||
LeftThumb1, // 指骨区起点(FingerSlot 算术依赖连续布局)
|
||||
LeftThumb2,
|
||||
LeftThumb3,
|
||||
LeftIndex1,
|
||||
LeftIndex2,
|
||||
LeftIndex3,
|
||||
LeftMiddle1,
|
||||
LeftMiddle2,
|
||||
LeftMiddle3,
|
||||
LeftRing1,
|
||||
LeftRing2,
|
||||
LeftRing3,
|
||||
LeftPinky1,
|
||||
LeftPinky2,
|
||||
LeftPinky3,
|
||||
LeftUpperLeg,
|
||||
LeftLowerLeg,
|
||||
LeftFoot,
|
||||
LeftToes,
|
||||
RightShoulder,
|
||||
RightUpperArm,
|
||||
RightLowerArm,
|
||||
RightHand,
|
||||
LeftUpperLeg,
|
||||
LeftLowerLeg,
|
||||
LeftFoot,
|
||||
RightThumb1,
|
||||
RightThumb2,
|
||||
RightThumb3,
|
||||
RightIndex1,
|
||||
RightIndex2,
|
||||
RightIndex3,
|
||||
RightMiddle1,
|
||||
RightMiddle2,
|
||||
RightMiddle3,
|
||||
RightRing1,
|
||||
RightRing2,
|
||||
RightRing3,
|
||||
RightPinky1,
|
||||
RightPinky2,
|
||||
RightPinky3,
|
||||
RightUpperLeg,
|
||||
RightLowerLeg,
|
||||
RightFoot,
|
||||
RightToes,
|
||||
Count
|
||||
};
|
||||
|
||||
// 槽位侧别(左右对称骨骼的识别结果)
|
||||
enum class Side : u8 { None, Left, Right };
|
||||
|
||||
// 槽位显示名("Hips"/"LeftUpperArm"...;调试映射表用)
|
||||
// 指别(0=拇指 … 4=小指;方言表里 Mixamo pinky / UE little 同槽)
|
||||
enum class FingerKind : u32 { Thumb = 0, Index, Middle, Ring, Pinky };
|
||||
|
||||
// ---- standard:槽位结构化访问 ----
|
||||
|
||||
// 槽位侧别(主干槽返回 None;镜像侧与枚举名一致)
|
||||
Side SideOfSlot(HumanoidSlot slot);
|
||||
|
||||
// 左右镜像(主干槽原样返回)
|
||||
HumanoidSlot MirrorSlot(HumanoidSlot slot);
|
||||
|
||||
// 指骨槽位合成:(side, 指, 段 1..3) → 槽位
|
||||
HumanoidSlot FingerSlot(Side side, FingerKind kind, u32 seg);
|
||||
|
||||
// 指骨槽位反解(非指骨槽位返回 false)
|
||||
bool SlotFinger(HumanoidSlot slot, Side& side, FingerKind& kind,
|
||||
u32& seg);
|
||||
|
||||
// 槽位显示名(Unity 语义:"Hips"/"LeftUpperArm"/"LeftHandThumb2"...;
|
||||
// 调试映射表/Inspector 明细行用)
|
||||
const char* HumanoidSlotName(HumanoidSlot slot);
|
||||
|
||||
// 名字归一化 + 槽位识别。
|
||||
// ---- mapper:名字归一化 + 槽位识别 ----
|
||||
|
||||
// 名字归一化 + 查方言表识别槽位。
|
||||
// "mixamorig:LeftForeArm" → {LowerArm, Left};"左ひじ" → {LowerArm, Left};
|
||||
// "Upper Arm_L" → {UpperArm, Left}。识别失败 ok=false。
|
||||
// "Upper Arm_L" → {UpperArm, Left};"DEF-thumb.01.L" → {Thumb1, Left};
|
||||
// "Thumb Proximal_R" → {Thumb1, Right}。识别失败 ok=false。
|
||||
HumanoidSlot MatchHumanoidSlot(std::string_view name, Side& side,
|
||||
bool& ok);
|
||||
|
||||
// 跨骨架自动映射:返回 (srcNodeIdx, dstNodeIdx) 对列表。
|
||||
// 同名 node 直映优先(同源拆分资产零损耗),余下按槽位匹配;
|
||||
// 一槽多候选时取语义关键词优先级表中的顺序(先高后低)。
|
||||
// 同名 node 直映优先(同源拆分资产零损耗),余下按槽位一对一配对;
|
||||
// 同槽多候选时取方言表内的优先序(先高后低)。
|
||||
std::vector<std::pair<u32, u32>> AutoMapHumanoid(
|
||||
const std::vector<ModelNode>& srcNodes,
|
||||
const std::vector<ModelNode>& dstNodes);
|
||||
|
||||
// 估计源/目标骨架的根对齐旋转(rest 姿态 basis 对齐,src 世界 →
|
||||
// dst 世界)。跨骨架 retarget 时两套骨架常不同朝向(kwy DummyDoll
|
||||
// Z-up vs X Bot Y-up),增量变换须经该旋转共轭才能跨空间传递。
|
||||
// up = 头 - 双脚中点;right = 右臂 - 左臂(去 up 分量);
|
||||
// fwd = up × right,脚趾朝向消歧。槽位不全(缺头/双脚/双臂)
|
||||
// 或 rest 退化时返回 identity。
|
||||
glm::quat EstimateHumanoidRootAlignment(
|
||||
const std::vector<ModelNode>& srcNodes,
|
||||
const std::vector<ModelNode>& dstNodes);
|
||||
|
||||
// 估计骨架的 T-pose 参考姿势(每节点**世界**旋转)。
|
||||
// 人形动画资产的摆动量以 T-pose 为零位;rest 是 A-pose(臂下垂)
|
||||
// 时,旋转增量直接搬运会过摆(臂越过中线)。此处把臂链
|
||||
// (upper_arm 子树)绕肩刚体摆到水平侧向作为参考,其余骨取 rest
|
||||
// (手指/趾走 rest 零位局部增量通道,见 AnimationAttach——指根
|
||||
// 扇形编码在 rest 平移里,不参与世界系零位搬运);rest 本为
|
||||
// T-pose 的资产摆动量≈0 自动退化。行走基不可用时全 rest。
|
||||
std::vector<glm::quat> EstimateHumanoidTPose(
|
||||
const std::vector<ModelNode>& nodes,
|
||||
const std::vector<glm::mat4>& restGlobals);
|
||||
|
||||
} // namespace Engine::Animation
|
||||
|
||||
#endif // HELLOVULKAN_HUMANOIDRIG_H
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
//
|
||||
// LogRing.h:进程级日志环形缓冲(编辑器 Log Panel 的数据源)。
|
||||
//
|
||||
// RingSink 注册进默认 logger 的 sinks 后,所有日志(引擎 + 脚本经
|
||||
// ScriptLog/ENGINE_* 打印的)自动落缓冲;面板按 version 增量感知、
|
||||
// Snapshot 整体拉取。头文件设施(sink_it_ 内联),无 cpp。
|
||||
//
|
||||
#ifndef HELLOVULKAN_LOGRING_H
|
||||
#define HELLOVULKAN_LOGRING_H
|
||||
|
||||
#include <Engine/Core/Types.h>
|
||||
|
||||
#include <spdlog/sinks/base_sink.h>
|
||||
#include <spdlog/details/log_msg.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Engine::Debug {
|
||||
|
||||
struct LogEntry {
|
||||
spdlog::level::level_enum level;
|
||||
std::string msg; // 整行(已按 ring pattern 格式化,无色码)
|
||||
};
|
||||
|
||||
class LogRingBuffer {
|
||||
public:
|
||||
static constexpr size_t kCap = 2048;
|
||||
|
||||
// 门控:启动期系统初始化日志(Vulkan/UBO/IBL...)不进面板,
|
||||
// 与 Unity Console 语义一致(只收运行期游戏侧日志)。
|
||||
// Application::Run 进入主循环时开闸
|
||||
bool IsEnabled() const {
|
||||
return m_Enabled.load(std::memory_order_relaxed);
|
||||
}
|
||||
void SetEnabled(bool v) {
|
||||
m_Enabled.store(v, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void Push(spdlog::level::level_enum lvl, std::string msg) {
|
||||
std::lock_guard<std::mutex> lk(m_Mutex);
|
||||
if (m_Entries.size() < kCap) {
|
||||
m_Entries.push_back({lvl, std::move(msg)});
|
||||
} else {
|
||||
m_Entries[m_Head] = {lvl, std::move(msg)};
|
||||
m_Head = (m_Head + 1) % kCap; // 满后覆写最旧
|
||||
}
|
||||
++m_Version;
|
||||
}
|
||||
|
||||
// 快照(满环时按时间序重排)
|
||||
std::vector<LogEntry> Snapshot() const {
|
||||
std::lock_guard<std::mutex> lk(m_Mutex);
|
||||
if (m_Entries.size() < kCap) {
|
||||
return m_Entries;
|
||||
}
|
||||
std::vector<LogEntry> out;
|
||||
out.reserve(kCap);
|
||||
out.insert(out.end(), m_Entries.begin() + static_cast<long>(m_Head),
|
||||
m_Entries.end());
|
||||
out.insert(out.end(), m_Entries.begin(),
|
||||
m_Entries.begin() + static_cast<long>(m_Head));
|
||||
return out;
|
||||
}
|
||||
|
||||
void Clear() {
|
||||
std::lock_guard<std::mutex> lk(m_Mutex);
|
||||
m_Entries.clear();
|
||||
m_Head = 0;
|
||||
++m_Version;
|
||||
}
|
||||
|
||||
u64 Version() const {
|
||||
return m_Version.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
private:
|
||||
mutable std::mutex m_Mutex;
|
||||
std::vector<LogEntry> m_Entries;
|
||||
size_t m_Head = 0;
|
||||
std::atomic<u64> m_Version{0};
|
||||
std::atomic<bool> m_Enabled{false};
|
||||
};
|
||||
|
||||
inline LogRingBuffer& LogRing() {
|
||||
static LogRingBuffer inst;
|
||||
return inst;
|
||||
}
|
||||
|
||||
// spdlog sink 适配:log() 整行入环。pattern 由装配端设置(无色码)
|
||||
class RingSink : public spdlog::sinks::base_sink<std::mutex> {
|
||||
protected:
|
||||
void sink_it_(const spdlog::details::log_msg& msg) override {
|
||||
if (!LogRing().IsEnabled()) {
|
||||
return; // 启动期:只落终端/文件,不进面板
|
||||
}
|
||||
spdlog::memory_buf_t formatted;
|
||||
base_sink<std::mutex>::formatter_->format(msg, formatted);
|
||||
std::string line(formatted.data(), formatted.size());
|
||||
while (!line.empty() &&
|
||||
(line.back() == '\n' || line.back() == '\r')) {
|
||||
line.pop_back();
|
||||
}
|
||||
LogRing().Push(msg.level, std::move(line));
|
||||
}
|
||||
void flush_() override {}
|
||||
};
|
||||
|
||||
} // namespace Engine::Debug
|
||||
|
||||
#endif //HELLOVULKAN_LOGRING_H
|
||||
@@ -37,29 +37,27 @@ namespace Engine
|
||||
void AddAllocation(u64 bytes);
|
||||
void AddDeallocation(u64 bytes);
|
||||
|
||||
// ---- 采样进程真实内存 ----
|
||||
void SampleProcessMemory();
|
||||
|
||||
// ---- 打印 ----
|
||||
void Print() const;
|
||||
void PrintDetailed() const;
|
||||
void PrintComparison() const;
|
||||
// ---- 只读快照(面板/遥测消费)----
|
||||
struct Snapshot
|
||||
{
|
||||
u64 textureCount = 0;
|
||||
u64 textureBytes = 0;
|
||||
u64 meshCount = 0;
|
||||
u64 meshBytes = 0;
|
||||
u64 bufferCount = 0;
|
||||
u64 bufferBytes = 0;
|
||||
u64 memoryBlockBytes = 0; // MemoryAllocator 已分配块
|
||||
u64 memoryUsedBytes = 0; // 块内实际占用
|
||||
u64 frameAllocation = 0; // 本帧新增(BeginFrame 清零)
|
||||
u64 frameDeallocation = 0; // 本帧释放
|
||||
u64 totalAllocation = 0;
|
||||
u64 totalDeallocation = 0;
|
||||
};
|
||||
Snapshot TakeSnapshot() const;
|
||||
|
||||
private:
|
||||
Stats() = default;
|
||||
|
||||
struct ProcessMemory
|
||||
{
|
||||
u64 VmSize = 0; // /proc/self/status VmSize
|
||||
u64 Rss = 0; // /proc/self/status VmRSS
|
||||
u64 Pss = 0; // smaps_rollup
|
||||
u64 SharedClean = 0;
|
||||
u64 PrivateDirty = 0;
|
||||
u64 PrivateClean = 0;
|
||||
};
|
||||
|
||||
ProcessMemory ReadProcessMemory() const;
|
||||
|
||||
mutable std::mutex m_Mutex;
|
||||
|
||||
// 资源计数
|
||||
@@ -81,9 +79,6 @@ namespace Engine
|
||||
u64 m_FrameDeallocation = 0;
|
||||
u64 m_TotalAllocation = 0;
|
||||
u64 m_TotalDeallocation = 0;
|
||||
|
||||
// 进程快照
|
||||
ProcessMemory m_ProcessMem{};
|
||||
};
|
||||
|
||||
#define STATS Engine::Stats::Get()
|
||||
|
||||
@@ -480,6 +480,9 @@ namespace Engine::ECS {
|
||||
// 重力倍率(UE CharacterMovement.GravityScale 同位):真实 9.81
|
||||
// 对人形角色偏"棉花糖"(滞空 1.2s+),游戏手感普遍 2~3 倍
|
||||
f32 gravityScale = 2.0f;
|
||||
// 形状附加偏移:叠加在脚底约定(胶囊底端=原点)之上,模型
|
||||
// pivot 不在脚底时对齐视觉网格用(Y+ 胶囊上抬)
|
||||
glm::vec3 shapeOffset{0.0f};
|
||||
|
||||
bool operator==(const CharacterControllerComponent&) const = default;
|
||||
// 实体原点 = 脚底(胶囊底端),符合角色 pivot 习惯
|
||||
|
||||
@@ -6,6 +6,11 @@
|
||||
// "看不到"它(无碰撞解算,仅查询)。由 PhysicsWorld 对账创建/销毁并
|
||||
// 在 PhysicsSystem::Update 之后驱动(Jolt 官方建议序)。
|
||||
//
|
||||
// 角色间碰撞(Jolt 设计行为):CharacterVirtual 彼此默认穿透,需经
|
||||
// CharacterVsCharacterCollision 显式互查。PhysicsWorld 持有一份
|
||||
// CharacterVsCharacterCollisionSimple 并注入每个角色(SetCharacterCollision),
|
||||
// 使角色扫掠时也撞其它角色——进/出列表由本类构造/析构自动维护。
|
||||
//
|
||||
// 速度语义(游戏逻辑职责分工):
|
||||
// - 水平移动:游戏逻辑每帧 SetCharacterVelocity 喂意图速度
|
||||
// - 垂直分量:本类自动积分重力(着地清零向下分量);Jump 置垂直速度
|
||||
@@ -36,13 +41,21 @@ namespace Engine {
|
||||
f32 mass = 70.0f;
|
||||
f32 maxSlopeAngle = 45.0f; // 度
|
||||
f32 stepHeight = 0.4f; // 台阶攀爬高
|
||||
// 形状附加偏移(相对"胶囊底端钉实体原点"的脚底约定):
|
||||
// 模型 pivot 不在脚底时对齐用(Y+ 上抬)
|
||||
glm::vec3 shapeOffset{0.0f};
|
||||
|
||||
bool operator==(const Params&) const = default;
|
||||
};
|
||||
|
||||
CharacterController(JPH::PhysicsSystem& system, const Params& params,
|
||||
const glm::vec3& position);
|
||||
~CharacterController() = default; // Ref 引用计数自动释放
|
||||
// 析构:先从角色间碰撞列表摘除(防悬垂指针),Ref 随即释放
|
||||
~CharacterController();
|
||||
|
||||
// 注入角色间碰撞列表(PhysicsWorld 单份共享):登记本角色并打开
|
||||
// 互查。构造后由 PhysicsWorld 创建时调用一次;列表须比本对象活得久
|
||||
void SetCharacterCollision(JPH::CharacterVsCharacterCollisionSimple& list);
|
||||
|
||||
// 每物理步调用:重力积分 + ExtendedUpdate(扫掠滑动 + 贴地 +
|
||||
// 上台阶)。allocator 与 PhysicsSystem::Update 串行复用同一池
|
||||
@@ -59,6 +72,16 @@ namespace Engine {
|
||||
|
||||
const Params& GetParams() const { return m_Params; }
|
||||
|
||||
// ---- 碰撞事件支持(PhysicsWorld 合成 Enter/Exit 用)----
|
||||
// 本步活跃接触列表(CharacterVirtual 直通;含对方体 ID/命中点/
|
||||
// 法线/距离)。CharacterVirtual 非刚体,Jolt 的 ContactListener
|
||||
// 不会为它回调——由 PhysicsWorld 读本列表对比出事件边沿
|
||||
const JPH::CharacterVirtual::ContactList& GetActiveContacts() const;
|
||||
// 上一次 Update 扫掠"之前"的速度(撞击强度判定)。Jolt 在扫掠
|
||||
// 中会抵消朝向墙面/陡面的速度分量(CancelVelocityTowardsSteepSlopes),
|
||||
// 扫掠后再读 GetLinearVelocity 往往已归零——撞墙判定必须用这里
|
||||
glm::vec3 GetSweepVelocity() const;
|
||||
|
||||
// 调试渲染:非 body,PhysicsSystem::DrawBodies 画不到,须手动
|
||||
// (脚本在物理位姿/脚底原点处画胶囊线框)
|
||||
void DebugDraw(JPH::DebugRenderer& renderer) const;
|
||||
@@ -67,6 +90,8 @@ namespace Engine {
|
||||
Params m_Params;
|
||||
JPH::Ref<JPH::CharacterVirtual> m_Character;
|
||||
JPH::CharacterVirtual::ExtendedUpdateSettings m_Ext;
|
||||
JPH::CharacterVsCharacterCollisionSimple* m_VsChar = nullptr; // 非拥有
|
||||
JPH::Vec3 m_SweepVelocity{0.0f, 0.0f, 0.0f}; // 扫掠前速度快照
|
||||
};
|
||||
|
||||
} // namespace Engine
|
||||
|
||||
@@ -102,6 +102,9 @@ namespace Engine {
|
||||
// 角色脚底位置(物理权威世界坐标):调试图元/特效锚点用它,
|
||||
// 而不是 Transform().GetPosition()——后者在有父级时是本地坐标
|
||||
glm::vec3 GetCharacterPosition(u32 entityBits) const;
|
||||
// 角色"撞击强度"速度(上一次扫掠前的速度):撞墙/碎墙等判定用它,
|
||||
// 而非 GetCharacterVelocity——后者已被 Jolt 抵消掉朝向墙面的分量
|
||||
glm::vec3 GetCharacterImpactVelocity(u32 entityBits) const;
|
||||
|
||||
private:
|
||||
// 创建时缓存的参数(变更检测 → 重建 body)
|
||||
@@ -138,7 +141,8 @@ namespace Engine {
|
||||
// 角色非刚体(不进 PhysicsSystem),无 body 清理负担
|
||||
void SyncCharacters();
|
||||
// 每物理步(PhysicsSystem::Update 之后,Jolt 官方建议序):
|
||||
// 重力积分 + 扫掠 → 写回 TransformComponent(标脏)
|
||||
// 重力积分 + 扫掠 → 写回 TransformComponent(标脏)+ 合成角色
|
||||
// 碰撞事件(CharacterVirtual 不进 ContactListener,见函数注释)
|
||||
void UpdateCharacters(f32 dt);
|
||||
// Mesh 碰撞体:实体视觉网格 → Jolt 形状(Static=MeshShape /
|
||||
// Dynamic=ConvexHull)。无 MeshRenderer 或构建失败返回 null(回退 Box)
|
||||
@@ -165,9 +169,16 @@ namespace Engine {
|
||||
std::unordered_map<u32, ConstraintRecord> m_Constraints;
|
||||
|
||||
// ---- 角色(entityBits → CharacterVirtual 封装)----
|
||||
// 角色间碰撞互查表:Jolt CharacterVirtual 默认彼此穿透,须显式注入
|
||||
// CharacterVsCharacterCollisionSimple 才互撞。声明在 m_Characters
|
||||
// 之前(析构更晚)——角色析构时会回表 Remove,列表须后于角色释放
|
||||
Scope<JPH::CharacterVsCharacterCollisionSimple> m_VsCharacterCollision;
|
||||
struct CharacterEntry {
|
||||
std::unique_ptr<CharacterController> cc;
|
||||
glm::vec3 lastPos{0.0f}; // 编辑器拖动检测
|
||||
// 上一步活跃接触的"对方实体"集合:本步对比出角色碰撞
|
||||
// Enter/Exit(CharacterVirtual 非刚体,ContactListener 不收)
|
||||
std::unordered_set<u32> contacts;
|
||||
};
|
||||
std::unordered_map<u32, CharacterEntry> m_Characters;
|
||||
|
||||
|
||||
@@ -47,7 +47,13 @@ namespace Engine {
|
||||
// 无填充歧义),加字段必须双端同步(simple_shader*.frag)
|
||||
struct MaterialParams {
|
||||
// x = metallic 因子,y = roughness 因子,z = 独立粗糙度图开关,w = MASK 镂空开关
|
||||
glm::vec4 factors{0.0f, 0.5f, 0.0f, 0.0f};
|
||||
// 默认 = 电介质表面推荐值(Unity/UE 同款):
|
||||
// metallic = 0 皮肤/头发/布料等非金属的正确值
|
||||
// roughness = 1.0 因子语义是"乘贴图",1.0 = 贴图值直用(同 glTF
|
||||
// roughnessFactor 缺省);配合兜底 MR 纹理 G=128
|
||||
// 得 effective roughness ≈ 0.5,不再是 ×0.5 后的
|
||||
// 0.25 假高光(ufbx 路径不写 factors,全靠这里兜底)
|
||||
glm::vec4 factors{0.0f, 1.0f, 0.0f, 0.0f};
|
||||
// glTF baseColorFactor:rgb 乘进 albedo,a 作 BLEND 透明度
|
||||
glm::vec4 baseColor{1.0f, 1.0f, 1.0f, 1.0f};
|
||||
// glTF 表面扩展(KHR_materials_ior / transmission / volume /
|
||||
@@ -55,7 +61,14 @@ namespace Engine {
|
||||
// y = 透射率(0 = 普通表面),z = 自发光强度,w = 体积厚度
|
||||
//(折射屏幕偏移的世界距离,0 = 无弯曲直透)
|
||||
glm::vec4 surface{1.5f, 0.0f, 1.0f, 0.0f};
|
||||
// 杂项语义:x = unlit(KHR_materials_unlit,shader 早退直出 albedo)
|
||||
// 杂项语义:
|
||||
// x = unlit(KHR_materials_unlit,shader 早退直出 albedo)
|
||||
// y = UV 左右镜像(u → 1-u,1 = 开启)
|
||||
// z = UV 上下镜像(v → 1-v,1 = 开启)
|
||||
// w = 保留
|
||||
// 镜像作用于材质全部纹理槽,叠加在每槽 UV 变换(uvA/uvT)之后:
|
||||
// 用于纠正源资产 UV 朝向(部分角色 rip 的 UV 与 glTF 上左原点
|
||||
// 约定相反,贴图整体翻转)。材质级语义,非单槽
|
||||
glm::vec4 misc{0.0f, 0.0f, 0.0f, 0.0f};
|
||||
// glTF emissiveFactor:自发光颜色(rgb;黑 = 不发光)。emissive
|
||||
// 槽默认兜底纹理是纯黑,无贴图时 Emissive Strength 乘出来恒黑
|
||||
@@ -63,8 +76,10 @@ namespace Engine {
|
||||
glm::vec4 emissiveFactor{0.0f, 0.0f, 0.0f, 0.0f};
|
||||
// 每槽 KHR_texture_transform(glTF2 每个 textureInfo 独立声明
|
||||
// offset/scale/rotation——烘焙单份顶点 UV 无法表达"一材质多槽
|
||||
// 各异变换",收敛为 shader 侧线性式 uv' = A·uv + t,作用在本引擎
|
||||
// v-up 顶点 UV 空间,换算推导见 Model.cpp glTF 分支注释)。
|
||||
// 各异变换",收敛为 shader 侧线性式 uv' = A·uv + t,作用在 glTF
|
||||
// 规范 UV 空间(v=0 = 图像顶);顶点 UV 经 aiProcess_FlipUVs 已
|
||||
// 归一至该空间,A/t 即规范式 off + R(rot)·S(scale)·uv 的摊平,
|
||||
// 换算推导见 Model.cpp glTF 分支注释)。
|
||||
// uvA[slot] = (a11, a12, a21, a22)(行主 2×2),uvT[slot] = (tx, ty, 0, 0)。
|
||||
// 长度必须 = Material::kSlotCount;std140 vec4 数组步长 16B 与
|
||||
// CPU 布局天然对齐
|
||||
@@ -72,6 +87,11 @@ namespace Engine {
|
||||
{1.0f, 0.0f, 0.0f, 1.0f}, {1.0f, 0.0f, 0.0f, 1.0f},
|
||||
{1.0f, 0.0f, 0.0f, 1.0f}, {1.0f, 0.0f, 0.0f, 1.0f}};
|
||||
glm::vec4 uvT[6] = {};
|
||||
// albedo 贴图 alpha 作细节叠加遮罩(脸部腮红/卧蚕等局部效果):
|
||||
// rgb = 叠加色(与 baseColor 同款直乘/直用,不做空间换算),
|
||||
// a = 强度(0 = 关闭,mix 恒等无副作用)。alpha 遮罩来自贴图本身,
|
||||
// 无 alpha 或 alpha 恒 1 的贴图开启后即等效整体染色
|
||||
glm::vec4 alphaOverlay{0.0f, 0.0f, 0.0f, 0.0f};
|
||||
};
|
||||
|
||||
class Material {
|
||||
|
||||
@@ -47,8 +47,9 @@ namespace Engine {
|
||||
// 材质资产数据段(磁盘格式 ↔ 内存值,无 GPU 状态)。
|
||||
// texPaths = 文件系统相对 CWD 路径(与模型解析产物同源,glTF/FBX
|
||||
// 纹理可直接快照);空串 = 该槽用共享默认纹理兜底。
|
||||
// glb 内嵌纹理无独立文件路径 → 对 glb 模型 Create Override 时
|
||||
// 纹理槽为空(回退默认纹理),参数/透明照常可编辑。
|
||||
// glb 内嵌纹理在模型导入期已抽到 <模型目录>/textures/ 落盘并回写为
|
||||
// 真实路径(Model::ParseFromDisk),故此处与文件纹理同语义——覆盖
|
||||
// 材质可正常继承
|
||||
struct MaterialAssetData {
|
||||
std::array<std::string, Material::kSlotCount> texPaths;
|
||||
MaterialParams params;
|
||||
|
||||
@@ -137,6 +137,12 @@ namespace Engine
|
||||
// 纯 CPU:读盘 + assimp 解析 → 几何 + 材质引用。无线程状态。
|
||||
static ParsedModel ParseFromDisk(const std::string& path);
|
||||
|
||||
// 资产显示名(编辑器 UI / clip 命名的唯一来源):取文件 stem;
|
||||
// stem 无信息量(空 / 导入资产库的 "asset")时读同目录 pathname
|
||||
// 文件取原始资产 basename,再回退父目录名。避免 UI 直接显示
|
||||
// 哈希路径(用户看到的是 id)
|
||||
static std::string AssetDisplayName(const std::string& path);
|
||||
|
||||
// GPU 构建(必须主线程):纹理上传 / mesh 缓冲 / 材质描述符。
|
||||
// 解析失败返回 nullptr。
|
||||
static Scope<Model> FromParsed(Device& device, MemoryAllocator& allocator,
|
||||
|
||||
@@ -31,6 +31,10 @@ namespace Engine {
|
||||
|
||||
class Device;
|
||||
|
||||
// 显存估算(按格式字节宽与 mip 链逐级累加;BC 压缩按 4x4 块计)——
|
||||
// 分配器台账与 Texture 资源台账共用同一算法,两边永不漂移
|
||||
u64 EstimateImageBytes(vk::Format fmt, u32 w, u32 h, u32 depth, u32 mips);
|
||||
|
||||
class MemoryAllocator {
|
||||
public:
|
||||
explicit MemoryAllocator(Device& device);
|
||||
|
||||
Reference in New Issue
Block a user