Merging geometries: BufferGeometryUtils.mergeGeometries

В библиотеке Three.js работа с большим количеством объектов может быть ресурсоёмкой, особенно если каждый объект представляет собой отдельную геометрию и материал. Для повышения производительности используется техника слияния геометрий, позволяющая объединить несколько объектов в одну геометрию, что снижает количество вызовов отрисовки (draw calls) и оптимизирует использование памяти GPU. Основным инструментом для этого является метод BufferGeometryUtils.mergeGeometries.


Основы BufferGeometryUtils.mergeGeometries

BufferGeometryUtils.mergeGeometries — это функция, которая принимает массив объектов типа THREE.BufferGeometry и возвращает одну объединённую геометрию. Она доступна через утилитарный модуль BufferGeometryUtils, который необходимо импортировать отдельно:

import { BufferGeometryUtils } from 'three/examples/jsm/utils/BufferGeometryUtils.js';

Синтаксис функции:

const mergedGeometry = BufferGeometryUtils.mergeGeometries(geometries, useGroups);
  • geometries — массив объектов THREE.BufferGeometry.
  • useGroups — булевый параметр, определяющий, нужно ли сохранять группы для разных материалов. По умолчанию false.

Принцип работы и особенности

  1. Слияние атрибутов Каждая BufferGeometry состоит из атрибутов (position, normal, uv, color и других). Функция mergeGeometries объединяет соответствующие атрибуты всех геометрий в один массив. Это позволяет GPU обрабатывать данные как единое целое.

  2. Сохранение индексов Если геометрии используют индексированные массивы (geometry.index), метод корректно смещает индексы каждой геометрии, чтобы они не конфликтовали между собой.

  3. Группы для материалов При установке useGroups = true каждая исходная геометрия получает собственную группу в объединённой геометрии. Это необходимо, если разные геометрии будут использовать разные материалы при рендеринге одной меш-сущности.


Примеры использования

Объединение простых кубов с одинаковым материалом:

import * as THREE from 'three';
import { BufferGeometryUtils } from 'three/examples/jsm/utils/BufferGeometryUtils.js';

const geometries = [];

for (let i = 0; i < 10; i++) {
    const cube = new THREE.BoxGeometry(1, 1, 1);
    cube.translate(i * 2, 0, 0); // перемещаем каждый куб
    geometries.push(cube);
}

const mergedGeometry = BufferGeometryUtils.mergeGeometries(geometries);
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
const mesh = new THREE.Mesh(mergedGeometry, material);

scene.add(mesh);

В этом примере создаются 10 кубов, которые объединяются в одну геометрию. Перемещение каждого куба происходит через метод translate для BufferGeometry, что обеспечивает правильное позиционирование в объединённой геометрии.


Слияние геометрий с разными материалами

Если необходимо объединять геометрии с разными материалами, важно использовать useGroups = true:

const geometries = [geom1, geom2, geom3];
const mergedGeometry = BufferGeometryUtils.mergeGeometries(geometries, true);

const materials = [
    new THREE.MeshStandardMaterial({ color: 0xff0000 }),
    new THREE.MeshStandardMaterial({ color: 0x00ff00 }),
    new THREE.MeshStandardMaterial({ color: 0x0000ff })
];

const mesh = new THREE.Mesh(mergedGeometry, materials);
scene.add(mesh);

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


Ограничения и рекомендации

  • Совместимость атрибутов: Все геометрии должны иметь одинаковый набор атрибутов (position, normal, uv). Если некоторые атрибуты отсутствуют в одной из геометрий, метод выбросит ошибку.
  • Память и производительность: Объединение сотен или тысяч геометрий сильно экономит GPU-ресурсы, но при этом создаёт один большой массив данных в памяти, что может увеличить нагрузку на CPU при подготовке.
  • Динамическое обновление: После объединения геометрий изменение отдельной части меша невозможно без пересоздания геометрии. Для интерактивных сцен рекомендуется объединять только статические объекты.

Практические советы

  • Использовать mergeGeometries для объектов, которые не изменяются динамически.
  • Для больших сцен с повторяющимися объектами использовать шаблон instanced meshes (THREE.InstancedMesh) вместо слияния геометрий, если требуется анимация или частые трансформации.
  • Всегда проверять наличие всех необходимых атрибутов перед слиянием. Добавление атрибута normal или uv можно выполнить вручную через geometry.setAttribute.

Расширенные возможности

  • Слияние сложных моделей: Можно объединять геометрии, импортированные из форматов .glb, .obj или .fbx. Перед объединением рекомендуется вызывать geometry.computeVertexNormals() для корректного расчёта освещения.
  • Смешение разных типов геометрий: Метод поддерживает любые геометрии BufferGeometry — сферы, цилиндры, плоскости и кастомные формы. Главное — соответствие атрибутов.

С помощью BufferGeometryUtils.mergeGeometries достигается значительное упрощение сцен с большим числом объектов, снижение количества draw calls и улучшение производительности рендеринга WebGL в проектах на Three.js.