Vue中使用Three.js加载GLB模型时,为什么旋转后模型位置错乱?

IT人法霞 阅读 56

在Vue组件里用Three.js加载GLB模型,旋转相机时模型位置突然偏移到角落了,之前在纯HTML环境没问题,现在改成Vue组件后就出问题了

代码结构是这样的:

<template>
  <div ref="container" class="model-container"></div>
</template>

<script setup>
import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';

const container = ref(null);
let renderer;

onMounted(() => {
  const scene = new THREE.Scene();
  const camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000);
  renderer = new THREE.WebGLRenderer();
  renderer.setSize(window.innerWidth, window.innerHeight);
  container.value.appendChild(renderer.domElement);

  const loader = new GLTFLoader();
  loader.load('/model.glb', (gltf) => {
    scene.add(gltf.scene);
    gltf.scene.position.set(0,0,-5);
  });

  function animate() {
    requestAnimationFrame(animate);
    renderer.render(scene, camera);
  }
  animate();
});
</script>

我已经尝试过设置模型位置、调整相机参数,甚至用boundingSphere计算中心点,但旋转时模型还是会突然跳到左上角。是不是Vue的响应式系统影响了Three.js的渲染循环?

我来解答 赞 5 收藏
二维码
手机扫码查看
2 条解答
W″康佳
问题出在没处理窗口缩放和容器尺寸,Vue组件挂载时页面布局可能还没稳定,加上没有监听resize,导致渲染器尺寸错乱,模型看着就像飞到角落去了。

另外WebGLRenderer会占用整个画布,但你把它塞进container后没设置样式,容易被CSS挤压变形。还有动画循环里的camera始终是初始实例,没更新过投影矩阵。

代码给你:

import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { onMounted, ref, onUnmounted } from 'vue';

const container = ref(null);
let renderer, scene, camera, animateId;

onMounted(() => {
// 创建场景和相机
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(75, container.value.clientWidth / container.value.clientHeight, 0.1, 1000);
camera.position.z = 5;

// 渲染器使用容器宽高
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(container.value.clientWidth, container.value.clientHeight);
renderer.setPixelRatio(window.devicePixelRatio); // 防模糊
container.value.appendChild(renderer.domElement);

// 加载模型
const loader = new GLTFLoader();
loader.load('/model.glb', (gltf) => {
gltf.scene.position.set(0, 0, -5);
scene.add(gltf.scene);
});

// 动画循环
const animate = () => {
animateId = requestAnimationFrame(animate);
renderer.render(scene, camera);
};
animate();

// 监听窗口变化
const handleResize = () => {
camera.aspect = container.value.clientWidth / container.value.clientHeight;
camera.updateProjectionMatrix();
renderer.setSize(container.value.clientWidth, container.value.clientHeight);
};
window.addEventListener('resize', handleResize);

// 组件卸载时清理
onUnmounted(() => {
window.removeEventListener('resize', handleResize);
cancelAnimationFrame(animateId);
renderer.dispose();
});
});


模板别忘了加样式:

<template>
<div ref="container" class="model-container" style="width:100vw;height:100vh"></div>
</template>


核心就三点:用容器实际尺寸初始化相机比例、监听resize更新矩阵、别用window.innerWidth/Height这种全局值。Vue里DOM异步挂载,必须等ref有值再读尺寸。
点赞 2
2026-02-12 20:08
❤红娟
❤红娟 Lv1
这问题我遇到过,确实挺烦人的。不是Vue的响应式系统影响了Three.js,而是因为你没有正确处理模型和相机之间的关系,尤其是当窗口大小变化或者视口调整时,渲染逻辑可能没有同步更新。

具体来说,你的代码中缺少对窗口尺寸变化的监听,以及相机投影矩阵的更新。当旋转相机时,如果没有重新计算视口宽高比,模型就会“飞走”或者跳到奇怪的位置。这是Three.js常见的坑。

### 解决方案
我们需要做几件事:
1. 监听窗口大小变化事件。
2. 每次窗口大小变化时,更新相机的宽高比和渲染器的尺寸。
3. 确保在调整后重新设置相机的投影矩阵。

下面是修改后的完整代码:

import { ref, onMounted } from 'vue';
import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';

const container = ref(null);
let renderer, scene, camera;

onMounted(() => {
// 初始化场景、相机和渲染器
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);

renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
container.value.appendChild(renderer.domElement);

// 加载GLB模型
const loader = new GLTFLoader();
loader.load('/model.glb', (gltf) => {
scene.add(gltf.scene);
gltf.scene.position.set(0, 0, -5); // 设置模型初始位置
});

// 添加窗口大小变化监听
window.addEventListener('resize', onWindowResize);

function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
animate();
});

// 处理窗口大小变化的函数
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight; // 更新相机宽高比
camera.updateProjectionMatrix(); // 重新计算相机投影矩阵

renderer.setSize(window.innerWidth, window.innerHeight); // 更新渲染器尺寸
renderer.setPixelRatio(window.devicePixelRatio); // 确保分辨率适配
}

// 组件卸载时移除监听
onUnmounted(() => {
window.removeEventListener('resize', onWindowResize);
});


### 为什么这样做?
- **相机宽高比**:当你调整浏览器窗口大小时,如果没有更新相机的aspect属性(即宽高比),会导致渲染结果变形或模型位置错乱。
- **投影矩阵更新**:每次修改相机的aspect后,必须调用camera.updateProjectionMatrix()来应用新的宽高比。
- **渲染器尺寸同步**:渲染器的尺寸也需要与窗口同步,否则画面会裁剪或拉伸。
- **设备像素比**:通过renderer.setPixelRatio(window.devicePixelRatio)可以确保在高分辨率屏幕上显示清晰。

### 其他注意事项
如果模型还是有偏移,可能是因为加载的GLB模型本身有原点偏移的问题。你可以通过以下方式检查并修正:
loader.load('/model.glb', (gltf) => {
const boundingBox = new THREE.Box3().setFromObject(gltf.scene);
const center = boundingBox.getCenter(new THREE.Vector3());
gltf.scene.position.sub(center); // 将模型中心对齐到原点
scene.add(gltf.scene);
});


这样应该就能解决你提到的问题了。希望这次能帮到你!
点赞 10
2026-02-01 02:01