I am trying to import GLB file into a scene (using THREE.GLTFLoader) and the issue I am facing with is the following:
The GLB file itself has a object with 4 different materials, and during the export I am getting 4 different meshes out of it. This makes things difficult because I want to have everything as a single mesh.
on the picture you may see the structure of GLB file in Blender.
GLTFLoader does not create multi-material meshes. In the screenshot above it will create four meshes as children of a single parent Group. You’ll need to either work with that Group instead, or merge them after loading using BufferGeometryUtils.
If you mean putting multiple THREE.Mesh instances as children of a shared THREE.Group, then there’s nothing wrong with that but it will not reduce draw calls. One Mesh/Material pair is one draw call. InstancedMesh will help if you’re drawing many of the same Mesh/Material pair.
as you said in case of multimaterial it creates new group and adds meshes formed by separating materials to it.
i need to identify this group later. is there any way to identify this ? or could you provide the part of code in the gltf loader where this process is done so that i can add one property in the userData for that group.
I believe you can assume that if you see several THREE.Mesh instances as direct children of a THREE.Group in the output of GLTFLoader, they were originally a multi-primitive mesh. There is no precise match to the concept of “multi-material mesh” in glTF.
Thanks for the answer, by checking the response of the GLTF loader, I could find nodes and meshes in parser.json.
Using this I identified nodes with multiple primitives, names of these nodes matches with the names of groups in the scene whose children are separated by materials.
here’s the code snippet
const loader = new GLTFLoader();
// Load the GLB scene
loader.load(
url,
(gltf: any) => {
const scene = gltf.scene;
processJson(gltf.parser.json);
processScene(scene);
},
(xhr: ProgressEvent<EventTarget>) => {
console.log((xhr.loaded / xhr.total) * 100);
},
(error) => {
console.log(error);
}
);
})
processJson(json: any) {
const { meshes, nodes } = json;
// identify groups with multi mat separated children
if (meshes) {
for (const node of nodes){
const {mesh: meshIndex, name} = node;
const mesh = meshes[meshIndex];
let primitivesWithMatCount = 0;
const { primitives } = mesh;
for (const primitive of primitives) {
if ("material" in primitive) {
primitivesWithMatCount++;
}
}
if (primitivesWithMatCount > 1) {
combinedMeshesParentNames.push(name);
}
}
}
}
combine meshes into one
const areChildrenSeparatedByMat= (child: any) => {
if (!child.isGroup) return false;
const { name } = child;
return combinedMeshesParentNames.includes(name);
};
const combineMeshes = (meshes: any[]) => {
var materials = [],
geometries = [],
mergedGeometry: any = new THREE.BufferGeometry(),
meshMaterial,
mergedMesh;
for (const mesh of meshes) {
mesh.updateMatrix();
geometries.push(mesh.geometry);
meshMaterial = new THREE.MeshStandardMaterial(mesh.material as any);
materials.push(meshMaterial);
}
mergedGeometry = mergeBufferGeometries(geometries, true);
mergedGeometry.groupsNeedUpdate = true;
mergedMesh = new THREE.Mesh(mergedGeometry, materials);
return mergedMesh;
};
const combineChildren = (group: THREE.Group) => {
const children = group.children;
const mergedMesh = combineMeshes(children);
mergedMesh.name = group.name;
mergedMesh.userData = group.userData;
if (group.parent) {
group.parent.add(mergedMesh);
group.parent.remove(group);
} else {
// todo : need to handle this
}
};
processScene(scene: THREE.Scene) =>{
if (combinedMeshesParentNames.length == 0) return
scene.traverse((child: any) => {
if (areChildrenSeparatedByMat(child)) {
combineChildren(child);
}
});
}