How to avoid duplicate materials with multi asset loading

Hi All

So I have a scene that loads many different assets into it. Many assets have same named materials and it is meant to be this way. So if I change the material texture , it shouldd update all assets using that material.

Of coarse I noticed this was not happening and so logged the materials array of the scene and noticed many instances of the said single material , instead of one.

So my question is how do I load an asset and when it loads it does not create a duplicate material , but rather if it finds that material in the scene already it just maps to that one?

Hello, the materials have to be shared in your DCC tools (or you need to run some code to share them after load)

HI thanks,

Well they were shared in the main file from my DCC , just that individual parts were exported out of the main file as separate .glb.

This is to reduce un-needed bloat in the babylon scene by only doing on demand loading.

Anyway I will look into doing some runtime material management post loading to see to get rid of any duplicates created.

cheers

1 Like

@Deltakosh I know we marked this as solved but I thought to share some code here to help others who might want to look into this.

The reason being , it was not a straight forward thing to do as I discovered some peculiar race conditions that will definitely catch a person of guard when trying to do this.

The race condition is in thinking that the scene material list gets updated exactly once just before each loadSuccess callback while loading several items.

What I found is that when loading several items , it is possible the list can be populated by several items as they are parsed internally before the callbacks. I expected one round of updates to the scene materials per asset just before each callback.

For example if you load say several .glb assets… on the first asset load Success you might see the materials list has the materials of this asset only, all is good.

On the second loadSuccess , you would expect to see only the addition of that assets materials to the list.

This is where you will fault. The scene materials could be populated by several other assets already.

Anyway that is quite a mouthful , sorry :wink: anyway here is a snippet of code I did that managed to get it all working even with this unusual behaviour :

this.Asset.prototype = {
	//other code before here...


	loadSuccess:function(event){			
		this.loaded = true;
		this.loading = false;			
		var m = this.assetController.sceneController.scene.materials;
		var ml = this.assetController.sceneController.scene.materials.length;
		
		//this is to keep a separate collection of only single instance materials with same name , the first loaded is the one used  for all subsequent asset loads
		for(var i=0;i< ml;i++){
				
			if(this.assetController.collectionMaterials[m[i].name] === null || this.assetController.collectionMaterials[m[i].name] === undefined){					
				this.assetController.collectionMaterials[m[i].name] =  m[i];
				//change sorting for alpha textures
				if(m[i].albedoTexture){
					if(m[i].albedoTexture.hasAlpha){				
						m[i].transparencyMode = 1;
					}		
				}
			}			
		}

		//collection to dispose of duplicates
		var disposableMaterials = [];

		//recursive function to handle marking duplicates to be disposed and also setting those assets to use the materials already in the scene
		this.handleDuplicateMaterials = function(nodesArray){		
						
			for(var i=0;i< nodesArray.length;i++){					
				var n = nodesArray[i];									
				if(n.material){						
					var existingMaterial = this.assetController.collectionMaterials[n.material.id];
					if(existingMaterial !== null && existingMaterial !== undefined ){							
						if(n.material.uniqueId !== existingMaterial.uniqueId){								
							disposableMaterials.push(n.material);
							n.material = null;								
							n.material = existingMaterial;
						}
					}
				}
				var nc = n.getChildren();
				if(nc !== null && nc !== undefined ){
					if(nc.length > 0){
						this.handleDuplicateMaterials(nc);
					}
				}
			}
				
		};
		
		//only call the recursive function using the __root__  node , as other items in the event list will all be processed via this recursice loop
		this.handleDuplicateMaterials(event[0].getChildren());
		
		//finnally dispose of the duplicates. 
		for(var i=0;i< disposableMaterials.length;i++){				
			disposableMaterials[i].dispose(true,true);
		}


		disposableMaterials = [];
			
		//othe code follows here ....	
	}	
	
};
3 Likes