Merge геометрий для оптимизации

В A-Frame, как и в других движках WebGL, производительность напрямую зависит от количества рендеринговых вызовов (draw calls). Каждый <a-entity> с собственной геометрией создаёт отдельный draw call, что может значительно замедлять работу сцены при большом количестве объектов. Объединение геометрий (merge geometries) позволяет снизить количество draw calls, объединяя несколько мешей в один, при этом сохраняя их визуальное представление и трансформации на базовом уровне.

В A-Frame объединение геометрий реализуется через прямую работу с Three.js объектами, так как A-Frame является надстройкой над Three.js. Ключевые классы для этого процесса: THREE.BufferGeometry и THREE.Mesh. Объединение требует внимательного подхода к позициям, нормалям, UV-картам и материалам.


Работа с THREE.BufferGeometry

BufferGeometry — это основной класс для хранения геометрии в Three.js. Для объединения нескольких объектов используются методы mergeBufferGeometries и ручное объединение атрибутов:

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

const geometries = [];

entity1.object3D.traverse((child) => {
  if (child.isMesh) geometries.push(child.geometry);
});

entity2.object3D.traverse((child) => {
  if (child.isMesh) geometries.push(child.geometry);
});

const mergedGeometry = mergeBufferGeometries(geometries);
const mergedMesh = new THREE.Mesh(mergedGeometry, sharedMaterial);
scene.object3D.add(mergedMesh);

Ключевые моменты:

  • Собрать все меши: обход object3D.traverse позволяет извлечь вложенные меши в составе <a-entity>.
  • Объединение геометрий: mergeBufferGeometries корректно соединяет массив BufferGeometry, создавая одну общую геометрию.
  • Общий материал: объединённая геометрия должна использовать один материал. Разные материалы требуют создания отдельных merged mesh для каждого материала.

Подготовка к объединению

Перед слиянием важно нормализовать трансформации:

  • Позиция (position) меша должна быть преобразована в мировые координаты.
  • Вращение (rotation) и масштаб (scale) необходимо применить к геометрии с помощью applyMatrix4():
child.updateMatrixWorld();
child.geometry.applyMatrix4(child.matrixWorld);

После применения этих преобразований меши можно безопасно объединять без потери позиции или ориентации в сцене.


Оптимизация через instancing

Если сцена содержит множество одинаковых объектов (например, деревья, здания или детали интерьера), вместо объединения геометрий можно использовать InstancedMesh:

const count = 100;
const instancedMesh = new THREE.InstancedMesh(geometry, material, count);

for (let i = 0; i < count; i++) {
  const matrix = new THREE.Matrix4();
  matrix.setPosition(Math.random() * 10, 0, Math.random() * 10);
  instancedMesh.setMatrixAt(i, matrix);
}

scene.object3D.add(instancedMesh);

Преимущества instancing:

  • Один draw call для всех экземпляров.
  • Экономия памяти, так как геометрия хранится единожды.
  • Возможность динамически менять позиции и трансформации каждого экземпляра.

Ограничения объединения геометрий

  1. Разные материалы: если меши используют разные текстуры, их объединение требует компромисса (например, создание атласов текстур) или разделение по материалам.
  2. Сложная анимация: объекты с анимациями скелета (skinned mesh) нельзя объединять обычным mergeBufferGeometries.
  3. Динамические изменения: после объединения геометрии редактировать отдельные меши напрямую невозможно; нужно либо хранить исходные геометрии, либо использовать instancing для динамических объектов.

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

  • Объединять стоит статические объекты, которые не будут двигаться или анимироваться.
  • Использовать mergeBufferGeometries вместо ручного объединения атрибутов для сокращения кода и ошибок.
  • Перед объединением проверять количество draw calls в браузерных инструментах разработчика (Chrome DevTools → Rendering → Draw Calls), чтобы оценить эффективность оптимизации.
  • Рассматривать Atlas текстур для сохранения материалов при объединении объектов с разными текстурами.

Интеграция в A-Frame

Пример объединения нескольких <a-box> в одну геометрию:

<a-scene>
  <a-box id="box1" position="0 0 -2"></a-box>
  <a-box id="box2" position="2 0 -2"></a-box>
</a-scene>

<script type="module">
  import * as THREE from 'three';
  import { mergeBufferGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js';

  const box1 = document.querySelector('#box1');
  const box2 = document.querySelector('#box2');

  const geometries = [];
  [box1, box2].forEach(entity => {
    entity.object3D.traverse(child => {
      if (child.isMesh) {
        child.updateMatrixWorld();
        child.geometry.applyMatrix4(child.matrixWorld);
        geometries.push(child.geometry);
      }
    });
  });

  const mergedGeometry = mergeBufferGeometries(geometries);
  const mergedMesh = new THREE.Mesh(mergedGeometry, new THREE.MeshStandardMaterial({ color: 0x00ff00 }));
  box1.sceneEl.object3D.add(mergedMesh);

  box1.parentNode.removeChild(box1);
  box2.parentNode.removeChild(box2);
</script>

Этот пример демонстрирует полный процесс: сбор мешей, применение трансформаций, объединение геометрий и добавление объединённого объекта в сцену.


Объединение геометрий является одним из самых эффективных способов оптимизации A-Frame сцен с большим количеством статических объектов. Грамотное использование mergeBufferGeometries и InstancedMesh позволяет добиться значительного снижения нагрузки на рендеринг без потери визуального качества.