Извлечение и замена материалов загруженной модели

В процессе загрузки 3D-моделей в сцену Three.js геометрия и материалы обычно приходят как единый объект, состоящий из иерархии узлов. Каждый узел может представлять отдельную часть модели и иметь собственный материал. Для корректной работы с внешними форматами (GLTF, OBJ, FBX) необходимо понимать структуру объекта после загрузки.

В большинстве случаев загруженная модель представляет собой экземпляр THREE.Group или THREE.Scene, внутри которого находятся объекты THREE.Mesh. Именно у Mesh хранятся материалы и геометрия.

Пример структуры:

Model (Group)
 ├─ Body (Mesh) → material: MeshStandardMaterial
 ├─ Wheels (Mesh) → material: MeshStandardMaterial
 └─ Glass (Mesh) → material: MeshPhysicalMaterial

Каждый узел может иметь собственный материал или массив материалов, если используется многоматериальная геометрия.

Получение доступа к материалам модели

После загрузки модели необходимо пройти по иерархии объектов сцены и найти все Mesh. Для этого используется метод traverse.

model.traverse(function(child) {

    if (child.isMesh) {
        console.log(child.material);
    }

});

Метод traverse рекурсивно обходит все дочерние элементы, позволяя получить доступ к материалам каждой части модели.

Часто требуется определить:

  • какие материалы используются
  • на каких объектах они находятся
  • сколько их всего

Это особенно важно при работе с крупными моделями, где количество материалов может достигать десятков.

Особенности материалов в импортированных моделях

Загруженные модели могут использовать различные типы материалов:

  • MeshStandardMaterial
  • MeshPhysicalMaterial
  • MeshPhongMaterial
  • MeshBasicMaterial

Формат glTF обычно использует MeshStandardMaterial, так как он поддерживает PBR-рендеринг (Physically Based Rendering).

Материал может содержать множество текстур:

  • map — диффузная текстура
  • normalMap — карта нормалей
  • roughnessMap — шероховатость
  • metalnessMap — металлическость
  • emissiveMap — самосвечение
  • aoMap — ambient occlusion

Пример доступа к текстуре:

child.material.map

Массив материалов

Иногда геометрия использует несколько материалов одновременно. В этом случае material будет массивом.

if (Array.isArray(child.material)) {

    child.material.forEach((material) => {
        console.log(material);
    });

}

Каждый материал соответствует определённой группе полигонов внутри геометрии.

Поиск материала по имени

При экспорте из 3D-редакторов (Blender, Maya, 3ds Max) материалам часто задаются имена. Эти имена сохраняются в модели и позволяют легко находить нужный материал.

let targetMaterial = null;

model.traverse((child) => {

    if (child.isMesh && child.material.name === "CarPaint") {
        targetMaterial = child.material;
    }

});

Это особенно удобно, когда требуется изменить только один конкретный материал.

Замена материала объекта

Самый простой способ изменить внешний вид части модели — заменить материал.

model.traverse((child) => {

    if (child.isMesh) {

        child.material = new THREE.MeshStandardMaterial({
            color: 0xff0000
        });

    }

});

После этого объект будет использовать новый материал.

Важно учитывать, что замена материала удаляет все текстуры, которые были назначены ранее.

Частичная замена материалов

Иногда требуется изменить только определённую часть модели. Например, перекрасить корпус автомобиля, но оставить стекло и колёса без изменений.

model.traverse((child) => {

    if (child.isMesh && child.name === "Body") {

        child.material = new THREE.MeshStandardMaterial({
            color: 0x3366ff,
            metalness: 0.8,
            roughness: 0.3
        });

    }

});

Использование имени объекта позволяет точно контролировать замену.

Изменение параметров существующего материала

Полная замена материала не всегда необходима. Иногда достаточно изменить свойства существующего материала.

model.traverse((child) => {

    if (child.isMesh) {

        child.material.color.set(0x00ff00);
        child.material.roughness = 0.6;
        child.material.metalness = 0.2;

    }

});

Такой подход сохраняет все текстуры и другие параметры.

Замена текстур материала

Текстуры также могут быть заменены после загрузки модели.

const textureLoader = new THREE.TextureLoader();

const newTexture = textureLoader.load("textures/new_diffuse.jpg");

model.traverse((child) => {

    if (child.isMesh) {

        child.material.map = newTexture;
        child.material.needsUpdate = true;

    }

});

Флаг needsUpdate сообщает рендереру, что материал необходимо пересобрать.

Использование нескольких материалов

Если геометрия использует массив материалов, замена выполняется по индексам.

model.traverse((child) => {

    if (child.isMesh && Array.isArray(child.material)) {

        child.material[0] = new THREE.MeshStandardMaterial({
            color: 0xff0000
        });

    }

});

Каждый индекс соответствует определённой группе полигонов.

Клонирование материалов

При работе с несколькими объектами важно учитывать, что материалы могут быть общими. Несколько мешей могут ссылаться на один и тот же экземпляр материала.

Изменение такого материала приведёт к изменению всех объектов.

Чтобы избежать этого, используется клонирование.

model.traverse((child) => {

    if (child.isMesh) {

        child.material = child.material.clone();
        child.material.color.set(0x0000ff);

    }

});

Теперь каждый объект имеет собственный материал.

Замена материалов с сохранением текстур

Иногда требуется заменить тип материала, но сохранить существующие текстуры.

model.traverse((child) => {

    if (child.isMesh) {

        const oldMaterial = child.material;

        const newMaterial = new THREE.MeshStandardMaterial({
            map: oldMaterial.map,
            normalMap: oldMaterial.normalMap,
            roughnessMap: oldMaterial.roughnessMap,
            metalnessMap: oldMaterial.metalnessMap
        });

        child.material = newMaterial;

    }

});

Это позволяет перейти, например, с MeshPhongMaterial на MeshStandardMaterial.

Замена всех материалов сцены

Иногда используется техника глобальной замены материалов, например, для режима отображения wireframe или отладочного рендера.

model.traverse((child) => {

    if (child.isMesh) {

        child.material = new THREE.MeshBasicMaterial({
            wireframe: true,
            color: 0xffffff
        });

    }

});

Такой режим часто используется при диагностике геометрии.

Кэширование оригинальных материалов

При временной замене материалов полезно сохранять оригинальные экземпляры.

const originalMaterials = new Map();

model.traverse((child) => {

    if (child.isMesh) {

        originalMaterials.set(child.uuid, child.material);

        child.material = new THREE.MeshNormalMaterial();

    }

});

Позже можно восстановить исходные материалы.

model.traverse((child) => {

    if (child.isMesh) {

        child.material = originalMaterials.get(child.uuid);

    }

});

Замена материалов при загрузке модели

Материалы можно модифицировать сразу после загрузки модели.

loader.load("model.glb", function(gltf) {

    const model = gltf.scene;

    model.traverse((child) => {

        if (child.isMesh) {

            child.material.metalness = 0.9;
            child.material.roughness = 0.1;

        }

    });

    scene.add(model);

});

Это позволяет автоматически адаптировать внешний вид модели под освещение сцены.

Использование пользовательских шейдеров

Вместо стандартных материалов можно назначить ShaderMaterial.

model.traverse((child) => {

    if (child.isMesh) {

        child.material = new THREE.ShaderMaterial({
            vertexShader: vertexShaderSource,
            fragmentShader: fragmentShaderSource
        });

    }

});

Такой подход используется для сложных эффектов:

  • процедурные материалы
  • анимация поверхности
  • нестандартное освещение

Производительность при работе с материалами

Частая замена материалов может снижать производительность, поскольку рендерер вынужден перекомпилировать шейдеры.

Рекомендуемые практики:

  • избегать постоянного создания новых материалов
  • использовать клонирование вместо полного создания
  • изменять свойства существующих материалов
  • повторно использовать экземпляры материалов

Также полезно отслеживать количество материалов в сцене:

console.log(renderer.info.memory.textures);
console.log(renderer.info.programs);

Это помогает контролировать расход GPU-ресурсов.

Работа с материалами внутри сложных моделей

Некоторые модели содержат вложенные группы:

Car
 ├─ Interior
 │   ├─ Seats
 │   └─ Dashboard
 └─ Exterior
     ├─ Body
     └─ Wheels

В таких случаях удобно фильтровать объекты по имени или типу.

model.getObjectByName("Wheels")

После получения объекта материал можно заменить напрямую.

const wheels = model.getObjectByName("Wheels");

wheels.material = new THREE.MeshStandardMaterial({
    color: 0x222222
});

Этот метод позволяет работать с конкретными частями модели без обхода всей иерархии.

Управление прозрачностью материалов

Для элементов вроде стекла или экранов можно изменить параметры прозрачности.

model.traverse((child) => {

    if (child.isMesh && child.name === "Glass") {

        child.material.transparent = true;
        child.material.opacity = 0.5;

    }

});

Также может потребоваться отключение записи в глубину:

child.material.depthWrite = false;

Это предотвращает артефакты при рендеринге прозрачных поверхностей.

Итоговая роль управления материалами

Гибкое извлечение и замена материалов позволяют:

  • динамически изменять внешний вид моделей
  • адаптировать импортированные ресурсы под освещение сцены
  • оптимизировать визуальное качество
  • реализовывать интерактивные эффекты

Контроль над материалами является одной из ключевых задач при работе с загруженными 3D-моделями в Three.js, поскольку именно материалы определяют финальный визуальный результат рендеринга.