开放世界里 200 个 NPC 每个都全速跑 AnimInstance,Update + Evaluate 的 CPU 账单会吃掉整个 GameThread。渲染侧早就用 LOD 减面了,动画侧的对应手段就是 URO(Update Rate Optimization):远处的角色不每帧更新动画,隔几帧算一次,中间帧用插值或复用 pose 顶过去。机制本身简单,但"降帧的动画看起来不跳"和"关键时刻不降帧"之间的权衡藏在细节里。基于 UE5.3 源码(AnimInstance.cpp 的 URO 相关路径)拆。
降帧省的是什么
先明确 AnimInstance 一帧做两件事的耗时结构:
- Update:逻辑侧——状态机转移评估、BlendSpace 采样参数、通知分发(前面两篇的对象);
- Evaluate:求值侧——真正解算骨骼 pose,包含序列解压、混合、IK 求解,是大头。
URO 对两个环节可以分别降频:bShouldUseURO 打开后,FAnimUpdateRateParameters 里 UpdateRate / EvalRate(UpdateRateSkipFrames / EvalRateSkipFrames 是帧数形态)决定各自的间隔。通常 Evaluate 的降频收益最大——pose 解算是纯计算,Update 里往往有游戏逻辑混杂,全跳怕副作用。
跳过一帧的语义:第 $k$ 帧命中更新,中间 $n-1$ 帧不重算 pose。为了画面不定格,需要一个输出策略,UE 给了两种:
Skip 与 Interp
URO 的参数枚举 EUpdateRateOptimizationMode 只影响跳过帧的 pose 来源:
- Skip:中间帧直接复用上一次 Evaluate 的结果。零额外成本,但角色动到一半会"定格"几帧再跳到下一段——远处站桩 NPC 看不出问题,走路角色会有轻微顿挫;
- Interp:中间帧对前后两次求值结果做骨骼变换插值(FQuat 球面插值 + 位移线性插值)。视觉上接近"动画只是播放得糙了",代价是每帧多一次全骨骼插值——如果骨骼数 200+,这个插值本身就不便宜。URO 的省账公式要把它算进去:降频省的是 Evaluate 的解算,Interp 加的是每帧的混合,骨骼数大、Evaluate 不重的角色(简单状态机)用 Interp 可能反而亏。
插值系数在更新间隔内线性推进:
$$\alpha = \frac{t - t_{last}}{n \cdot \Delta t}$$
$t_{last}$ 是上次 Evaluate 的时刻,$n$ 是跳过帧数。$\alpha$ 从 0 走到 1 的过程就是"上次 pose 渐变到这次 pose"。
延迟判定:为什么不能"到 LOD 立刻降"
LOD 切换是离散事件,而游戏相机在 LOD 边界附近抖动时,LOD 值会在阈值两侧来回跳。如果"LOD ≥ 3 就降频"是即时生效,边界抖动的结果是更新帧间隔跟着抖——一会儿 30Hz 一会儿 60Hz,视觉上是跳变。URO 的标准做法是把 LOD 先滞回平滑(FAnimUpdateRateParameters::SpeedRunAnimUpdateRate 一带的滞后逻辑),得到一个稳定的"等效 LOD"再查表决定降频档,并且档位切换本身也做帧数平滑。这个"延迟判定"是 URO 观感质量的关键,少了它,远处角色看起来会"抽搐"。
不降帧的豁免区
URO 有两个硬性豁免,源码里是直接写死的条件:
- Montage 激活期间不降。Montage 是交互动作(攻击、受击、翻越)的载体,这些动作对时序最敏感——通知触发、root motion、section 切换全依赖稳定的求值节奏。降帧会让 Montage 的通知窗口错位,root motion 积分粒度变粗。判定在
UAnimInstance::ShouldUseUpdateRateOptimizations之类路径里检查 ActiveMontage; - 同步组(Sync Group)成员按组内最严标准来。同步组存在的意义是多序列(上身/下身)严格同相位播放,组里一个角色降帧、另一个不降,相位就散了。所以组内任意成员不允许 URO,全组统一节奏。
这条规则反过来用:如果你的交互动作不走 Montage 而走 default slot 的序列,URO 不会豁免它——交互动作的动画会被降频,手感劣化且难排查。这也回答了实践里"远处角色攻击动作看起来黏滞"的问题。
配置与验收
URO 配置入口在 SkeletalMeshComponent 的 AnimUpdateRateParams(也可用项目的 SkeletalMeshLODSettings 资产按 LOD 批量设),两个核心参数:BaseNonRenderedUpdateRate(屏幕外/超远距离的更新率)和 LOD 分档表。验收手段:
stat anim看 Update 和 Evaluate 总耗时随距离的变化曲线——降频生效时 Evaluate 耗时台阶式下降;- 远处角色截图对比 Skip 和 Interp 的走路循环,Interp 模式脚部滑动明显更少;
- 注意 曲线和通知跟着降频走:URO 降频期间通知窗口扫的是跳变的时间窗(一次扫多帧跨度),会集中触发中间所有通知——脚步声在降频角色上是"几帧内连播几步"的听感。表现层能接受,逻辑层(已说过)不该依赖通知。