Three.js:如何实现相机朝向平滑过渡至指定向量
嘿,这个场景我太熟悉了!当初做360全景播放器的时候,直接用lookAt()跳转确实会让用户觉得很突兀,完全破坏了沉浸感。咱们可以用**缓动动画(tweening)**来实现平滑过渡,给你两种实操性很强的方案:
方案一:手动实现缓动(无需额外库)
如果不想引入第三方库,完全可以自己在渲染循环里处理插值逻辑,核心思路是逐帧计算相机朝向的过渡进度,用缓动函数让动画更自然(比如开头和结尾慢,中间快)。
- 先定义几个全局变量保存过渡状态:
let targetLookDir = null; // 目标朝向向量 const transitionDuration = 1000; // 过渡时长(毫秒) let startTime = null; // 过渡开始时间
- 点击网格时初始化过渡状态:
mesh.addEventListener('click', () => { // 计算相机到网格的朝向向量(归一化后更稳定) targetLookDir = mesh.position.clone() .sub(camera.position) .normalize(); startTime = performance.now(); });
- 在渲染循环里处理过渡逻辑:
function render() { requestAnimationFrame(render); if (targetLookDir && startTime) { const elapsed = performance.now() - startTime; const progress = Math.min(elapsed / transitionDuration, 1); // 进度 clamp 在 0-1 之间 // 用 ease-in-out 缓动函数优化过渡曲线,比纯线性更自然 const easedProgress = progress < 0.5 ? 2 * progress * progress : 1 - Math.pow(-2 * progress + 2, 2) / 2; // 获取相机当前的朝向向量 const currentLookDir = camera.getWorldDirection(new THREE.Vector3()); // 计算当前帧的目标朝向:插值当前朝向和目标朝向 const currentTarget = new THREE.Vector3().lerpVectors( currentLookDir, targetLookDir, easedProgress ); // 更新相机朝向 camera.lookAt(camera.position.clone().add(currentTarget)); // 过渡完成后重置状态 if (progress >= 1) { targetLookDir = null; startTime = null; } } renderer.render(scene, camera); }
方案二:用Tween.js简化开发
如果项目里已经在用动画库,或者不想自己写帧逻辑,Tween.js是Three.js生态里最常用的动画工具,代码会更简洁。
- 首先引入Tween.js(npm安装的话:
npm install @tweenjs/tween.js):
import * as TWEEN from '@tweenjs/tween.js';
- 点击网格时创建过渡动画:
mesh.addEventListener('click', () => { // 计算目标朝向向量 const targetDir = mesh.position.clone() .sub(camera.position) .normalize(); // 获取当前朝向向量,存到临时对象里(Tween需要可修改的属性) const currentDir = camera.getWorldDirection(new THREE.Vector3()); const tweenTarget = { x: currentDir.x, y: currentDir.y, z: currentDir.z }; // 创建并启动tween动画 new TWEEN.Tween(tweenTarget) .to({ x: targetDir.x, y: targetDir.y, z: targetDir.z }, transitionDuration) .easing(TWEEN.Easing.Quadratic.InOut) // 选择缓动曲线 .onUpdate(() => { // 每一帧更新相机朝向 camera.lookAt( camera.position.clone().add(new THREE.Vector3(tweenTarget.x, tweenTarget.y, tweenTarget.z)) ); }) .start(); });
- 别忘了在渲染循环里更新Tween:
function render() { requestAnimationFrame(render); TWEEN.update(); // 必须调用这个才能让tween动画运行 renderer.render(scene, camera); }
额外注意点
- 为什么用方向向量而不是直接插值相机的
rotation?因为Three.js的欧拉角(rotation)容易出现万向锁问题,导致过渡时出现奇怪的旋转,用方向向量或者四元数(Quaternion)更稳定。如果想用四元数,可以试试THREE.Quaternion.slerp来插值相机的quaternion属性,效果同样出色。 - 缓动曲线可以根据需求调整:Tween.js提供了很多预设(比如
Quintic.InOut、Sinusoidal.InOut),你可以多试几种,找到最贴合你产品风格的过渡效果。 - 如果你的360播放器里相机位置是固定的,上面的代码直接用就行;要是相机也需要移动,同样的思路可以复用在位置插值上。
内容的提问来源于stack exchange,提问作者Elekk
相关产品推荐
相关产品推荐

