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

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])

错误原因分析

  1. instanceof使用错误:child instanceof Globe()是语法错误,instanceof右侧必须是构造函数,而非调用构造函数生成的实例。这会导致遍历场景时判断逻辑失效,误处理非预期对象,触发WeakMap键类型错误。
  2. 初始化参数错误:globeImageUrl(parameters.Theme)传入的是数字索引,但该方法需要的是图片URL字符串,导致地球模型初始化时纹理加载异常。
  3. 纹理操作不当:每次切换时重新加载纹理,不仅浪费资源,异步加载的纹理对象还可能导致材质更新逻辑混乱;同时未正确使用预加载的纹理数组。
  4. 不必要的场景遍历: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 02:45:44