Search code examples
javascriptthree.jsmodel3dtextures

three.js: Apply external texture to glTF


I have two files: "model.gltf" and "texture.jpeg". I want to apply texture file to model and render it using three.js but after 3 days of continuous research and hundreds of attempts, I just can't get it to work properly. My method that sets the current object in the scene:

/**
 * SET method: Sets object in the scene (deleting previous one)
 * 
 * @author Anton Pernisch <[email protected]>
 * @param {string} modelPath - Path to object in glTF format
 * @param {string} texturePath - Path to texture
 * @returns {boolean}
 * 
 * @todo Texture
 */
SET_Object: function(modelPath, texturePath) {
    // Clear out the scene
    for (var i = this.prodconf3_scene.children.length - 1; i >= 0; i--) { 
        obj = this.prodconf3_scene.children[i];
        this.prodconf3_scene.remove(obj); 
    }

    const modelLoader = new THREE.GLTFLoader();

    modelLoader.load(modelPath, (gltf) => {
        // HERE? apply texture from texturePath to this gltf

        this.prodconf3_obj = gltf.scene;
        this.prodconf3_scene.add(this.prodconf3_obj);
    });
    
    return true;
}

This code imports glTF just fine, it's just black. Eventually, I was able to apply .jpeg texture to this model, but only sort of. This is the best result I could get: here

And that was by this method:

/**
 * SET method: Sets current object to scene (deleting previous one)
 * 
 * @author Anton Pernisch <[email protected]>
 * @param {string} modelPath - Path to object in glTF format
 * @param {string} texturePath - Path to texture
 * @returns {boolean}
 * 
 * @todo Texture
 */
SET_Object: function(modelPath, texturePath) {
    // Clear out the scene
    for (var i = this.prodconf3_scene.children.length - 1; i >=0 ; i--) { 
        obj = this.prodconf3_scene.children[i];
        this.prodconf3_scene.remove(obj); 
    }

    const modelLoader = new THREE.GLTFLoader();
    const textureLoader = new THREE.TextureLoader();

    modelLoader.load(modelPath, (gltf) => {
        var model = gltf.scene;
        model.traverse((o) => {
            if (o.isMesh) {
                o.material = new THREE.MeshBasicMaterial({map: textureLoader.load(texturePath)});
            }
        });

        this.prodconf3_obj = model;
        this.prodconf3_scene.add(this.prodconf3_obj);
    });
    
    return true;
}

I have also tried creating texture by TextureLoader load method and then meshing it together with gltf.scene from modelLoader.load function. That didn't work at all, I was getting

Uncaught TypeError: Cannot read property 'center' of undefined

error (I'm not sure about using gltf.scene as the first argument in THREE.Mesh() tho).

So, my question is, what is the proper and updated approach to properly apply the texture to glTF in three.js?


Solution

  • This looks fine to me, but I wonder if we're supposed to pass a reference to an already loaded texture.

    Object3D#Traverse isn't asynchronous, so we'll have to update the material synchronously.

    Live example.

    SET_Object: async function(modelPath, texturePath) {
        // Clear out the scene
        for (let i = this.prodconf3_scene.children.length - 1; i >= 0; i--) { 
            obj = this.prodconf3_scene.children[i];
            this.prodconf3_scene.remove(obj); 
        }
    
        const modelLoader = new THREE.GLTFLoader();
        const textureLoader = new THREE.TextureLoader();
    
        // Load texture
        const texture = await textureLoader.loadAsync(texturePath);
        texture.flipY = false;
    
        // Load model
        const { scene: model } = await modelLoader.loadAsync(modelPath);
    
        // Update model
        model.traverse((o) => {
            if (o.isMesh) {
                o.material.map = texture;
                o.material.needsUpdate = true;
            }
        });
    
        this.prodconf3_obj = model;
        this.prodconf3_scene.add(this.prodconf3_obj);
        
        return this.prodconf3_obj;
    }