Hello,
I’m trying to assign custom materials to mesh children (parent is armature, children are body, eyes, tongue etc.).
The file is a glb with meshopt compression. I traverse trough the parent and assign textures and materials like this:
parentMesh.traverse((node) => {
if (node.isMesh) {
if (node.name == "body") {
const bodyTexture = new THREE.TextureLoader(this.manager).load("bodyTexture.jpg");
if (typeof bodyTexture === "object" && bodyTexture !== null) {
const material = new THREE.MeshPhysicalMaterial({
map: bodyTexture
})
}
node.material = material;
}
// other mesh parts
});
this.el.object3D.add(parentMesh); // I'm calling this snippet inside an a-frame component so I'm refreshing the object in the scene
The mesh appears white. The interesting thing is I’m able to set (and see) the base color without any problems but any texture map (base color, normal map, roughness) is not visible. The textures are loaded properly, I can also see them assigned in the material and the material assigned to the mesh in the console. Also, the same code works like a charm when switching to a glb file without compression.
I’m quite struggling to debug this so I would be grateful for hints on how to approach this. I would like to use the compressed mesh as the size difference is huge (8 MB vs 600 kB) and I can’t use draco compression as I don’t see the same compression results. Thanks in advance.
Can you make sure the mesh geometry actually has texture coordinates? There should be a uv
entry in the attributes
geometry property.
1 Like
No, the uv coordinates are missing.
So the solution is you have to make sure the geometry has texture coordinates otherwise applying textures won’t work.
A tool like Blender can help you with adding texture coordinates to your assets.
I probably was not clear enough, the uncompressed mesh (exported right out of blender) of course has UV coordinates and everything is fine, the problem is only with the compressed mesh. Do you think there is a way to fix it in blender? Blender doesn’t seem to be able to open the compressed mesh so I probably need some special plugin for the loader? If I manage to do that do I have to unwrap the model again? I would rather not go trough this again as I would have to probably redo all the textures as well. I tried to play with different parameters during compression in with gltfpack but it didn’t help.
If the model you’re exporting doesn’t have a texture assigned at export time… I bet meshopt will remove the UVs as part of its optimization.
There might be a flag you can pass to meshopt to preserve unused attributes… or you can just assign a small dummy texture+material before exporting and that might do the trick.
1 Like
There is an assigned texture, altough I opted to not include the textures in the file at export to reduce the size. So I tried to export it with a dummy texture but the coordinates are still missing. I also tried to play around with different parameters in gltfpack but no difference. I’m probably missing something obvious…
If you don’t find a solution with gltfpack, maybe you can reconsider your strategy and include the texture as a KTX2 compressed texture in the asset as well. You will end up with a smaller asset size in total compared with loading a JPG texture separately.
If this doesn’t work for you, consider to ask a question at the gltfpack repository: GitHub - zeux/meshoptimizer: Mesh optimization library that makes meshes smaller and faster to render
1 Like
I was able to figure it out with the help of the repo community. The suggestion to use a dummy texture is a working solution, as no assigned texture (at export) is considered as there is no need for texture coordinates by gltfpack. But I didn’t know it is necessary to reassign the coordinates to the new texture. Thank you for your help
Updated code snipped
parentMesh.traverse((node) => {
if (node.isMesh) {
if (node.name == "body") {
const bodyTexture = new THREE.TextureLoader(this.manager).load("bodyTexture.jpg");
const modelMaterial = node.material;
bodyTexture.repeat.copy(modelMaterial.map.repeat); //without this the map doesn't appear or is distorted
bodyTexture.offset.copy(modelMaterial.map.offset); //without this the map doesn't appear or is distorted
if (typeof bodyTexture === "object" && bodyTexture !== null) {
const material = new THREE.MeshPhysicalMaterial({
map: bodyTexture
})
}
node.material = material;
}
// other mesh parts
});
this.el.object3D.add(parentMesh); // I'm calling this snippet inside an a-frame component so I'm refreshing the object in the scene
You’ll probably also want texture.flipY= false
here. If you’d like an implementation of meshopt compression that does not removed unused UV coordinates, the script below will work:
npm install --global @gltf-transform/cli
gltf-transform meshopt input.glb output.glb
However, this only does meshopt compression, and by default the gltfpack
utility does much more than just that. If you need those other features, see gltf-transform --help
.
1 Like