使用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); }); }
问题原因
- 闭包变量引用冲突:
addLayer中绑定的style.load回调是异步执行的,循环中创建的options对象在闭包中被共享引用。当事件触发时,循环已结束,所有回调都拿到了最后一次循环的options值,导致加载同一个mtl文件。 - 重复绑定事件:每次调用
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: () => {}, }); }); }); }
关键修改点
- 统一事件绑定:将所有模型加载逻辑放到单次
style.load绑定的回调中,避免重复绑定事件。 - 解决闭包冲突:在
forEach循环内部直接创建图层和加载模型,每个循环的options都是独立对象,不会被后续循环覆盖。 - 优化异步逻辑:封装
loadObj为Promise,用async/await让异步代码更清晰(可选,不影响核心问题修复)。
内容的提问来源于stack exchange,提问作者Hiếu Trọng Hồ
相关产品推荐
相关产品推荐

