Three.js地球纹理GUI设置报错:WeakMap非对象键错误修复
修复Three.js地球模型GUI纹理切换的WeakMap错误
问题场景
为Three.js + three-globe地球模型添加GUI切换纹理功能时,出现错误:TypeError: Attempted to set a non-object key in a WeakMap,推测与纹理操作相关。
原错误代码
const gui = new GUI(); const textureLoader = new THREE.TextureLoader(); const myTexture = [ textureLoader.load("//unpkg.com/three-globe/example/img/earth-blue-marble.jpg"), textureLoader.load("//unpkg.com/three-globe/example/img/earth-night.jpg"), textureLoader.load("//unpkg.com/three-globe/example/img/earth-day.jpg"), textureLoader.load("//unpkg.com/three-globe/example/img/earth-dark.jpg"), ]; const parameters = { Theme: 0, }; const updateAllMaterials = () => { scene.traverse((child) => { if ( child instanceof Globe() && // 错误:instanceof需搭配构造函数,而非实例 child.material instanceof THREE.MeshPhongMaterial ) { //child.material = myTexture[parameters.Theme]; child.material.needsUpdate = true; child.material.map = textureLoader.load("//unpkg.com/three-globe/example/img/earth-blue-marble.jpg"); // 错误:重复加载纹理 } }); }; gui .add(parameters, "Theme", { day: 0, night: 1, basic: 2, dark: 3, }) .onFinishChange(() => { updateAllMaterials(); }); gui.open(); const elem = document.getElementById("globeViz"); const globe = Globe() .globeImageUrl(parameters.Theme) // 错误:传入索引而非图片URL (elem); //.globeMaterial([MeshPhongMaterial])
错误原因分析
instanceof使用错误:child instanceof Globe()是语法错误,instanceof右侧必须是构造函数,而非调用构造函数生成的实例。这会导致遍历场景时判断逻辑失效,误处理非预期对象,触发WeakMap键类型错误。- 初始化参数错误:
globeImageUrl(parameters.Theme)传入的是数字索引,但该方法需要的是图片URL字符串,导致地球模型初始化时纹理加载异常。 - 纹理操作不当:每次切换时重新加载纹理,不仅浪费资源,异步加载的纹理对象还可能导致材质更新逻辑混乱;同时未正确使用预加载的纹理数组。
- 不必要的场景遍历:three-globe已封装了地球模型的操作接口,无需手动遍历场景查找对象,遍历过程中可能接触到不符合WeakMap键要求的对象。
修复步骤及修正代码
1. 正确保存地球模型引用
创建globe后,直接获取其内部的Mesh对象,避免场景遍历:
const elem = document.getElementById("globeViz"); const globe = Globe() .globeImageUrl("//unpkg.com/three-globe/example/img/earth-blue-marble.jpg") // 初始化传入正确的URL (elem); const globeMesh = globe.getGlobe(); // 获取地球模型的Mesh实例
2. 修正纹理切换函数
直接操作已获取的globeMesh,使用预加载的纹理数组,无需重复加载:
const updateAllMaterials = () => { if (globeMesh.material instanceof THREE.MeshPhongMaterial) { globeMesh.material.map = myTexture[parameters.Theme]; // 使用预加载的纹理 globeMesh.material.needsUpdate = true; // 标记材质需要更新 } };
3. 完整修正后代码
const gui = new GUI(); const textureLoader = new THREE.TextureLoader(); // 预加载所有纹理 const myTexture = [ textureLoader.load("//unpkg.com/three-globe/example/img/earth-blue-marble.jpg"), textureLoader.load("//unpkg.com/three-globe/example/img/earth-night.jpg"), textureLoader.load("//unpkg.com/three-globe/example/img/earth-day.jpg"), textureLoader.load("//unpkg.com/three-globe/example/img/earth-dark.jpg"), ]; const parameters = { Theme: 0, }; const elem = document.getElementById("globeViz"); // 初始化地球模型,传入正确的初始纹理URL const globe = Globe() .globeImageUrl("//unpkg.com/three-globe/example/img/earth-blue-marble.jpg") (elem); const globeMesh = globe.getGlobe(); // 获取地球Mesh实例 // 纹理切换函数 const updateAllMaterials = () => { if (globeMesh.material instanceof THREE.MeshPhongMaterial) { globeMesh.material.map = myTexture[parameters.Theme]; globeMesh.material.needsUpdate = true; } }; // GUI配置 gui .add(parameters, "Theme", { day: 0, night: 1, basic: 2, dark: 3, }) .onFinishChange(() => { updateAllMaterials(); }); gui.open();
额外优化:等待纹理加载完成后初始化
由于TextureLoader.load是异步操作,可添加加载完成监听,确保所有纹理加载完毕后再启用GUI切换:
let loadedTextures = 0; const totalTextures = myTexture.length; myTexture.forEach(texture => { texture.addEventListener('load', () => { loadedTextures++; if (loadedTextures === totalTextures) { // 所有纹理加载完成后初始化GUI gui.open(); } }); }); // 初始隐藏GUI,等待加载完成 gui.close();
内容的提问来源于stack exchange,提问作者shange
相关产品推荐
相关产品推荐

