Динамическое обновление атрибутов геометрии

Three.js предоставляет мощный механизм работы с BufferGeometry, позволяющий напрямую управлять вершинами, нормалями, цветами и другими атрибутами. В отличие от старого Geometry, BufferGeometry хранит данные в Typed Arrays, что обеспечивает высокую производительность при рендеринге и обновлении сцены. Динамическое обновление этих атрибутов необходимо для анимации вершин, изменения цвета поверхности в реальном времени, создания интерактивных эффектов и физически основанных симуляций.

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

  • Атрибуты хранятся как THREE.BufferAttribute или THREE.Float32BufferAttribute.
  • Для изменения данных используется свойство array атрибута.
  • После изменения данных требуется пометить атрибут как нуждающийся в обновлении, установив attribute.needsUpdate = true.

Работа с позициями вершин

Атрибут position отвечает за координаты вершин объекта. Пример обновления позиции одной вершины:

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

vertices[0] = 0.5;  // Изменение x первой вершины
geometry.attributes.position.needsUpdate = true;

Важно, что индексация массива строго линейная: каждая вершина занимает 3 значения (x, y, z). При работе с большим числом вершин рекомендуется избегать создания новых массивов и изменять существующие данные для оптимизации производительности.

Динамическое обновление нормалей

Нормали определяют направление поверхности для освещения. При изменении вершин необходимо пересчитывать нормали:

geometry.computeVertexNormals();
geometry.attributes.normal.needsUpdate = true;

Если геометрия изменяется постепенно (например, в анимации волн), вызов computeVertexNormals() каждый кадр может быть дорогостоящим. В таких случаях можно хранить заранее рассчитанные нормали или использовать шейдеры для динамических вычислений.

Работа с цветами

Three.js поддерживает вершинные цвета, которые можно динамически менять:

const colors = new Float32Array([
    1, 0, 0,
    0, 1, 0,
    0, 0, 1
]);
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));

colors[0] = 0.5;  // Полупрозрачный красный
geometry.attributes.color.needsUpdate = true;

Для использования цветов в материале необходимо включить флаг vertexColors:

const material = new THREE.MeshStandardMaterial({ vertexColors: true });

Анимация вершин с помощью BufferAttribute

Для анимации часто используют синусоиды или случайные функции:

const time = performance.now() * 0.001;
for (let i = 0; i < vertices.length; i += 3) {
    vertices[i + 2] = Math.sin(time + i * 0.1) * 0.5;  // Волны по оси Z
}
geometry.attributes.position.needsUpdate = true;
geometry.computeVertexNormals();

Использование Float32Array и прямое изменение значений позволяет анимировать тысячи вершин с высокой частотой кадров.

Индексированные геометрии

Если используется geometry.setIndex(), необходимо учитывать, что позиции изменяются в position, а индекс просто указывает, какие вершины использовать для построения треугольников. Для динамических анимаций изменяется только position, индекс можно оставить неизменным.

Подсказки по производительности

  1. Не пересоздавать атрибуты каждый кадр. Создать один раз и обновлять массив.
  2. Использовать dynamic: true при создании атрибута:
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3).setUsage(THREE.DynamicDrawUsage));
  1. Минимизировать вызовы computeVertexNormals(). Пересчитывать нормали только при значительных изменениях.
  2. Использовать шейдеры для больших объемов анимаций. Вершинные шейдеры позволяют избежать постоянного обновления CPU-массивов.

Интеграция с анимационным циклом

Three.js использует цикл рендера через requestAnimationFrame. Для динамических геометрий структура кода обычно такая:

function animate() {
    requestAnimationFrame(animate);
    
    // Обновление вершин
    updateVertices();

    renderer.render(scene, camera);
}
animate();

В функции updateVertices() происходит изменение массива атрибута, установка needsUpdate = true и, при необходимости, пересчет нормалей.

Расширенные техники

  • Morph Targets: позволяют хранить несколько состояний вершин и плавно интерполировать между ними. Подходит для анимации лиц, деформации моделей.
  • InstancedMesh: при большом количестве объектов с одинаковой геометрией можно динамически изменять только матрицы трансформации, оставляя атрибуты геометрии неизменными.
  • Shader Attributes: для сложных процедурных эффектов обновление геометрии происходит напрямую на GPU через пользовательские атрибуты.

Динамическое обновление атрибутов в Three.js является основой для процедурной анимации, интерактивных эффектов и оптимизированного рендеринга больших сцен. Гибкая работа с BufferGeometry и понимание принципов BufferAttribute позволяет создавать сложные визуальные эффекты без потери производительности.