PBR materials do not show at all, Standard materials are always pure black

Hey, hard to post a sandbox example for this since the entire script is some 2,000 lines long, but I have a scene with a bright light in it and a bunch of gaussian splat meshes. When I load in a PBR GLB generated in Blender with normals, it does not appear. I can assign this material:

					neonMaterial = new BABYLON.StandardMaterial("neonMaterial", scene);
					neonMaterial.emissiveColor = new BABYLON.Color3(0.35, 0.96, 0.88);
					neonMaterial.alpha = 0.9;
					neonMaterial.alphaMode = BABYLON.Constants.ALPHA_ADD;

But for whatever reason any other material will not work. The meshes are solid, have the faces pointing the right way, etc. Any ideas?

Here is my scene loading function:

//////////////////////////////////////////////////////////////
/////////////////////////// LOAD SCENE////////////////////////

// Function to load scene
async function loadScene(roomData) {
	try {

		clearCurrentScene();

		// Object to store panel positions and meshes
		let panelData = {};

		// Dispose of the background mesh
		if (backgroundMesh) {
			backgroundMesh.dispose();
			backgroundMesh = null;
		}

		// Create a transform node to hold the GLB meshes
		const glbTransformNode = new BABYLON.TransformNode("glbTransformNode", scene);

		// Load the new splat mesh
		const splatMesh = await loadSplatMesh(roomData.urls[0]);
		if (splatMesh) {
			currentSplatMeshes.push(splatMesh);
			// Position the GLB transform node at the splat mesh position
			glbTransformNode.position = splatMesh.position;
		} else {
			console.warn(`Failed to load SPLAT mesh for room: ${roomData.name}`);
		}

		// Store panel data
		scene.metadata = scene.metadata || {};
		scene.metadata.panelData = panelData;
		console.log("Panel data stored:", panelData);

		// Load GLB models
		await loadGLBModel(roomData.urls[1], glbTransformNode, roomData);
		console.log("GLB model loaded successfully");

		// Play the entry audio
		if (roomData.entryaudio) {
			const audioArrayBuffer = await loadLargeFile(roomData.entryaudio);
			if (audioArrayBuffer) {
				const audioBlob = new Blob([audioArrayBuffer], { type: 'audio/mpeg' });
				await playAudio(URL.createObjectURL(audioBlob));
				console.log("Entry audio started playing");
			} else {
				console.warn("Failed to load entry audio");
			}
		}

	} catch (error) {
		console.error("Error loading scene:", error);
	}
}

async function loadSplatMesh(url) {
	const splatScale = 1.5;
	
	try {
		console.log(`Starting to load SPLAT mesh from: ${url}`);
		
		const splatArrayBuffer = await loadLargeFile(url);

		if (!splatArrayBuffer) {
			console.warn(`Failed to load SPLAT mesh from: ${url}`);
			return null; // Return null instead of throwing an error
		}

		console.log(`SPLAT file size: ${splatArrayBuffer.byteLength} bytes`);

		if (splatArrayBuffer.byteLength % 4 !== 0) {
			console.warn(`Invalid SPLAT file size: ${splatArrayBuffer.byteLength} bytes (not a multiple of 4)`);
			return null; // Return null instead of throwing an error
		}

		const splatMesh = new BABYLON.GaussianSplattingMesh("SplatLevel", null, scene);
		
		await new Promise((resolve, reject) => {
			try {
				splatMesh._loadData(splatArrayBuffer);
				resolve();
			} catch (error) {
				console.warn(`Error loading SPLAT data: ${error.message}`);
				reject(error);
			}
		});
		
		console.log("SPLAT mesh loaded successfully");

		splatMesh.position = new BABYLON.Vector3(0, 1.7, 0);
		splatMesh.scaling = new BABYLON.Vector3(splatScale, splatScale, splatScale);
		splatMesh.isPickable = false;

		return splatMesh;
	} catch (error) {
		console.error("Error loading SPLAT mesh:", error);
		return null; // Return null instead of throwing an error
	}
}

async function loadGLBModel(url, glbTransformNode, roomData) {
	let assetContainer;
	let neonMaterial;

	try {
		console.log("Starting to load GLB from:", url);
		const glbArrayBuffer = await loadLargeFile(url);
		if (!glbArrayBuffer) {
			throw new Error("Failed to load GLB file");
		}
		console.log("GLB file fetched from cache or network");

		// Ensure we're working with an ArrayBuffer
		const arrayBuffer = glbArrayBuffer instanceof ArrayBuffer ? glbArrayBuffer : await glbArrayBuffer.arrayBuffer();

		// Create a Blob from the ArrayBuffer
		const blob = new Blob([arrayBuffer], { type: 'model/gltf-binary' });
		const blobUrl = URL.createObjectURL(blob);

		return new Promise((resolve, reject) => {
			BABYLON.SceneLoader.LoadAssetContainer(
				"",
				blobUrl,
				scene,
				function (container) {
					URL.revokeObjectURL(blobUrl);
					assetContainer = container;
					console.log("GLB loaded successfully", assetContainer);

					// Log material information for debugging
					container.meshes.forEach(mesh => {
						if (mesh.material) {
							console.log(`Material for mesh ${mesh.name}:`, mesh.material);
						}
					});

					let panelData = {};
					let teleporterMeshes = [];

					neonMaterial = new BABYLON.StandardMaterial("neonMaterial", scene);
					neonMaterial.emissiveColor = new BABYLON.Color3(0.35, 0.96, 0.88);
					neonMaterial.alpha = 0.9;
					neonMaterial.alphaMode = BABYLON.Constants.ALPHA_ADD;

					assetContainer.addAllToScene();

					processMeshes(assetContainer.meshes, glbTransformNode, neonMaterial, panelData, roomData, teleporterMeshes);

					glbTransformNode.position = new BABYLON.Vector3(0, 1.7, 0);
					glbTransformNode.scaling = new BABYLON.Vector3(-1, 1, 1);

					handleTeleporters(teleporterMeshes);
					console.log("Starting to load panels...");
					loadPanels(roomData);
					console.log("Finished loading panels");    
					resolve();
				},
				function (event) {
					if (event.lengthComputable) {
						const progress = event.loaded / event.total * 100;
						console.log(`Loading progress: ${progress.toFixed(2)}%`);
					}
				},
				function (scene, message, exception) {
					URL.revokeObjectURL(blobUrl);
					console.error(`Error loading GLB: ${message}`, exception);
					reject(new Error(message));
				},
				".glb"
			);
		});
	} catch (error) {
		console.error("Error loading GLB:", error);
		console.error("Stack trace:", error.stack);
		if (assetContainer) {
			assetContainer.dispose();
		}
		if (neonMaterial) {
			neonMaterial.dispose();
		}
		throw error;
	}
}

function processMeshes(meshes, glbTransformNode, neonMaterial, panelData, roomData, teleporterMeshes) {
	console.log(`Processing ${meshes.length} meshes...`);
	for (const mesh of meshes) {
		try {
			console.log(`Processing mesh: ${mesh.name}`);
			mesh.parent = glbTransformNode;
			mesh.isPickable = true;
			mesh.renderingGroupId = 1;
	
			if (mesh.name.toLowerCase().includes("collider")) {
				mesh.visibility = 0;
				mesh.isPickable = false;
				mesh.checkCollisions = true;
				console.log("Collider mesh found:", mesh.name);
			}
	
			new TeleportTarget(mesh, scene);                
			currentGLBMeshes.push(mesh);
			
			if (mesh.name.toLowerCase().includes("teleport")) {
				teleporterMeshes.push(mesh);
				mesh.material = neonMaterial;
			}
	
			if (mesh.name.toLowerCase().includes("panel")) {
				mesh.setEnabled(false);
				scene.metadata.panelData[mesh.name] = {
					position: mesh.getAbsolutePosition().clone(),
					mesh: null
				};
			}
	
			console.log("Mesh processed:", mesh.name);
		} catch (error) {
			console.error(`Error processing mesh ${mesh.name}:`, error);
		}
	}
	console.log("All meshes processed");
}

  function loadPanels(roomData) {
	let locationData = buttonsData.find(location => 
	  location.rooms.some(room => room.name === roomData.name)
	);
  
	if (locationData && locationData.panels) {
	  locationData.panels.forEach(panelInfo => {
		let panelData = scene.metadata.panelData[panelInfo.name];
		if (panelData) {
		  loadPanelGLB(panelInfo, panelData.position)
			.then(panelRoot => {
			  panelData.mesh = panelRoot;
			})
			.catch(error => console.error(`Failed to load panel: ${panelInfo.name}`, error));
		}
	  });
	}
  }

async function loadPanelGLB(panelInfo, position) {
	try {
	  console.log(`Loading panel GLB from: ${panelInfo.glbUrl}`);
	  const arrayBuffer = await loadLargeFile(panelInfo.glbUrl);
	  
	  if (!arrayBuffer) {
		throw new Error("Failed to load panel GLB file");
	  }
  
	  return new Promise((resolve, reject) => {
		const blob = new Blob([arrayBuffer], { type: 'application/octet-stream' });
		const url = URL.createObjectURL(blob);
  
		BABYLON.SceneLoader.ImportMesh("", "", url, scene, function(panelMeshes) {
		  URL.revokeObjectURL(url);
		  console.log(`Panel GLB loaded, meshes:`, panelMeshes);
		  
		  if (panelMeshes.length === 0) {
			reject(new Error("No meshes were imported from the GLB file"));
			return;
		  }
  
		  let panelRoot = new BABYLON.TransformNode("panelRoot", scene);
		  
		  let importedRoot = panelMeshes[0];
		  while (importedRoot.parent && panelMeshes.includes(importedRoot.parent)) {
			importedRoot = importedRoot.parent;
		  }
		  
		  importedRoot.parent = panelRoot;
		  
		  panelRoot.position = position;
		  panelRoot.scaling = new BABYLON.Vector3(1, 1, 1);
		  
		  setupPanelMesh(importedRoot, panelInfo);
		  
		  panelRoot.setEnabled(true);
		  console.log(`Panel loaded and positioned at ${panelRoot.position}`);
		  resolve(panelRoot);
		}, null, function(scene, message, exception) {
		  URL.revokeObjectURL(url);
		  console.error(`Error loading panel GLB: ${message}`, exception);
		  reject(new Error(`Failed to load panel GLB: ${message}`));
		}, ".glb");
	  });
	} catch (error) {
	  console.error(`Error loading panel GLB: ${error}`);
	  throw error;
	}
  }

  function setupPanelMesh(node, panelInfo) {
	if (node instanceof BABYLON.AbstractMesh) {
		node.isPickable = true;
		node.renderingGroupId = 1;

		// Create and apply the ClickPanelBehavior
		const clickBehavior = new ClickPanelBehavior(node, 3, panelInfo.audioUrl);
		
		// Convert the material
		clickBehavior.convertToStandardMaterial();

		// Preserve the original material
		if (node.material) {
			node.material.freeze();
		}
	}
	node.getChildMeshes().forEach(childMesh => setupPanelMesh(childMesh, panelInfo));
}


function handleTeleporters(teleporterMeshes) {
	if (teleporterMeshes.length === 0) {
	  console.log("No teleporter found in the imported GLB");
	} else {
	  console.log("Teleporters found:", teleporterMeshes.length);
	  let firstTeleporter = teleporterMeshes[0];
	  console.log("First teleporter found:", firstTeleporter.getAbsolutePosition());
	  
	  // Create a TeleportTarget for the first teleporter
	  const teleportTarget = new TeleportTarget(firstTeleporter, scene);
	  
	  // Teleport the camera after a short delay
	  setTimeout(() => {
		teleportTarget.teleportCamera();
	  }, 100); // 100ms delay, adjust as needed
	}
  }

// Function to clear the cache (useful for updating assets)
async function clearCache() {
	const cache = await caches.open('large-file-cache');
	const keys = await cache.keys();
	for (const key of keys) {
		await cache.delete(key);
	}
	console.log("Cache cleared");
}
/////////////////////////// LOAD SCENE////////////////////////		
//////////////////////////////////////////////////////////////

Pbr and standard do not use the same light unit, do you see it black or not visible at all ?

Also it would be almost impossible to troubleshoot without a repro in the playground. All your code is probably not needed for the material repro only…

I can take a look at splitting out the loading and caching section, I think that’s what’s causing it. I’ll try to section it out later tonight/tomorrow.

PBR = invisible, can’t see it at all. Standard is pure black no matter what light type.

Solved the issue. I was clearing the scene between loads and somehow clearing the scene materials, textures and lighting before loading the new scene caused the issue:

	// Clear all materials
	if (scene.materials) {
		scene.materials.slice().forEach(material => {
			console.log(`Disposing material: ${material.name}`);
			material.dispose();
		});
	}

	// Clear all textures
	if (scene.textures) {
		scene.textures.slice().forEach(texture => {
			console.log(`Disposing texture: ${texture.name}`);
			texture.dispose();
		});
	}

	// Clear all lights
	if (scene.lights) {
		scene.lights.slice().forEach(light => {
			console.log(`Disposing light: ${light.name}`);
			light.dispose();
		});
	}
1 Like