直接用线性插值(Lerp)做相机跟随会显得“飘”,因其匀速过渡无法响应目标突变,而临界阻尼弹簧公式(如SmoothDamp)通过加速度约束与阻尼反馈实现帧率无关、无振荡的物理感跟随。

为什么直接用线性插值(Lerp)做相机跟随会显得“飘”
因为 Lerp 是匀速过渡,目标突然加速或转向时,相机跟不上又刹不住,视觉上像在冰面上滑行。真实物理感的跟随需要加速度约束和阻尼反馈——也就是弹簧系统建模:把相机看作连在目标身上的质量块,中间是带阻尼的弹簧。
关键不是“模拟完整物理”,而是复用二阶阻尼弹簧公式(Critically Damped Spring),它无振荡、收敛快、参数直观。Unity 的 SmoothDamp 和 Unreal 的 SpringInterpolate 底层都是这个。
常见错误:手写 lerp(pos, target, 0.1f) 套多层,或者用固定步长积分微分方程——既难调参,又受帧率影响。
用 Critically Damped Spring 公式实现稳定帧率无关跟随
核心公式只有一行更新逻辑,不依赖 dt 积分,天然帧率无关:
立即学习“C++免费学习笔记(深入)”;
float omega = 2.0f / smoothTime; float x = omega * deltaTime; float exp = 1.0f / (1.0f + x + 0.48f * x * x + 0.235f * x * x * x); vec3 velocityNew = (position - positionPrev) * exp; position = target + (target - position) * (1.0f - exp) + velocityNew * deltaTime;
但更推荐封装成函数,传入当前相机位置、目标位置、当前速度、平滑时间(单位:秒):
参数说明:
-
smoothTime越小响应越快(如 0.15f),越大越“慵懒”(如 0.5f) -
currentVelocity必须是引用传入,函数内部会就地更新它 - 该公式本质是临界阻尼解的三阶近似,比纯指数衰减更稳,比四阶龙格库塔轻量得多
如何处理目标旋转(朝向)的平滑跟随
位置能用向量直接算,但旋转不能简单对 quat 做线性插值——会出现轴向翻转或绕远路。正确做法是:
- 用
glm::slerp或quat::slerp(取决于你用的 math 库)做球面插值 - 但 slerp 本身不带阻尼,需手动控制插值权重:
weight = 1.0f - std::exp(-deltaTime / smoothTime) - 更鲁棒的做法:把目标朝向转成
vec3 forward和vec3 up,对这两个向量分别用上面的位置阻尼公式更新,再用glm::lookAt重构相机朝向 - 避免直接对欧拉角插值——万向节死锁会让相机突然仰头或翻滚
实际集成时最容易被忽略的三个点
很多实现跑起来“差不多”,但一到快速变向、镜头拉远、或低帧率场景就露馅:
- 相机位置更新后,必须立刻用新位置重算
view矩阵,否则渲染帧延迟一帧,跟随感直接崩坏 -
smoothTime不是常量——建议根据镜头距离动态缩放,比如距离目标 > 10 米时,smoothTime *= 1.5f,否则远距离跟随时会“拖尾”明显 - 若目标有瞬移(如传送、瞬移技能),需检测位移突变并重置
currentVelocity为零,否则相机会朝错误方向猛甩出去
阻尼弹簧不是黑盒,它的“手感”完全由 smoothTime 和速度初值决定;调参时盯着目标做急停和 90° 转向,比看静态截图管用十倍。


















