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

使用Threebox加载多组.obj与.mtl文件时材质复用问题求助

问题描述

使用Threebox加载多组.obj和.mtl格式3D模型时,调用loadObj方法后所有模型均渲染为最后一个.mtl文件的材质。已尝试用await和Promise处理异步逻辑,但问题仍未解决。

现象说明:

  • 加载多个模型时,全部应用最后一个模型的mtl材质
  • 单个模型可正常显示对应材质

相关代码:

map: Map;
path = '../assets/SG_NhaThoDucBa/';
tb: any;

coordList = [
  [106.693039, 10.772404],
  [106.6929226, 10.77202084],
  [106.6927602, 10.7712239],
  [106.6926045, 10.7703538],
  [106.692724, 10.770191],
  [106.6935564, 10.770271],
  [106.6935902, 10.7702023],
  [106.6936212, 10.770139],
  [106.6935586, 10.7696358],
  [106.693639, 10.768409],
];

constructor() {}

private buildMap(): void {
  this.map = new Map({
    container: 'map',
    zoom: 15.4,
    center: [106.693039, 10.772404],

    pitch: 64.9,
    bearing: 172.5,
    antialias: true,
    style: '../map.json',
  });
}

private addLayer(options: any, index: number): void {
  this.map.on('style.load', () => {
    const _this = this;
    this.map.addLayer({
      id: 'custom-threebox-model ' + index,
      type: 'custom',
      renderingMode: '3d',
      onAdd: function () {
        _this.tb.loadObj(options, (model: any) => {
          model.setCoords(_this.coordList[index]);
          model.setRotation({ x: 0, y: 0, z: 0 });
          _this.tb.add(model);
        });
      },

      render: function () {},
    });
  });
}

ngOnInit(): void {
  this.buildMap();
  this.tb = (window as any).tb = new Threebox(
    this.map,
    this.map.getCanvas().getContext('webgl'),
    {
      defaultLights: true,
      enableSelectingObjects: true,
      enableDraggingObjects: true,
      enableTooltips: true,
      multiLayer: true,
    }
  );

  this.coordList.forEach((item, index) => {
    let i = index > 8 ? index + 1 : '0' + (index + 1);
    const options = {
      obj: this.path + i + '/0_anew_object.obj',
      mtl: this.path + i + '/autogenerate.mtl',
      type: 'mtl',
      scale: { x: 3.2, y: 3.2, z: 2.7 },
      units: 'meters',
      rotation: { x: 90, y: -90, z: 0 },
    };
    this.addLayer(options, index);
  });
}
问题原因
  1. 闭包变量引用冲突:addLayer中绑定的style.load回调是异步执行的,循环中创建的options对象在闭包中被共享引用。当事件触发时,循环已结束,所有回调都拿到了最后一次循环的options值,导致加载同一个mtl文件。
  2. 重复绑定事件:每次调用addLayer都会绑定一次style.load事件,事件触发时会执行所有回调,加剧了变量引用冲突的问题。
解决方案

修改后的代码

map: Map;
path = '../assets/SG_NhaThoDucBa/';
tb: any;

coordList = [
  [106.693039, 10.772404],
  [106.6929226, 10.77202084],
  [106.6927602, 10.7712239],
  [106.6926045, 10.7703538],
  [106.692724, 10.770191],
  [106.6935564, 10.770271],
  [106.6935902, 10.7702023],
  [106.6936212, 10.770139],
  [106.6935586, 10.7696358],
  [106.693639, 10.768409],
];

constructor() {}

private buildMap(): void {
  this.map = new Map({
    container: 'map',
    zoom: 15.4,
    center: [106.693039, 10.772404],
    pitch: 64.9,
    bearing: 172.5,
    antialias: true,
    style: '../map.json',
  });
}

// 封装loadObj为Promise(可选,优化异步逻辑)
private loadObjPromise(options: any): Promise<any> {
  return new Promise((resolve) => {
    this.tb.loadObj(options, resolve);
  });
}

ngOnInit(): void {
  this.buildMap();
  this.tb = (window as any).tb = new Threebox(
    this.map,
    this.map.getCanvas().getContext('webgl'),
    {
      defaultLights: true,
      enableSelectingObjects: true,
      enableDraggingObjects: true,
      enableTooltips: true,
      multiLayer: true,
    }
  );

  // 统一绑定一次style.load事件
  this.map.on('style.load', () => {
    this.coordList.forEach((item, index) => {
      let i = index > 8 ? index + 1 : '0' + (index + 1);
      const options = {
        obj: this.path + i + '/0_anew_object.obj',
        mtl: this.path + i + '/autogenerate.mtl',
        type: 'mtl',
        scale: { x: 3.2, y: 3.2, z: 2.7 },
        units: 'meters',
        rotation: { x: 90, y: -90, z: 0 },
      };

      const layerId = 'custom-threebox-model ' + index;
      this.map.addLayer({
        id: layerId,
        type: 'custom',
        renderingMode: '3d',
        // 使用箭头函数避免this指向问题,同时确保每个回调持有当前循环的独立options
        onAdd: async () => {
          const model = await this.loadObjPromise(options);
          model.setCoords(item);
          model.setRotation({ x: 0, y: 0, z: 0 });
          this.tb.add(model);
        },
        render: () => {},
      });
    });
  });
}

关键修改点

  1. 统一事件绑定:将所有模型加载逻辑放到单次style.load绑定的回调中,避免重复绑定事件。
  2. 解决闭包冲突:在forEach循环内部直接创建图层和加载模型,每个循环的options都是独立对象,不会被后续循环覆盖。
  3. 优化异步逻辑:封装loadObj为Promise,用async/await让异步代码更清晰(可选,不影响核心问题修复)。

内容的提问来源于stack exchange,提问作者Hiếu Trọng Hồ

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 04:16:02