动画数据、运动网络、姿态依赖图与物理角色
范围:Base cranimation/crskeleton/crbody/creature/crmetadata;Suite crclip/crmotiontree/crparameterizedmotion/move/crextra/crposedb;Framework fwanimation;NaturalMotion 集成。静态分析,未运行。入口:Summary。
1. 这里的动画是多阶段计算系统#
RAGE 把可共享资产、每角色播放状态、姿态计算图、骨架/组件输出与游戏协调分开。角色“正在播什么”和“本帧最终骨骼矩阵是什么”属于不同层,动作状态更新和完整 pose 合成也可以有不同完成点。
move network 指动作控制图,不是网络通信模块。crClip 不只是原始关键帧:它封装元数据并隐藏底层动画或表达式存储。crCreature 也不限于骨架,还可输出 mover、灯光、粒子、blendshape、shader 变量等组件。
2. 共享定义与实例状态#
crSkeletonData/bone data 表达共享骨架结构,crSkeleton 持有具体姿态;animation、clip dictionary、move network definition 可作为资源共享,player/network/director 维护实例状态。
mvNetworkDef::Create 的注释专门要求调用者持有 outputBuffer 的所有权,直到网络更新结束并被销毁。可见“定义复用”不等于“输出缓冲自动归引擎管理”;异步生命周期必须跟随真实使用者。
Clip 引用还区分只引用 clip 与连同 dictionary 的引用。模型、动画字典和任务对象之间的共享关系直接决定 streaming 能否卸载。进入动作时只保留 clip 裸指针,忽略字典生命期,会破坏资源系统的假设。
3. 压缩不仅减少文件大小,还改变采样热路径#
crAnimChannel 有 raw/static float、vector、quaternion、量化、变长量化、间接量化、RLE、线性和 B-spline 等类型,并提供 compression/decompression cost 元信息。chunk 表示短时间段,packer 再将编辑表示整理成运行时 block stream。
crAnimation::IsRaw 的文档明确:raw 只经历无损压缩,cooked 包含有损处理,不能完全恢复为可编辑原稿。压缩容差应根据骨骼/通道用途选择,而不是给整个人体一个统一阈值。
Smallest-three quaternion 编码保存三个分量并重建另一分量,利用单位四元数约束减少数据;packer 内有重建和 normalize 索引组织。但 channelsmallestthree.h 属于 CR_DEV 条件下的通道描述,不能把该编辑对象虚函数调用当成发行版最终采样路径。应继续看 packer/block stream 和 iterator 如何解码。
收益与限制:
| 手段 | 主要收益 | 需要观察的代价 |
|---|---|---|
| 静态通道提取 | 恒定骨骼几乎不占动态采样流量 | 需要正确恢复默认 DOF |
| 量化与紧凑 block | 减少存储、驻留与读取带宽 | 末端误差、重建 CPU、误差跨骨链放大 |
| 时间 chunk | 可局部采样,避免解压整段 | 随机跳转和循环边界缓存失效 |
| nearest frame 选项 | 可能减少插值成本 | 接口明确提示结果不够平滑 |
| mover 专门处理 | 直接得到运动增量 | 跨循环、混合和时间变化必须一致 |
本仓库不能仅通过 codec 类型给出压缩率。需要真实动画资产、容差配置和工具输出才能测量。
4. Frame 是 DOF 容器,不等于一张骨骼矩阵数组#
CompositeFrame 根据 frame 的 DOF 映射填入采样结果;未匹配轨道默认清零,clear=false 时保留旧值,调用方必须先初始化或正确处理。CompositeFrameWithMover 额外将 mover DOF 转换为 delta。
这个约定使 partial-body、过滤、表达式和非骨骼参数能够共用 frame 流水线,但也会产生隐蔽错误:上帧残留、不同 skeleton 的索引映射、没有初始化的缺失通道,都可能在最终渲染前才表现出来。
frame accelerator、缓存与索引锁减少每次根据骨名查找的开销。其收益来自“提前构造和复用映射”,因此骨架重建、换装、LOD 切换或资源卸载必须正确失效这些映射。
5. Clip 元数据让动画具有玩法语义#
Clip 包含 properties、tags、events,可以让脚步音、面部事件、同步窗口或脚本可见属性跟随动画时间。fwAnimDirector 读取 VisibleToScript 属性并处理 phase 区间,包括跨循环的区间查找。
所以只“停止不可见角色骨骼采样”不意味着可以停止所有动作更新:时间推进、事件、root motion 和玩法状态可能仍必须继续。事件的 exactly-once/跨循环语义需要在时间跳跃、倒放、回放和延迟补 pose 时单独检查。
6. Move network 与参数化运动#
Move 将 definition 与 instance 分离,提供状态、子网络和参数缓冲。signal 中 flags 是持续开关,requests 每次更新后重置,需要调用者继续推入才会保持。把一次 request 当成永久状态,会得到与设计相反的行为。
参数化运动层提供 parameterization、weights、sampler、registration curve、time alignment 等对象,将输入参数映射到动作片段及其混合和时间配准。它的价值是让速度、方向等连续变化能够共享一组动作,避免离散状态爆炸。
但参数维数增长会增加内容覆盖和插值难度;稀疏采样可能出现脚滑或过渡不自然,时间对齐会影响步频和根运动。文件存在能证明能力,具体 GTA 动作如何使用仍须结合动作网络定义和资产,当前集合缺少完整运行资产,不能重建所有角色行为图。
7. Motion tree 先表达意图,Composer 再表达执行依赖#
crmtComposer 明确说明:从 motion node tree 创建 dependency tree。它提供 Animation、Blend/Merge/Add、Filter、Frame、Normalize、Skeleton、IK、Physical、Mirror、Extrapolate、ShaderVars 等操作。节点图体现动作组合语义,依赖图体现输入结果何时就绪以及临时 frame 何时可释放。
当前 composer 的结构上限包括 256 dependencies、65 栈深容量、128 frame 编号、12 个 used-frame 相关容量和 56 个 proxy 条目。它们属于不同内部结构,不能把 128 简化成“可同时存在 128 张完整 pose”,也不能将这些数字当作全部平台永久规格。
proxy 节点缓存、frame allocator/releaser 数组和 ComputeFrameOffsets 表明系统不仅安排计算,也安排临时结果的存活区间。一个 blend tree 的成本因此由 活跃节点、临时 pose 峰值、共享结果和依赖深度共同决定,而不是编辑器中总节点数。
新项目应为动画图编译器增加静态峰值估算,检查最坏过渡叠加、嵌套子图与 proxy 共享;运行时同时记录真实活跃依赖数。把每个节点都分配独立堆对象和完整 frame 往往会丢掉这套设计的重要收益。
8. 调度:Update、Compose 与 Recycle 各有完成语义#
crmtMotionTreeScheduler::Schedule 断言只能由主线程调度,检查前一轮状态 Idle,然后建立 Update → 可选 Compose → Recycle 的依赖链。Compose 通过 scratch 创建更细的依赖节点;Recycle 负责结束流程和回调。
IsComplete(updateOnly) 区分状态更新完成与整个流程完成。WaitOnComplete 支持提高优先级,等待线程通过注册的 semaphore 被通知。父 motion tree 参数又允许附属对象的计算等待父对象正确完成。
图用于说明完成层次,并非每棵树都含两个采样节点。主线程调度并不代表节点在主线程执行;同样,等待状态更新完成不代表骨架 pose 已安全读取。
9. Director 处理物理前、物理中、渲染前和镜头后#
fwAnimDirector 暴露 PrePhysics、MidPhysics、PreRender 和 PostCamera 的 Start/Wait 接口。它将 creature、move、expressions、facial rig、ragdoll、synced scene 等 component 组合起来。
这解决一个实际矛盾:角色运动要尽早提供给物理,而镜头相关 IK、最终可见性或某些附加输出要更晚确定。阶段化使不同消费者在需要时等待对应结果,而不是统一在帧初完成所有角色的全量 pose。
StartUpdatePostCamera 对已更新角色把 deltaTime 置零,或者只安排 late compose,避免同一帧再次推进动作时间。它说明“补算 pose”和“推进动画状态”必须分离。
但需要谨慎看待旧注释:当前 UpdateMove 的 ignoreVisibilityTest 标为 UNUSED_PARAM,结尾同时把 m_Updated 与 m_PoseUpdated 置 true。因此不能只凭保留的 mover-only 注释断言该入口现版本一直按不可见角色跳过完整 pose。具体裁剪还要看调用方与组件配置。报告将其作为框架能力和时序设计,而非已量测的全局动画 LOD 策略。
10. 动画与物理的双向连接#
Ragdoll component 从 creature skeleton 做 inverse pose,保存身体和根 frame,再将 frame 传回 move 或应用回 skeleton;包含对额外 DOF 的处理。NaturalMotion fragment manager 与游戏物理分成 StepPhase1/StepPhase2,动画速度和身体接触参与行为控制。
这个体系的特征是“有行为控制的物理角色”与关键帧系统共存。过渡时要处理根位置、根速度、骨骼姿态、关节体、碰撞 bound 及动画事件的一致性。突然把整个人体从物理矩阵复制回动画默认姿态,会产生位置跳变或速度不连续。
本报告没有把整个 NaturalMotion 内部行为库当成已逐行为审计;能确认的是接口、两阶段集成和姿态/速度通路。接触求解与 articulated 细节见物理。
11. Pose matching 的真实能力与规模边界#
crPoseMatcher 支持从 skeleton 和历史 skeleton 构建查询,样本含 key、time 和 point cloud。MatchPointCloud 当前实现遍历所有样本,通过 PreFilter 获取权重,计算带 early-out 的点云距离,再用 PostFilter 选择结果;返回 clip 身份、时间和对齐 transform。
它是实实在在的姿态匹配能力,可服务倒地后的起身衔接等用途;Ragdoll component 也装入相关 pose matcher 资源。但这段实现是线性扫描候选,不能据此称为现代大规模、带未来轨迹特征和索引加速的完整 motion-matching locomotion。
复杂度随候选数量和点云比较成本增长。扩大数据库时应测过滤效果、early-out 命中与最坏候选数;若目标是每角色每帧搜索数万片段,需要设计特征归一化、近邻索引、轨迹与接触约束、数据库 streaming,而不只是换一个更多样本的文件。
12. 缓存、带宽与动画性能模型#
crAnimCache 缓存解压 block 和 frame indices,底层使用 thread-safe accelerator。缺省按需创建路径仅配置 4 个 blocks,但允许通过 SetInstance 注入外部 cache;这绝不是“GTA 全局只缓存四段动画”的证据。
性能可以按以下分析模型拆解:
角色成本 ≈ 状态图更新 + 活跃 clip 解码 + DOF 混合 + IK/表达式 + 骨架传播 + 物理交换 + 调度/等待。
大量同装扮 NPC 不一定共享解码结果:播放时间、动作层、参数、骨架变化不同会改变复用条件。反过来,短而多的不同 clip 可能造成缓存抖动,少量长图也可能由依赖深度控制整帧尾部。
| 优化方向 | 必须保留的语义 | 适用边界 |
|---|---|---|
| 降低不可见 pose 频率 | 时间、事件、root motion、碰撞需求 | 镜头切换需补 pose,不能时间推进两次 |
| 过滤非必要 DOF | 附件、IK、物理仍依赖的骨骼 | 需要依赖闭包,而非只删不可见 mesh 的骨 |
| 共享/缓存采样结果 | phase、骨架映射、曲线参数一致 | 人群不同步时命中率下降 |
| 动画压缩 | 足底、手持物、面部误差要求 | 最小文件体积未必最省 CPU |
| 延迟并行计算 | 结果在消费前完成 | 等待太晚会形成帧尾长链 |
13. 新项目的验证矩阵#
应同时准备:稀疏/密集人群、多人同动作/完全不同动作、换装和骨架 LOD、快速镜头切换、过场同步、ragdoll 起身、低帧率与时间缩放。记录每阶段的角色数、活跃节点、临时 frame 峰值、cache misses、解码/IK 时间和 wait 时间。
质量验证要观察脚滑、末端误差、根轨迹、事件重复/遗漏和物理切换连续性。只测渲染帧率会漏掉这些问题。本次未执行上述实验,也不提供未经测量的角色承载数量。
14. 源码证据#
| 文件 | 关键证据 |
|---|---|
| animation.h | 92–114 Frame/mover 契约;136–139 raw/cooked |
| animchannel.h | 26–108 通道类型及成本 |
| animpacker.cpp | 索引、quantize、重建/normalize 和 legacy 转换 |
| animcache.h | 缓存接口;92–113 可替换缺省实例 |
| clip.h | 38–68 元数据、类型和引用选项 |
| move_network.h | 43–48 request/flag;95–103 outputBuffer 生命周期 |
| parameterizedmotion.h | 数据与实例分离,参数化动作抽象 |
| composer.h | 35–109 依赖生成、操作和容量 |
| motiontreescheduler.cpp | 79–175 Schedule/Attach;203 起完成与等待 |
| animdirector.cpp | 242–319 晚期更新;381–416 当前 UpdateMove;1613 起阶段等待 |
| directorcomponentragdoll.cpp | pose matcher 装入、129 起姿态互转 |
| posematcher.cpp | 265/299 skeleton 查询;472–517 线性样本匹配 |
| NaturalMotion manager.h | fragment/NM manager 的阶段接口 |