В процессе загрузки 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 рекурсивно обходит все дочерние элементы,
позволяя получить доступ к материалам каждой части модели.
Часто требуется определить:
Это особенно важно при работе с крупными моделями, где количество материалов может достигать десятков.
Загруженные модели могут использовать различные типы материалов:
MeshStandardMaterialMeshPhysicalMaterialMeshPhongMaterialMeshBasicMaterialФормат 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, поскольку именно материалы определяют финальный визуальный результат рендеринга.