如何在Three.js中自动居中GLTF模型并调整相机显示完整模型
解决方案:自动居中GLTF模型并让相机完整显示
问题分析
你的代码存在几个关键问题导致相机无法正确显示模型:
- 模型定位逻辑冲突:先通过
sub(center)将模型中心移到原点,又手动设置y = size.y / 2,破坏了模型的居中定位 - 相机距离计算不准确:使用
Math.max(size.x, size.y, size.z)没有考虑相机垂直视场角与窗口宽高比的匹配关系 - 变量作用域错误:
resize事件中访问的verticalOffset和distance是loader回调内的局部变量,会导致未定义错误 - OrbitControls目标未同步:初始化时设置的目标与模型实际中心不匹配
修正步骤
- 正确定位模型:将模型的中心对齐到世界原点(或根据需求让底部对齐Y=0),确保后续相机目标计算准确
- 精准计算相机距离:基于模型尺寸和相机参数计算距离,保证模型完全适配窗口
- 修复变量作用域:将模型尺寸、中心等变量提升到外部,确保resize事件能访问
- 同步OrbitControls目标:让控制器目标与模型中心保持一致
完整修正代码
const { ipcRenderer } = window.electron; var mixer, clock; let currentAnimationIndex = 0; let animations = []; // 补全animations变量定义 let modelCenter, modelSize; // 提升变量作用域 function playNextAnimation() { if (currentAnimationIndex >= animations.length) return; const action = mixer.clipAction(animations[currentAnimationIndex]); action.reset().play(); action.clampWhenFinished = true; action.loop = THREE.LoopOnce; action.onFinished = () => { currentAnimationIndex++; playNextAnimation(); }; } ipcRenderer.on('load-gltf', (event, { filePath, characterName }) => { console.log(`Loading GLTF file: ${filePath}, Character Name: ${characterName}`); if (!filePath) { console.error('File path is undefined or invalid'); return; } // Initialize Three.js scene const scene = new THREE.Scene(); clock = new THREE.Clock(); const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true, preserveDrawingBuffer: false }); renderer.setSize(window.innerWidth, window.innerHeight); renderer.setPixelRatio(window.devicePixelRatio || 1); renderer.setClearColor(0x000000, 0); renderer.toneMapping = THREE.LinearToneMapping; renderer.toneMappingExposure = 2; renderer.physicallyCorrectLights = true; renderer.outputEncoding = THREE.sRGBEncoding; renderer.clear() document.body.appendChild(renderer.domElement); // Add lights const directionalLight = new THREE.DirectionalLight(0xffffff, 3); directionalLight.position.set(5, 10, 7.5); scene.add(directionalLight); const ambientLight = new THREE.AmbientLight(0xffffff, 0.5); scene.add(ambientLight); // Add OrbitControls - 移到loader前,确保后续能正确更新目标 const controls = new THREE.OrbitControls(camera, renderer.domElement); controls.target.set(0, 0, 0); controls.update(); const loader = new THREE.GLTFLoader(); loader.load(filePath, (gltf) => { const bbox = new THREE.Box3().setFromObject(gltf.scene); modelCenter = bbox.getCenter(new THREE.Vector3()); modelSize = bbox.getSize(new THREE.Vector3()); // 方案1:将模型中心对齐到世界原点 gltf.scene.position.sub(modelCenter); // 方案2:如果需要模型底部对齐Y=0,替换上面一行: // gltf.scene.position.set(-modelCenter.x, -modelCenter.y + modelSize.y/2, -modelCenter.z); // 计算相机距离:同时适配模型宽高与窗口比例,避免裁切 const fovRad = camera.fov * Math.PI / 180; const aspectRatio = window.innerWidth / window.innerHeight; // 分别计算满足高度和宽度所需的最小距离 const distanceHeight = (modelSize.y / 2) / Math.tan(fovRad / 2); const distanceWidth = (modelSize.x / 2) / (Math.tan(fovRad / 2) * aspectRatio); const distance = Math.max(distanceHeight, distanceWidth) * 1.2; // 1.2为预留边距系数 // 设置相机位置:正面朝向模型,高度对齐模型中心 camera.position.set(0, modelCenter.y - gltf.scene.position.y, distance); camera.lookAt(modelCenter); // 看向模型中心 controls.target.copy(modelCenter); // 同步控制器目标 controls.update(); scene.add(gltf.scene); // Initialize AnimationMixer mixer = new THREE.AnimationMixer(gltf.scene); // Store animations if (gltf.animations && gltf.animations.length > 0) { animations = gltf.animations; console.log('Animations found in the model:'); animations.forEach((clip, index) => { console.log(`${index + 1}: ${clip.name}`); }); playNextAnimation(); } else { console.log('No animations found in the model.'); } animate(); }); function animate() { renderer.clear(); requestAnimationFrame(animate); controls.update(); const delta = clock.getDelta(); if (mixer) mixer.update(delta); renderer.render(scene, camera); } window.addEventListener('resize', () => { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); // 窗口尺寸变化时重新计算相机位置 if (modelSize && modelCenter) { const fovRad = camera.fov * Math.PI / 180; const aspectRatio = window.innerWidth / window.innerHeight; const distanceHeight = (modelSize.y / 2) / Math.tan(fovRad / 2); const distanceWidth = (modelSize.x / 2) / (Math.tan(fovRad / 2) * aspectRatio); const distance = Math.max(distanceHeight, distanceWidth) * 1.2; camera.position.set(0, modelCenter.y - gltf.scene.position.y, distance); camera.lookAt(modelCenter); controls.target.copy(modelCenter); controls.update(); } }); });
关键说明
- 模型定位:提供两种可选方案,默认将模型中心对齐世界原点;如需模型底部贴合Y轴,可切换注释代码
- 相机适配:同时考虑模型宽高与窗口比例,确保模型在任何方向都不会被裁切,1.2的系数可根据需求调整边距大小
- Resize响应:窗口尺寸变化时自动重新计算相机位置,保持模型的完整显示状态
- 交互同步:OrbitControls的目标始终与模型中心绑定,保证拖拽旋转时的聚焦点正确
内容的提问来源于stack exchange,提问作者javanub
相关产品推荐
相关产品推荐

