Прямой доступ к BufferGeometry Three.js

A-Frame, как фреймворк для создания VR-приложений на базе WebGL, строится поверх библиотеки Three.js. Это позволяет использовать низкоуровневые структуры данных Three.js, включая BufferGeometry, для создания кастомных 3D-объектов с высокой производительностью. BufferGeometry представляет собой массивы атрибутов вершин, нормалей, цветов и UV-координат, хранящиеся в виде Float32Array или других TypedArray, что позволяет GPU обрабатывать данные напрямую без промежуточных объектов Geometry.

В A-Frame объекты с geometry обычно создаются через <a-box>, <a-sphere> и другие примитивы. Прямой доступ к BufferGeometry предоставляет возможность строить уникальные формы и оптимизировать производительность за счёт точного контроля над вершинными буферами.


Получение и модификация BufferGeometry

Каждый элемент сцены A-Frame имеет свойство object3D, которое является экземпляром THREE.Object3D. Для работы с геометрией необходимо обратиться к дочернему объекту mesh:

const box = document.querySelector('a-box');
const mesh = box.getObject3D('mesh'); // THREE.Mesh
const geometry = mesh.geometry;       // THREE.BufferGeometry

После получения объекта BufferGeometry можно читать и изменять данные напрямую:

// Доступ к атрибуту вершин
const positionAttr = geometry.attributes.position;

// Изменение позиции первой вершины
positionAttr.setXYZ(0, 1.0, 2.0, 3.0);

// Уведомление Three.js о необходимости обновления
positionAttr.needsUpdate = true;

Важно всегда выставлять needsUpdate = true, иначе изменения не будут видны на рендере.


Создание кастомной BufferGeometry с нуля

Для нестандартных объектов часто проще создать BufferGeometry напрямую и присвоить её mesh.geometry:

const geometry = new THREE.BufferGeometry();

// Массив вершин: 3 координаты на каждую вершину
const vertices = new Float32Array([
  0, 0, 0,   // вершина 1
  1, 0, 0,   // вершина 2
  0, 1, 0    // вершина 3
]);

geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));

// Создание меша с материалом
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
const mesh = new THREE.Mesh(geometry, material);

// Добавление в сцену A-Frame
const sceneEl = document.querySelector('a-scene');
sceneEl.object3D.add(mesh);

Такой подход позволяет создавать треугольники, полигоны и сложные модели без использования сторонних форматов.


Анимация вершин

Прямой доступ к BufferGeometry даёт возможность реализовать анимацию на уровне вершин. Например, волнообразное движение плоскости:

const geometry = mesh.geometry;
const position = geometry.attributes.position;
const count = position.count;

function animateWave(time) {
  for (let i = 0; i < count; i++) {
    const x = position.getX(i);
    const y = position.getY(i);
    position.setZ(i, Math.sin(x + time * 0.001) * 0.5);
  }
  position.needsUpdate = true;
  requestAnimationFrame(animateWave);
}

animateWave(0);

Такой подход экономит ресурсы, так как обновляются только массивы вершин, а не весь объект сцены.


Использование индексов и оптимизация

BufferGeometry поддерживает индексированные вершины через geometry.setIndex(). Индексы позволяют повторно использовать вершины, уменьшая размер массивов и нагрузку на GPU:

const geometry = new THREE.BufferGeometry();
const vertices = new Float32Array([
  0, 0, 0,
  1, 0, 0,
  0, 1, 0,
  1, 1, 0
]);
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));

// Индексы треугольников
const indices = new Uint16Array([
  0, 1, 2,
  2, 1, 3
]);
geometry.setIndex(new THREE.BufferAttribute(indices, 1));

Индексы особенно полезны для моделей с большим количеством повторяющихся вершин, например, сеток или ландшафтов.


Нормали и освещение

Для корректного освещения важно рассчитывать нормали после изменения вершин:

geometry.computeVertexNormals();

Если в процессе анимации вершины изменяются, нормали следует пересчитывать каждый кадр или на ключевых этапах:

positionAttr.setXYZ(0, 1.0, 2.0, 3.0);
geometry.computeVertexNormals();
positionAttr.needsUpdate = true;

Интеграция с A-Frame компонентами

Прямой доступ к BufferGeometry позволяет создавать пользовательские компоненты, которые динамически модифицируют форму объектов:

AFRAME.registerComponent('wave-plane', {
  schema: { speed: { type: 'number', default: 1.0 } },
  init: function () {
    this.mesh = this.el.getObject3D('mesh');
    this.positions = this.mesh.geometry.attributes.position;
  },
  tick: function (time) {
    const positions = this.positions;
    for (let i = 0; i < positions.count; i++) {
      const x = positions.getX(i);
      positions.setZ(i, Math.sin(x + time * 0.001 * this.data.speed));
    }
    positions.needsUpdate = true;
    this.mesh.geometry.computeVertexNormals();
  }
});

Компонент можно использовать на <a-plane wave-plane="speed: 2">, и он будет изменять форму плоскости в реальном времени.


Работа с цветами и UV-координатами

Атрибуты color и uv также доступны через BufferGeometry.attributes. Примеры:

// Добавление цветов вершин
const colors = new Float32Array([
  1, 0, 0,   // красная вершина
  0, 1, 0,   // зелёная
  0, 0, 1    // синяя
]);
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));

// UV для текстурирования
const uvs = new Float32Array([
  0, 0,
  1, 0,
  0, 1
]);
geometry.setAttribute('uv', new THREE.BufferAttribute(uvs, 2));

Материал должен учитывать вершинные цвета (vertexColors: true) для корректного отображения.


Практические рекомендации

  • Использовать BufferGeometry для объектов с большим количеством вершин или динамическими изменениями.
  • Всегда выставлять needsUpdate = true после изменения атрибутов.
  • Использовать индексы для повторяющихся вершин.
  • Пересчитывать нормали после значительных изменений.
  • Для анимации вершин избегать создания новых массивов каждый кадр — изменять существующие.

Прямой доступ к BufferGeometry позволяет комбинировать удобство A-Frame с гибкостью Three.js, обеспечивая максимальную производительность и творческий контроль над 3D-моделями.