使用Three.js实现相机补间动画时初始位置出现闪烁问题
问题:OrbitControls平移相机后Tween动画初始位置闪烁
我开发了一个基于Three.js的简单程序,可将相机通过补间动画移动到指定位置,正常运行时效果良好。但当使用OrbitControls平移相机后再触发补间动画,动画初始阶段会出现位置闪烁问题。
当前代码:
import * as THREE from 'three'; import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'; const TWEEN = require('@tweenjs/tween.js'); const canvas = document.querySelector('#c'); const renderer = new THREE.WebGLRenderer({ canvas }); const canvasWid = Math.floor(window.innerWidth * 2 / 3), canvasHei = (window.innerHeight * 2 / 3); renderer.setSize(canvasWid, canvasHei); var scene = new THREE.Scene(); scene.background = new THREE.Color('grey'); scene.add(new THREE.HemisphereLight(0xB1E1FF, 0xB97A20, 1)); const camera = new THREE.PerspectiveCamera(45, innerWidth / innerHeight, 0.1, 1000); camera.position.set(10, 15, 20); const controls = new OrbitControls(camera, canvas); controls.screenSpacePanning = true; controls.target.set(0, 0, 0); controls.update(); const geometry = new THREE.BoxGeometry( 4, 4, 4 ); const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); const cube = new THREE.Mesh( geometry, material ); scene.add( cube ); var tween; var tweening = false; function animate() { requestAnimationFrame(animate); if (tweening) { TWEEN.update(); camera.lookAt(new THREE.Vector3(0, 0, 0)); } renderer.render(scene, camera); } animate(); window.addEventListener('keydown', keyDown); function keyDown(e) { if (e.key === "a") { var vv = new THREE.Vector3(prompt(), prompt(), prompt()); tween = new TWEEN.Tween(camera.position).to(vv, 3000) .easing(TWEEN.Easing.Quadratic.InOut) .start(); tweening = true; tween.onComplete(function () { tweening = false; }); } }
问题原因
OrbitControls在交互后会持续维护相机状态(即使无交互,启用阻尼时也会逐帧更新),启动Tween动画时,两者会同时修改相机position属性,导致帧间位置突变,引发闪烁。
修复方案
通过禁用OrbitControls避免与Tween冲突,同时优化动画逻辑确保状态同步:
import * as THREE from 'three'; import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'; const TWEEN = require('@tweenjs/tween.js'); const canvas = document.querySelector('#c'); const renderer = new THREE.WebGLRenderer({ canvas }); const canvasWid = Math.floor(window.innerWidth * 2 / 3), canvasHei = (window.innerHeight * 2 / 3); renderer.setSize(canvasWid, canvasHei); var scene = new THREE.Scene(); scene.background = new THREE.Color('grey'); scene.add(new THREE.HemisphereLight(0xB1E1FF, 0xB97A20, 1)); const camera = new THREE.PerspectiveCamera(45, innerWidth / innerHeight, 0.1, 1000); camera.position.set(10, 15, 20); const controls = new OrbitControls(camera, canvas); controls.screenSpacePanning = true; controls.target.set(0, 0, 0); controls.update(); const geometry = new THREE.BoxGeometry(4, 4, 4); const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 }); const cube = new THREE.Mesh(geometry, material); scene.add(cube); function animate() { requestAnimationFrame(animate); // 始终更新TWEEN,保证动画正常运行 TWEEN.update(); // 仅当控制器启用时更新其状态 if (controls.enabled) { controls.update(); } renderer.render(scene, camera); } animate(); window.addEventListener('keydown', keyDown); function keyDown(e) { if (e.key === "a") { const targetPos = new THREE.Vector3( parseFloat(prompt('X坐标')), parseFloat(prompt('Y坐标')), parseFloat(prompt('Z坐标')) ); // 禁用控制器,防止干扰补间动画 controls.enabled = false; // 以相机当前实时位置为补间起始点 tween = new TWEEN.Tween(camera.position.clone()) .to(targetPos, 3000) .easing(TWEEN.Easing.Quadratic.InOut) .onUpdate(function(currentPos) { // 更新相机位置 camera.position.copy(currentPos); // 确保相机始终看向目标点 camera.lookAt(controls.target); }) .onComplete(function() { // 动画结束后重新启用控制器 controls.enabled = true; // 同步控制器与相机状态,避免后续交互异常 controls.update(); }) .start(); } }
关键修改说明
- 禁用控制器:补间启动时关闭
controls.enabled,避免两者同时修改相机位置 - 实时起始位置:用
camera.position.clone()作为补间起点,确保读取的是相机当前真实位置 - 逐帧更新朝向:将
camera.lookAt移到onUpdate回调,每次位置变化后立即调整朝向 - 状态同步:动画结束后启用控制器并调用
controls.update(),保证后续交互正常
内容的提问来源于stack exchange,提问作者Luca Phillips
相关产品推荐
相关产品推荐

