Виртуальные сцены, создаваемые с помощью A-Frame, работают поверх графического движка Three.js и используют возможности WebGL. Основное ограничение производительности в браузере — количество draw call (вызовов отрисовки), которые отправляются в GPU за один кадр. Чем больше отдельных объектов в сцене, тем выше нагрузка на CPU и драйвер видеокарты.
Даже при относительно простых моделях сцена может работать медленно, если содержит сотни или тысячи однотипных объектов — деревья, камни, частицы, архитектурные элементы. В таких случаях ключевым инструментом оптимизации становится батчинг геометрии (batching).
Батчинг — это объединение нескольких объектов в один графический вызов. В контексте A-Frame и Three.js используются два основных подхода:
Каждый метод имеет свои особенности, ограничения и области применения.
A-Frame предоставляет декларативный HTML-подобный синтаксис:
<a-entity geometry="primitive: box" material="color: red"></a-entity>
Каждый a-entity создаёт отдельный объект Three.js
(THREE.Mesh). Даже если 100 объектов имеют одинаковую
геометрию и материал, по умолчанию они будут отрисовываться отдельными
draw call.
Это создаёт узкое место:
Оптимизация требует перехода от множества независимых мешей к агрегированным структурам.
Метод merge объединяет несколько BufferGeometry в одну
общую геометрию. После объединения:
В основе лежит функция
BufferGeometryUtils.mergeBufferGeometries() из
Three.js.
THREE.MeshBufferGeometryMeshAFRAME.registerComponent('merge-meshes', {
init: function () {
const meshes = [];
this.el.object3D.traverse((node) => {
if (node.isMesh) {
node.updateMatrix();
meshes.push(node);
}
});
const geometries = meshes.map(mesh => {
const geometry = mesh.geometry.clone();
geometry.applyMatrix4(mesh.matrix);
return geometry;
});
const mergedGeometry = THREE.BufferGeometryUtils.mergeBufferGeometries(geometries);
const material = meshes[0].material;
const mergedMesh = new THREE.Mesh(mergedGeometry, material);
this.el.setObject3D('mesh', mergedMesh);
}
});
1. Одинаковый материал
Все объединяемые геометрии должны использовать один материал. Иначе придётся создавать группы индексов, что усложняет рендеринг.
2. Статичность
После объединения объекты нельзя:
Это метод для полностью статических объектов.
3. Отсутствие индивидуального фрустум-куллинга
GPU больше не может отсекать невидимые части отдельно. Если хотя бы часть объединённой геометрии видна, отрисовывается всё.
Instancing использует возможности WebGL 2.0 для отрисовки одной и той же геометрии с разными трансформациями в рамках одного draw call.
В Three.js применяется класс THREE.InstancedMesh.
В отличие от merge:
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
const count = 1000;
const instancedMesh = new THREE.InstancedMesh(geometry, material, count);
const dummy = new THREE.Object3D();
for (let i = 0; i < count; i++) {
dummy.position.set(
Math.random() * 50,
0,
Math.random() * 50
);
dummy.updateMatrix();
instancedMesh.setMatrixAt(i, dummy.matrix);
}
scene.add(instancedMesh);
A-Frame не предоставляет встроенный HTML-синтаксис для instancing, поэтому используется кастомный компонент.
AFRAME.registerComponent('instanced-boxes', {
schema: { count: { type: 'int', default: 1000 } },
init: function () {
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshStandardMaterial({ color: 0x2196F3 });
const mesh = new THREE.InstancedMesh(
geometry,
material,
this.data.count
);
const dummy = new THREE.Object3D();
for (let i = 0; i < this.data.count; i++) {
dummy.position.set(
Math.random() * 50,
0,
Math.random() * 50
);
dummy.updateMatrix();
mesh.setMatrixAt(i, dummy.matrix);
}
this.el.setObject3D('mesh', mesh);
}
});
1. Один draw call для тысяч объектов
Кардинально снижает нагрузку на CPU.
2. Индивидуальные трансформации
Каждый экземпляр может иметь:
3. Динамическое обновление
Можно изменять отдельные матрицы:
mesh.setMatrixAt(index, newMatrix);
mesh.instanceMatrix.needsUpdate = true;
1. Одинаковая геометрия и материал
Все экземпляры используют одну и ту же геометрию.
2. Ограниченные индивидуальные материалы
Разные цвета требуют:
instanceColor3. Нет индивидуальных children
InstancedMesh — это плоская структура, не поддерживающая вложенные объекты.
| Характеристика | merge | instancing |
|---|---|---|
| Количество draw call | 1 | 1 |
| Возможность анимации отдельных объектов | Нет | Да |
| Поддержка разных геометрий | Да | Нет |
| Поддержка разных материалов | Ограничено | Нет |
| Подходит для динамики | Нет | Да |
| Фрустум-куллинг | Общий | Общий |
На практике применяются оба метода:
Для индивидуального цвета используется:
const color = new THREE.Color();
mesh.setColorAt(i, color.setHex(Math.random() * 0xffffff));
mesh.instanceColor.needsUpdate = true;
Материал должен поддерживать vertex colors:
new THREE.MeshStandardMaterial({
vertexColors: true
});
InstancedMesh хранит:
instanceMatrix)instanceColor)При 10 000 экземпляров используется:
Это существенно эффективнее, чем 10 000 отдельных Mesh-объектов.
InstancedMesh имеет один bounding box. Если хотя бы один экземпляр попадает в область видимости, рендерится весь батч.
Для сцен с широким распределением объектов:
Instancing подходит для:
Merge подходит для:
В режиме VR нагрузка удваивается, поскольку сцена рендерится дважды (для каждого глаза). Оптимизация batching становится критически важной для поддержания 72–90 FPS на устройствах вроде Meta Quest 2.
1. Более 100 одинаковых объектов — рассматривать instancing.
2. Статические модели после загрузки — объединять.
3. Разделять сцену на логические чанки.
4. Проверять draw call через инструменты разработчика браузера.
Позволяет:
В A-Frame используется компонент shader с кастомным
GLSL.
В производственных проектах геометрии объединяются:
Это снижает нагрузку до загрузки в браузер.
Оптимизированная сцена на A-Frame обычно включает:
Грамотное применение merge и instancing позволяет масштабировать сцену до десятков тысяч объектов без критического падения производительности, сохраняя стабильный FPS даже на мобильных VR-устройствах.