How to Modify GLTF Materials in Three.js

Loading GLTF models is a standard practice in Three.js, but changing their appearance dynamically requires accessing and modifying their specific materials. This article provides a straightforward guide on how to traverse a loaded GLTF scene, locate target meshes, and update their material properties such as color, roughness, and textures in real-time.

Loading the GLTF Model

To modify a GLTF model’s materials, you must first load the model using the GLTFLoader. Once loaded, the model’s 3D object hierarchy is accessible within the loader’s callback function.

import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';

const loader = new GLTFLoader();
loader.load('path/to/model.gltf', (gltf) => {
    const model = gltf.scene;
    scene.add(model);
    
    // Material modification code goes here
});

Traversing the Model to Find Materials

GLTF models are often made of nested groups and meshes. To access specific materials, use the traverse method on the loaded scene. This method executes a callback function for every descendant object in the model’s hierarchy.

model.traverse((child) => {
    if (child.isMesh) {
        console.log('Mesh Name:', child.name);
        console.log('Material Name:', child.material.name);
    }
});

Modifying Specific Materials

Once you have isolated the meshes, you can target specific parts of your model using the mesh name or the material name, which are typically defined in your 3D modeling software (like Blender).

Method 1: Target by Mesh Name

If you know the name of the mesh that holds the material, you can target it directly and update its properties.

model.traverse((child) => {
    if (child.isMesh && child.name === 'Car_Body') {
        // Change color to red
        child.material.color.set(0xff0000); 
        
        // Adjust physical properties
        child.material.roughness = 0.2;
        child.material.metalness = 0.8;
    }
});

Method 2: Target by Material Name

If a single material is applied to multiple meshes, targeting the material by its name is more efficient.

model.traverse((child) => {
    if (child.isMesh && child.material) {
        if (child.material.name === 'Chrome_Metal') {
            child.material.color.set(0xcccccc);
            child.material.metalness = 1.0;
            child.material.roughness = 0.1;
        }
    }
});

Method 3: Assigning a New Material

Instead of modifying the existing material, you can replace it entirely with a new Three.js material.

import * as THREE from 'three';

const newMaterial = new THREE.MeshStandardMaterial({
    color: 0x00ff00,
    roughness: 0.5,
    metalness: 0.1
});

model.traverse((child) => {
    if (child.isMesh && child.name === 'Glass_Window') {
        child.material = newMaterial;
    }
});

Handling Shared Materials (Cloning)

In many GLTF models, multiple meshes share the exact same material instance. If you modify the material of one mesh, it will automatically update all other meshes sharing that material.

To modify the material of a single mesh without affecting others, clone the material first:

model.traverse((child) => {
    if (child.isMesh && child.name === 'Unique_Part') {
        // Clone the material to make it unique to this mesh
        child.material = child.material.clone();
        
        // Apply changes
        child.material.color.set(0x0000ff);
    }
});

Updating Textures

To replace or add a texture to a GLTF material, use the TextureLoader to load your image, then assign it to the material’s map property.

const textureLoader = new THREE.TextureLoader();
const newTexture = textureLoader.load('path/to/texture.jpg');

// Ensure correct texture orientation for GLTF models
newTexture.flipY = false; 

model.traverse((child) => {
    if (child.isMesh && child.name === 'Tshirt') {
        child.material.map = newTexture;
        child.material.needsUpdate = true; // Tell Three.js to render the new texture
    }
});