You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Three.js中自动居中GLTF模型并调整相机显示完整模型

解决方案:自动居中GLTF模型并让相机完整显示

问题分析

你的代码存在几个关键问题导致相机无法正确显示模型:

  1. 模型定位逻辑冲突:先通过sub(center)将模型中心移到原点,又手动设置y = size.y / 2,破坏了模型的居中定位
  2. 相机距离计算不准确:使用Math.max(size.x, size.y, size.z)没有考虑相机垂直视场角与窗口宽高比的匹配关系
  3. 变量作用域错误:resize事件中访问的verticalOffset和distance是loader回调内的局部变量,会导致未定义错误
  4. OrbitControls目标未同步:初始化时设置的目标与模型实际中心不匹配

修正步骤

  1. 正确定位模型:将模型的中心对齐到世界原点(或根据需求让底部对齐Y=0),确保后续相机目标计算准确
  2. 精准计算相机距离:基于模型尺寸和相机参数计算距离,保证模型完全适配窗口
  3. 修复变量作用域:将模型尺寸、中心等变量提升到外部,确保resize事件能访问
  4. 同步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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 07:04:54