Любая загруженная 3D-модель в Three.js представлена
не единым объектом, а иерархическим деревом. Вершиной этого дерева
является экземпляр THREE.Object3D, внутри которого могут
находиться десятки или сотни дочерних элементов: группы
(THREE.Group), меши (THREE.Mesh), кости
(THREE.Bone), источники света и вспомогательные
объекты.
Загрузчики форматов (GLTFLoader, FBXLoader,
OBJLoader и другие) создают сложные графы объектов, где
каждый элемент связан родительскими и дочерними связями. Такая структура
называется сценографическим деревом (scene graph).
Пример типичной структуры модели:
Object3D (root)
├── Group
│ ├── Mesh
│ ├── Mesh
│ └── Mesh
├── Group
│ ├── Mesh
│ └── Mesh
└── Light
Работа с такими структурами требует обхода дерева — последовательного прохода по всем объектам внутри модели.
Основные задачи обхода:
Для этих задач Three.js предоставляет встроенные методы обхода.
traverse()Класс THREE.Object3D содержит метод:
object.traverse(callback)
Метод выполняет рекурсивный обход всех дочерних элементов объекта и передает каждый найденный элемент в функцию-колбэк.
Пример:
model.traverse(function(child) {
console.log(child);
});
Если model — это загруженная модель (например
gltf.scene), то будут перечислены:
Обход выполняется глубинным способом (Depth-First Traversal): сначала родитель, затем все его потомки.
После загрузки GLTF-модели:
loader.load("model.glb", function(gltf) {
const model = gltf.scene;
model.traverse(function(object) {
console.log(object.type);
});
scene.add(model);
});
В консоли можно увидеть последовательность:
Scene
Group
Mesh
Mesh
Mesh
Bone
SkinnedMesh
Это позволяет определить структуру объекта и понять, какие элементы доступны для дальнейших изменений.
Чаще всего требуется работать только с определёнными типами
элементов. Наиболее распространённый случай — поиск мешей
(THREE.Mesh).
model.traverse(function(child) {
if (child.isMesh) {
console.log("Mesh найден:", child);
}
});
Свойство isMesh является быстрым способом проверки
типа.
Другие полезные проверки:
child.isGroup
child.isLight
child.isBone
child.isCamera
child.isSkinnedMesh
Такая фильтрация значительно упрощает обработку сложных моделей.
Один из самых распространённых сценариев — массовая замена материала.
model.traverse(function(child) {
if (child.isMesh) {
child.material = new THREE.MeshStandardMaterial({
color: 0x00ffcc
});
}
});
Обход дерева позволяет изменить внешний вид всей модели независимо от того, насколько глубоко внутри структуры находится меш.
При загрузке моделей тени обычно не активированы автоматически. Обход дерева позволяет быстро включить их для всех элементов.
model.traverse(function(child) {
if (child.isMesh) {
child.castShadow = true;
child.receiveShadow = true;
}
});
После этого модель начинает корректно взаимодействовать с освещением сцены.
Во многих 3D-редакторах (Blender, Maya) элементам можно задавать имена. Эти имена сохраняются в модели и доступны в Three.js.
Обход используется для поиска конкретного элемента.
let target;
model.traverse(function(child) {
if (child.name === "Door") {
target = child;
}
});
После этого можно выполнять действия:
target.rotation.y = Math.PI / 2;
Альтернативный способ — встроенный метод:
model.getObjectByName("Door");
Однако traverse() даёт больше гибкости при сложных
условиях поиска.
Иногда требуется изменить свойства материалов у всех объектов.
Пример изменения параметров:
model.traverse(function(child) {
if (child.isMesh) {
child.material.metalness = 0.8;
child.material.roughness = 0.2;
}
});
Другой пример — отключение фрустумного отсечения:
child.frustumCulled = false;
Такой подход полезен для динамических сцен или анимаций.
SkinnedMeshАнимированные модели содержат объекты типа
SkinnedMesh.
Поиск таких элементов:
model.traverse(function(child) {
if (child.isSkinnedMesh) {
console.log("Найден скелетный меш");
}
});
После обнаружения можно получить доступ к:
child.skeletonchild.bindMatrixchild.geometryЭто используется при работе с анимацией персонажей.
traverseVisible()Помимо обычного обхода существует метод:
object.traverseVisible(callback)
Он работает аналогично traverse(), но игнорирует
невидимые объекты.
Пример:
model.traverseVisible(function(child) {
console.log(child.name);
});
Если у объекта установлено:
object.visible = false
он не будет участвовать в обходе.
Это полезно для оптимизации при больших сценах.
traverseAncestors()Метод используется для движения вверх по иерархии, к родительским элементам.
object.traverseAncestors(callback)
Пример:
mesh.traverseAncestors(function(parent) {
console.log(parent.name);
});
Последовательно будут выведены:
Group
Scene
Метод удобен для:
Метод traverse() реализован рекурсивно. Внутренний
принцип работы можно представить так:
function traverse(object, callback) {
callback(object);
for (let child of object.children) {
traverse(child, callback);
}
}
Каждый объект содержит массив:
object.children
в котором находятся все дочерние элементы.
Такая архитектура делает обход дерева естественным и эффективным.
Модели из CAD-систем или сложные сцены могут содержать тысячи объектов. Частый обход дерева может создавать нагрузку.
Способы оптимизации:
1. Ограничение типа объектов
if (!child.isMesh) return;
2. Кэширование результатов
Вместо повторного обхода можно сохранить найденные объекты:
const meshes = [];
model.traverse(function(child) {
if (child.isMesh) meshes.push(child);
});
Дальнейшая работа ведётся напрямую с массивом
meshes.
3. Однократная обработка
Обход дерева выполняется один раз после загрузки модели.
Типичная последовательность действий после загрузки GLTF:
loader.load("car.glb", function(gltf) {
const model = gltf.scene;
model.traverse(function(child) {
if (child.isMesh) {
child.castShadow = true;
child.receiveShadow = true;
child.material.envMapIntensity = 1.2;
}
});
scene.add(model);
});
В этом примере:
Обход дерева часто используется для диагностики структуры сцены.
model.traverse(function(child) {
console.log(
child.type,
child.name,
child.children.length
);
});
Это позволяет понять:
При взаимодействии с пользователем (например, кликах мыши) необходимо определить, какой объект был выбран.
После получения объекта из Raycaster можно выполнить
обход его родителей:
intersected.object.traverseAncestors(function(parent) {
if (parent.name === "Car") {
console.log("Выбран автомобиль");
}
});
Таким образом определяется принадлежность объекта к более крупной структуре.
Модели часто содержат логические группы:
Car
├── Body
├── Wheels
└── Glass
Обход дерева позволяет выполнять операции над целыми категориями объектов.
model.traverse(function(child) {
if (child.name.includes("Wheel")) {
child.rotation.x += 0.1;
}
});
Это удобно для синхронных анимаций.
В процессе разработки полезно визуализировать дерево объектов.
Простейший способ:
function printTree(object, level = 0) {
console.log(
"-".repeat(level),
object.name || object.type
);
object.children.forEach(child => {
printTree(child, level + 1);
});
}
Вызов:
printTree(model);
выводит иерархию всей модели.
Traversal является фундаментальной частью работы со сценографическим деревом. Через обход дерева выполняются:
Методы traverse, traverseVisible и
traverseAncestors предоставляют полный набор инструментов
для перемещения по иерархии объектов любой сложности. В сочетании с
типами Object3D, Mesh, Group и
SkinnedMesh они формируют основу управления структурой
загруженных 3D-моделей в Three.js.