文章

Three.js 相机漫游:让巡检动画不再晕 3D

数字孪生场景里的自动巡检漫游,从关键帧到 CatmullRom 曲线,把镜头运动做"稳"的几个原则。

Three.js 相机漫游:让巡检动画不再晕 3D

机房数字孪生项目里有个需求叫”自动巡检”:点一下按钮,镜头沿预设路线在机柜间穿行,途经每台告警设备时停留展示。听起来就是个相机动画,但第一版做完,测试同事看了两分钟说头晕。

这篇记录我怎么把镜头运动做”稳”的。

晕的根源:速度突变和视线甩动

回看第一版,问题很典型:

  • 相机在关键点之间直线插值,到点就急转弯——速度方向突变
  • lookAt 每到一个巡检点瞬间切换目标——视线甩动

人眼对这两件事极其敏感。现实里人走路转弯是弧线,看东西是先转头再聚焦,镜头也必须这样。

位置走曲线:CatmullRomCurve3

把巡检点串成 THREE.CatmullRomCurve3,相机沿曲线取点前进,急转弯自然变成圆弧。两个细节:

等弧长采样。直接用 curve.getPoint(t) 按参数 t 匀速推进,在控制点密集处会变慢、稀疏处会加速。要用 getPointAt(u)——它按弧长归一化取点,镜头速度才是恒定的。

进出站缓动。到达巡检点前后用 easeInOut 减速/加速,停留展示时速度归零。匀速→急停的顿挫感,比转弯更容易让人晕。

视线独立成第二条曲线

这是这个项目里最值的一个经验:不要让 lookAt 目标跟随巡检点跳变,给视线目标单独建一条平滑曲线

相机位置曲线和视线目标曲线同步推进,视线目标曲线比位置曲线”提前”一小段——效果就是镜头永远在”提前看向”即将到达的地方,像人走路时目光自然落在前方几米,而不是盯着脚尖再突然抬头。

1
2
3
4
const camPos = pathCurve.getPointAt(u);
const lookAt = targetCurve.getPointAt(Math.min(u + lookAhead, 1));
camera.position.copy(camPos);
camera.lookAt(lookAt);

用户接管:漫游不是绑架

自动漫游期间用户滚动滚轮或按下鼠标,必须立刻交出控制权——暂停漫游、恢复 OrbitControls,并给一个”继续巡检”的按钮。镜头可以替用户走路,但不能绑架用户的眼睛。

实现上就是给 OrbitControls 的交互事件加监听,任何输入到来就 pauseRoaming()。恢复时从当前相机位置平滑过渡回曲线上最近的点,而不是瞬移回去。

结语

镜头运动的所有原则可以压缩成一句话:镜头是替观众走路的腿。腿不能瞬移、不能急停、不能一边走一边甩头。位置和视线各一条平滑曲线、等弧长采样、进出站缓动、随时交还控制——做到这四件,巡检动画就从”过山车”变成了”散步”。

本文由作者按照 CC BY 4.0 进行授权