跳到正文
Virok
在长

30Hz 插值里那些不明显的坑

2 分钟更新于 2026年9月4日

AI 摘要

文章梳理了游戏开发中插值表现层的几个常见陷阱:旋转必须用 Slerp 避免大角度路径异常;传送类突变位置需由 Sim 层打标记跳过插值;alpha 超 1 时插值会变成外插引发抖动。核心结论是表现层不能自己判断状态,插值逻辑需严格依赖 Sim 层信号驱动。

“插值只在表现层”是条好规则,但它是必要条件不是充分条件。实际写的时候还有几个具体的坑。

旋转要用 Slerp 不用 Lerp

Vector3.Lerp 对位置没问题,但旋转用 Lerp 会在大角度时出问题——两个四元数之间线性插值,中间态不是合法旋转,速度不均匀。

Quaternion.Slerp(prev.Rotation, curr.Rotation, alpha);
// 不是 Quaternion.Lerp

更麻烦的是四元数的双覆盖问题:q-q 表示同一个旋转,但插值路径完全相反。如果 prev 和 curr 点积为负,要先把 curr 取反:

if (Quaternion.Dot(prev, curr) < 0f) curr = new Quaternion(-curr.x, -curr.y, -curr.z, -curr.w);

不处理的话,角色转身时偶尔会绕远路转一整圈。

传送不能插值

闪现、击退、重生这类“位置突变”的帧,插值会让实体从旧位置“滑”到新位置,看起来像瞬移拖影。

解法是给状态加一个标记:

public readonly struct SimTransform
{
    public readonly Vector2 Position;
    public readonly bool Teleported;
}

Teleported == true 时直接跳到目标位置,不插值。这个标记由 Sim 层在发生传送的那个 tick 置位,表现层消费后清零。

关键是这个判断必须来自 Sim 层,不能让表现层自己比较距离阈值——阈值是个魔数,迟早在某次调参后失效。

alpha 可能大于 1

累加器写法下,如果一帧内一个 tick 都没跑(_accumulator < FixedDelta),alpha 是合法的。但如果渲染帧率低于 30Hz,或者 tick 被 MaxStepsPerFrame 截断了,_accumulator 会持续累积,alpha 可能超过 1。

超过 1 再插值就是外插(extrapolation),实体会冲到当前 tick 状态之外,然后下一帧又弹回来——产生抖动。

float alpha = Mathf.Clamp01(_accumulator / FixedDelta);

钳一下最省事。更严谨的做法是超过 1 时改用外插且限制最大外插时长,但对 30Hz 的 Sim 来说,钳制够用了。

待验证

还没处理的情况:实体在插值期间被销毁。目前是下一 tick 直接移除,视觉上会“啪”地消失。考虑加一个淡出,但那需要在表现层维护死亡实体的生命周期——又是一层状态,先记着。

这是一条笔记,不是成稿文章。它可能不完整、可能有错,也可能过段时间就被我推翻。 想看成体系的文章,去 Blog