Metal objects appear dark

Hello,

I am trying to load and render a gLTF model that contains some metallic surfaces and after rendering they appear dark (black, but glossy). I tried all the recommendations from other topics, but nothing works. Here is the code I was able to come up with.

Function to load model:

    async loadAndOptimizeGLTFModel(path) {
        return new Promise((resolve, reject) => {
            const loader = new GLTFLoader();
            const dracoLoader = new DRACOLoader();
            dracoLoader.setDecoderPath( '/lib/draco/' );
            loader.setDRACOLoader( dracoLoader );

            loader.load(path, (gltf) => {
                resolve(gltf);
            });
          });
    }

Post-processing:

renderer.gammaFactor = 2.2;
renderer.outputEncoding = THREE.sRGBEncoding;
//...
const light = new THREE.DirectionalLight()
light.castShadow = true
light.shadow.mapSize.width = 640
light.shadow.mapSize.height = 480
light.shadow.camera.near = 0.5
light.shadow.camera.far = 100
scene.add(light);
//...
var textureLoader = new THREE.TextureLoader();
        textureLoader.load('assets/textures/wave-textures.png', (texture) => {
            gltf.scene.children.forEach((child) => {
                child.children.forEach((c) => {
                    if(c instanceof THREE.Mesh && c.material.metalness === 1) {

                        if(c.material instanceof THREE.MeshStandardMaterial) {
                            console.log("YES, this is Standard Material");
                        }

                        c.material.envMap = texture;
                        c.material.envMapIntensity = 0.7;
                        c.material.color.convertSRGBToLinear();
                    };
                });
            });
        });  

Here are my logs messages:

93: YES, this is Standard Material

I use a white square with some grey waves as a texture for now.

Could you please suggest what I am doing wrong and how to make the object look like a real polished metal?
Thank you in advance!

  1. Make sure that the envMap is actually loading. Check Network panel in Chrome devtools and see if the request for the environment map texture has the correct texture in its response.

  2. Also since it looks like you’re loading just a simple flat texture (not an hdri), in the texture loader callback be sure to add a proper mapping:

texture.mapping = THREE.EquirectangularReflectionMapping;

Line 11 :point_right: example (if your texture has a different kind of mapping, ex. a cubemap, you can try others.)

You saved my day! Thank you!
Now my metal looks white and I need to find a proper texture.

By the way, what if I used hdri textures? How do I load it? Thanks!

You can load them using RGBELoader. Then you just set the texture as scene.environment.

Keep in mind that HDRI textures produce not only reflections but also light - and they will make other lights in the scene useless. HDRIs are effectively light data saved to a texture - thanks to that it’s possible to achieve very realistic lighting and reflections in 3D, based on just photographs.

I found a pretty good HDRI texture and loaded it as follows:

        let generator = new THREE.PMREMGenerator(this.renderer);

        new RGBELoader().load('assets/textures/HDRi-reflection-U.hdr', (hdrmap) => {
            let envmap = generator.fromEquirectangular(hdrmap);
            gltf.scene.children.forEach((child) => {
                child.children.forEach((c) => {
                    if(c instanceof THREE.Mesh && c.material.metalness === 1) {
                        c.material.envMap = envmap;
                    };
                });
            });
        });   

However, my model became black again. :sob:

See the code above (ie. this) - it’s a bit different from loading just a texture.

Load the HDRI, set .mapping, and set texture as scene.environment - that’s it. No more, no less, no models or generators involved, three will do the calculations for you on all models where it’s needed.

The trick is that my gTLF has a lot of different textures in it, that’s the reason I need to iterate over meshes like this:

gltf.scene.children.forEach((child) => {
                child.children.forEach((c) => {
                    if(c instanceof THREE.Mesh && c.material.metalness === 1) {
                        // Change texture of this particular mesh only, not the other ones.
                    }
              };
});

Is it possible to use HDRI with one mesh only instead of the environment of the whole scene?

scene.environment applies light and reflections to all models and textures in your scene. You wouldn’t need to set it manually anywhere - when using HDRIs you can ignore the existence of material.envMap.

If you’d like each submesh of your model to have different reflections (although it’s kinda hard to imagine a scenario in which you’d like that? :thinking:) - consider using just non-HDRI textures instead.

It is awesome! I tried to apply it to the whole scene and it seems that even non-metallic surfaces look fine. Thank you very much! Appreciate your help!

1 Like