角色规模化:去掉 Skinned Mesh,不等于去掉每实例更新

自动关联目录:角色规模化:去掉 Skinned Mesh,不等于去掉每实例更新

核心判断:把大量角色从 USkeletalMeshComponent 降级为 VAT + ISM,只消除了逐骨骼求值和部分 Draw Call,并没有自动消除每实例时钟、可见性、Transform、状态选择、Custom Data 和挂点更新。规模化必须同时定义表示层级、更新频率、事件精度和附件能力,而不是只替换渲染后端。

一、业务冲突:首帧不能穿帮,远处角色又不能全量求值

本地角色进入场景时,Mesh、Skeleton、默认 Idle 和其它动作资源可能分别晚到。为了避免长时间参考姿势,加载流程把关键内容、普通预热和“本地角色已产出首个有效姿势”拆成独立 Ready 条件,并在正常基础姿势出现前播放一个私有启动 Idle。这要求动画宿主即使暂时不可见也能推进时钟、产生姿势并完成 Ready。

但把相同策略应用到大量场景角色会迅速放大成本。通用网格的 Skeletal Component 默认可以只在渲染时 Tick 并启用 UE Update Rate Optimization;一旦成为完整动画宿主,为了虚拟时钟、事件、骨骼覆盖和首帧姿势,当前实现会改成 AlwaysTickPoseAndRefreshBones 并关闭 URO。单角色正确性策略因此变成了群体 CPU 成本。

工程后来增加了 VAT + ISM 的群体表示:骨骼动画预烘焙到纹理,多个相同资源角色共享实例组件。它显著减少 Skeletal Component、骨骼求值和提交批次,但检查代码后能看到新的核心问题:隐藏实例仍逐个推进动画、比较 Transform、写入材质 Custom Data,并更新已经绑定的动画挂点。去掉 Skinned Mesh 只是换掉最贵的一段,不等于每实例工作已经消失。

二、实际实现:关键首帧走完整宿主,群体走烘焙实例后端

加载侧把角色依赖分成 Spawn Critical 与 Post-Spawn Warmup。关键资产和纹理走各自的平台请求组,全部成功才满足内容 Ready;请求保持 Lease 到场景解码结束,替换或销毁用 Generation 拒绝旧完成。数据已下载、内容可创建和本地首个正常姿势是三个不同条件,避免“文件到了”提前放行画面。

首帧阶段使用仅本地存在的启动轨道:它不复制、不产生对外事件,并复用正式动画加载链的资产版本、Skeleton 和宿主 Generation 校验。每帧检测到正常全身基础姿势已采样后,启动 Idle 短淡出;超时日志会记录 Mesh 注册、Tick、暂停、Skeleton、宿主绑定和最后更新时间,能区分资源没到、组件没 Tick 与绑定错误。

群体后端则按烘焙资源分 Bucket,每个 Bucket 一个 ISM Component。VAT 纹理保存各动画帧的骨骼矩阵,实例 Custom Data 保存当前帧、前一帧和混合权重;原 Skeletal Component 与事件排水 Tick 被关闭。稳定对象身份映射到可压缩的 Instance Index,Play/Stop/Seek/Weight 等命令适配成每实例轻量状态。

当前群体 Tick 会复制并排序对象 ID,逐个检查 Actor 是否仍在、同步可见性与 World Transform、推进动画状态,把帧坐标写回 Custom Data;即使实例不可见,状态仍继续推进。若挂接了动画 Socket,还会在 CPU 上插值当前与前一动作的挂点 Transform 并更新附属组件。

flowchart LR
    D[角色重要度与能力需求] --> F[完整骨骼宿主]
    D --> R[降频骨骼宿主]
    D --> V[VAT + ISM]
    D --> S[静态/代理表示]
    F --> C[精确 Pose / Marker / Attachment]
    R --> C
    V --> L[轻量时钟 / 帧坐标 / 有限挂点]
    S --> O[仅 Transform 与可见状态]

三、值得保留:Ready、表示与逻辑身份已经开始分层

关键内容与普通预热分开是正确的。不是所有动作都应阻塞首帧,也不能让纹理请求悄悄退回不匹配的平台通道。聚合请求保证任一关键组失败就明确失败,完成只回调一次,Lease 又防止刚 Ready 就被回收。

首个有效姿势作为独立 Ready 事实也值得保留。UE 中 USkeletalMesh 已绑定不代表 AnimInstance 已经在 Worker 产出可用 CompactPose;用实际采样结果交接启动 Idle,比依赖固定等待时间可靠。诊断同时输出资源、宿主和 Tick 条件,已经从“加载慢”推进到可定位阶段。

群体表示按资源 Bucket 共享 ISM、用稳定对象身份屏蔽 Instance Index 压缩,并在切换后关闭原 Skeletal Tick,这些都是实质性收益。VAT 仍保留当前帧、前一帧与混合权重,说明降级没有简单退化成僵硬翻页。DS 不创建这一渲染子系统,也使逻辑角色和表现后端保持了基本分离。

四、结构性缺陷:当前仍是“完整”与“群体”两档,缺少预算调度

完整宿主一旦启用就始终刷新骨骼并关闭 URO,未区分本地玩家、近景 NPC、屏外但需要事件的角色和纯远景角色。群体后端则每帧排序 ID、逐 Actor 比较 Transform、推进不可见实例,并可能对每个 Custom Data 写入触发渲染更新。它消除了骨骼求值,却保留了 O(实例数) 的 Game Thread 工作和频繁更新提交。

Attachment 是最容易漏算的成本。VAT 可以在 GPU 上恢复顶点,却无法免费给 CPU 提供任意骨骼 Transform;当前做法把选定 Socket 的轨迹也烘焙并逐实例插值。挂点数量、更新频率和附属组件数量一多,CPU 成本会重新接近被替换的骨骼链。任意骨骼脚本、精确 IK、物理布料、Morph 和完整 Marker 也不是该后端能够等价支持的能力。

预取清单与启动 Idle 修复了首帧,但尚未形成运行时 Residency 预算:哪些动作只预取元数据,哪些必须在首帧前驻留,哪些按重要度流式,仍容易靠手工清单增长。调试侧已有骨架显示、活动轨高亮、烘焙进度、Marker 预览、性能开关,以及时间、权重、Layer、Mask、BlendSpace/Montage 诊断回读;但缺少统一的“每层角色数、每层 CPU、骨骼数、附件数、上传字节和升降级原因”视图。

五、目标设计:用能力契约和 Significance 调度表现层级

目标不是让所有角色自动进入最便宜后端,而是先声明不可降级能力,再在预算内选择最低成本表示。近景玩家可能需要逐骨骼姿势、精确 Marker、IK 和多个附件;中景角色可降低求值频率并在帧间插值;远景群体只保留状态名、起播时间和有限 Socket;不可见且无玩法表现需求的角色只推进虚拟时钟,恢复可见时直接追赶相位。

void UpdatePresentation(Character& c, BudgetFrame& budget)
{
    Capabilities need = GatherNeeds(c); // 精确事件、IK、物理、附件、可见性
    Tier desired = ChooseCheapestTier(need, ComputeSignificance(c), budget);
    c.presentation.TransitionWithHysteresis(desired);

    switch (c.presentation.Tier()) {
    case FullPose:    TickPoseAndBones(c); break;
    case ReducedPose: TickOnAssignedFrame(c); InterpolatePresentation(c); break;
    case BakedCrowd:  AdvanceClock(c); BatchInstanceUpload(c); TickRequiredSockets(c); break;
    case VirtualOnly: AdvanceClockOnly(c); break;
    }
}

升降级必须传递统一的 AnimationPresentationSnapshot,至少包含状态、资产 Pin、相位、速度、权重和播放代际,不能切后端就重播。不可见实例不必每帧写 GPU 数据;逻辑时钟可用起播时间解析,重新可见时一次性计算相位。Transform 和 Custom Data 应批量收集后一次提交,Socket 也要按声明的必要集合与更新频率调度。

六、决策规则:优化目标是“每个角色每秒做多少必要工作”

场景表现策略验证指标
本地角色首帧关键内容 + 启动 Idle + 首个正常 Pose 交接各 Ready 阶段、参考姿势帧数、交接连续性
近景玩法角色完整 Pose、精确事件与必要附件GT/Worker 求值时间、骨骼数、事件误差
中景角色分帧求值并插值,保留权威相位更新频率、姿势误差、升降级抖动
远景同资源群体VAT/ISM,批量上传,有限 Socket每实例 CPU、上传字节、Bucket/Draw 数
屏外角色只推进可解析时钟,不刷新骨骼或 GPU 数据不可见更新数、恢复相位误差
表示层级切换迁移快照,不重新 Play相位、权重、状态与附件连续
DS保留玩法状态与权威事件,不创建表现后端逻辑一致、渲染对象为零

实践中最容易高估的是“换成实例化以后已经解决规模问题”。VAT/ISM 的价值非常明确,但代码也证明,成本会从骨骼求值转移到逐实例管理、GPU 参数上传和附件更新。下一步性能架构应围绕能力、重要度和预算做连续分层,并把降级原因与成本直接暴露给工具;否则两种后端只是在高成本与较低成本之间手工切换,还不是一个可控的角色规模系统。