Three.js中OBJLoader加载对象的全局操作问题求助
嘿,这个问题我之前做Three.js项目时也踩过坑!核心原因就是变量作用域——你在loadObj函数里定义的obj是局部变量,回调函数之外的代码根本拿不到它。下面给你几个实用的解决方案,覆盖不同场景需求:
方案1:全局变量存储(适合小型项目)
最简单直接的方式,把加载后的模型对象赋值给一个全局变量,这样动画循环或者其他函数里就能访问了:
// 全局变量,用来存储加载后的模型 let loaded3DObj = null; function loadObj(path, name) { const mtlLoader = new THREE.MTLLoader(); mtlLoader.setPath(path); mtlLoader.load(name + ".mtl", function(materials) { materials.preload(); const objLoader = new THREE.OBJLoader(); objLoader.setMaterials(materials); objLoader.setPath(path); objLoader.load(name + ".obj", function(object) { // 把模型赋值给全局变量 loaded3DObj = object; scene.add(loaded3DObj); // 回调内的操作正常执行 loaded3DObj.position.set(0, 0, 0); }); }); } // 动画循环里的操作 function animate() { requestAnimationFrame(animate); // 一定要先判断模型是否加载完成,避免报错 if (loaded3DObj) { loaded3DObj.rotation.y += 0.01; } renderer.render(scene, camera); }
方案2:类属性管理(适合中大型项目)
如果项目结构复杂,用类来封装场景资源会更模块化,避免全局变量污染:
class ThreeSceneManager { constructor() { this.scene = new THREE.Scene(); this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); this.renderer = new THREE.WebGLRenderer(); // 用类属性存储加载后的模型 this.loadedModel = null; this.init(); } init() { this.renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(this.renderer.domElement); this.loadObj('models/', 'myModel'); this.startAnimation(); } loadObj(path, name) { const mtlLoader = new THREE.MTLLoader(); mtlLoader.setPath(path); mtlLoader.load(name + ".mtl", (materials) => { materials.preload(); const objLoader = new THREE.OBJLoader(); objLoader.setMaterials(materials); objLoader.setPath(path); objLoader.load(name + ".obj", (object) => { this.loadedModel = object; this.scene.add(this.loadedModel); }); }); } startAnimation() { requestAnimationFrame(() => this.startAnimation()); if (this.loadedModel) { this.loadedModel.rotation.y += 0.01; } this.renderer.render(this.scene, this.camera); } } // 初始化场景管理器 const sceneManager = new ThreeSceneManager();
方案3:Promise封装+async/await(现代JS风格)
用Promise把加载逻辑封装起来,配合async/await让异步代码更像同步逻辑,可读性更强:
// 把加载函数封装成Promise function loadObj(path, name) { return new Promise((resolve, reject) => { const mtlLoader = new THREE.MTLLoader(); mtlLoader.setPath(path); mtlLoader.load( name + ".mtl", (materials) => { materials.preload(); const objLoader = new THREE.OBJLoader(); objLoader.setMaterials(materials); objLoader.setPath(path); objLoader.load( name + ".obj", (object) => resolve(object), // 加载成功返回模型 () => {}, // 进度回调可选 (err) => reject(err) // 加载失败返回错误 ); }, () => {}, (err) => reject(err) ); }); } // 初始化逻辑用async函数 async function initScene() { try { // 等待模型加载完成 const my3DObj = await loadObj('models/', 'myModel'); scene.add(my3DObj); // 动画循环直接使用my3DObj function animate() { requestAnimationFrame(animate); my3DObj.rotation.y += 0.01; renderer.render(scene, camera); } animate(); } catch (err) { console.error('模型加载失败:', err); } } initScene();
关键注意点
- 不管用哪种方案,一定要先判断模型是否加载完成(比如
if(loaded3DObj)),不然在加载完成前访问会触发Cannot read properties of null错误。 - 如果加载多个模型,可以用对象或数组批量存储,比如
loadedModels = {car: null, tree: null},回调里对应赋值即可。
内容的提问来源于stack exchange,提问作者Jolyon
相关产品推荐
相关产品推荐

