Three.js批量加载GLB模型时旋转报错及材质丢失问题求助
嘿,刚看了你的问题,作为Three.js新手遇到这俩问题太正常了,我来帮你拆解下:
一、旋转报错的根源和解决办法
你碰到的Uncaught TypeError: Cannot read properties of undefined (reading 'rotation'),核心原因是GLTFLoader加载模型是异步的!你的animate函数一启动就开始循环执行,但这时候GLB模型还在加载中,scene.children里根本还没模型对象,自然读不到rotation属性。后来有一个模型能转,是因为那个模型刚好在某一帧加载完成了,这时候scene.children里才有它,代码就生效了,是不是挺坑的😂
给你两个实用的解决思路:
1. 用单独数组存加载好的模型,动画里判断后操作
别直接依赖scene.children,自己搞个数组存加载完成的模型,动画里只对已加载的模型操作:
import * as THREE from 'three'; import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; const canvas = document.querySelector('#frontPageCanvas'); const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); const renderer = new THREE.WebGLRenderer({canvas, antialias: true}); renderer.setSize(window.innerWidth, window.innerHeight); renderer.setClearColor( 0xffffff, 0); // 新增数组专门存加载好的模型 const loadedModels = []; function loadFiles(capsulesFiles, scene) { const capsuleloader = new GLTFLoader(); capsulesFiles.forEach((capsulesFile) => { capsuleloader.load(capsulesFile, (gltf) => { const model = gltf.scene; scene.add(model); loadedModels.push(model); // 加载完就塞进数组 }); }); }; let capsulesFiles = ['../three.js/models/capsule_1.glb', '../three.js/models/capsule_1.glb']; loadFiles(capsulesFiles, scene); camera.position.z = 5; function animate() { requestAnimationFrame(animate); // 遍历数组,只处理已加载的模型 loadedModels.forEach((model, index) => { if(index === 0){ model.rotation.y += 0.05; } else if(index === 1){ model.rotation.x -= 0.01; model.rotation.y -= 0.01; } }); renderer.render(scene, camera); } animate();
2. 等所有模型加载完再启动动画
如果想等所有模型都加载好再动,可以用Promise来批量处理加载,确保动画只在所有模型就绪后才开始:
import * as THREE from 'three'; import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; const canvas = document.querySelector('#frontPageCanvas'); const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); const renderer = new THREE.WebGLRenderer({canvas, antialias: true}); renderer.setSize(window.innerWidth, window.innerHeight); renderer.setClearColor( 0xffffff, 0); const loadedModels = []; // 把单个模型加载改成返回Promise的函数 function loadSingleModel(filePath) { return new Promise((resolve, reject) => { const loader = new GLTFLoader(); loader.load(filePath, (gltf) => { resolve(gltf.scene); }, undefined, (err) => { reject(`加载模型失败:${err}`); }); }); } let capsulesFiles = ['../three.js/models/capsule_1.glb', '../three.js/models/capsule_1.glb']; // 批量加载所有模型,全部完成后再启动动画 Promise.all(capsulesFiles.map(file => loadSingleModel(file))) .then(models => { models.forEach(model => { scene.add(model); loadedModels.push(model); }); // 所有模型就绪,启动动画 animate(); }) .catch(err => console.error(err)); camera.position.z = 5; function animate() { requestAnimationFrame(animate); loadedModels.forEach((model, index) => { if(index === 0){ model.rotation.y += 0.05; } else if(index === 1){ model.rotation.x -= 0.01; model.rotation.y -= 0.01; } }); renderer.render(scene, camera); }
二、材质/纹理丢失的排查方向
关于Maya导出GLB后材质没了的问题,我之前用Verge3D导出也踩过坑,你可以从这几点查:
Verge3D导出设置要盯紧
导出时一定要勾选Embed Textures(嵌入纹理)!不然纹理文件不会打包进GLB,Three.js找不到纹理就会显示默认的灰色材质。同时确认导出选项里的材质模式,aiStandardSurface属于PBR材质,要确保Verge3D导出时启用了PBR相关的导出配置,别选成旧的材质模式。Maya里的材质要做好前置检查
先确认aiStandardSurface的玻璃、塑料材质在Maya视图里能正常显示,纹理(如果用了的话)要正确连接到材质节点的对应通道,比如玻璃的透明度通道要开启;另外检查模型的UV是否正确,UV有问题的话纹理也会显示异常甚至不显示。Three.js的渲染配置要跟上
GLB的PBR材质需要正确的颜色编码和光照,给渲染器加这两行:renderer.outputEncoding = THREE.sRGBEncoding; renderer.physicallyCorrectLights = true;别忘了给场景加光照!PBR材质没光照的话会显得像没材质,至少加个环境光和方向光:
// 环境光 const ambientLight = new THREE.AmbientLight(0xffffff, 0.5); scene.add(ambientLight); // 方向光 const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); directionalLight.position.set(5, 5, 5); scene.add(directionalLight);
备注:内容来源于stack exchange,提问作者YoNoms

