react-three-fiber icon indicating copy to clipboard operation
react-three-fiber copied to clipboard

Materials returned by buildGraph are not reliable

Open AlaricBaraou opened this issue 1 year ago • 8 comments

While using the GltfLoader from three.js through drei useGltf hook, I noticed that the render varied from time to time after reloading. After investigating I found out that the order of objects returned by GltfLoader in the imported scene may vary from one load to the other.

Since buildGraph will group material by name, it'll use the first material with a specific name as the "reference" for other materials using the same name. Since the order may vary on load, the material returned for a particular name might vary too. That's also an expectation that might need to be discussed, the Gltf specs specify that a material name isn't necessarily unique.

https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html

Any top-level glTF object MAY have a name string property for this purpose. These property values are not guaranteed to be unique as they are intended to contain values created when the asset was authored.

And three.js also doesn't expect a material name to be unique https://threejs.org/docs/#api/en/materials/Material.name

Optional name of the object (doesn't need to be unique). Default is an empty string.

Therefore, I believe buildGraph should group by uuid, since three.js already reuse material in the GltfLoader https://github.com/utsuboco/three.js/blob/dev/examples/jsm/loaders/GLTFLoader.js#L3145

Materials could still return an object containing the names of the materials but with a constant unique identifier to it to differentiate them. Maybe something like the cacheKey that three.js use in GltfLoader https://github.com/utsuboco/three.js/blob/dev/examples/jsm/loaders/GLTFLoader.js#L3138

Doing so, there wouldn't be any "randomness" in which material gets to be returned by buildGraph under the common name. But this would be a breaking change and should probably be targeted for v9.

That said, here is a simple example.

  1. Loading a GLTf containing two mesh with these two materials
  2. Mesh1 has a material with the name 'cloth' Mesh2 has a similar material also using the name 'cloth' but with a vertexColors property set to true
  3. After loading, buildGraph traverse the scene, since the order isn't guaranteed, Mesh1 or Mesh2 can come up first.
  4. The first will assign see its material assigned to materials.cloth and the next one will be ignored
  5. Tools like gltfjsx will then assign the same cloth material to both mesh while they don't share it initially.

I hope I could make the issue clear enough, otherwise, let me know if something isn't clear.

If/once everyone agrees with this and on the best way to solve this, I'd be happy to make a PR myself. 👍

AlaricBaraou avatar Sep 16 '22 03:09 AlaricBaraou