InstancedMesh: рендеринг тысяч идентичных объектов

Three.js предоставляет мощный инструмент для оптимизации рендеринга большого числа идентичных объектов — InstancedMesh. В отличие от обычных мешей, каждый экземпляр InstancedMesh использует одну и ту же геометрию и материал, но может иметь свои уникальные трансформации и свойства. Это позволяет существенно экономить память и ресурсы GPU при визуализации сцен с тысячами повторяющихся объектов.


Создание InstancedMesh

InstancedMesh создается с использованием следующего конструктора:

const mesh = new THREE.InstancedMesh(geometry, material, count);
  • geometry — геометрия, общая для всех экземпляров.
  • material — материал, используемый всеми объектами.
  • count — количество экземпляров.

Например:

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);
scene.add(instancedMesh);

Здесь создается 1000 одинаковых кубов с зеленым материалом.


Трансформации экземпляров

Каждый экземпляр InstancedMesh можно позиционировать, вращать и масштабировать независимо. Для этого используется объект THREE.Matrix4 и метод setMatrixAt(index, matrix):

const dummy = new THREE.Object3D();

for (let i = 0; i < count; i++) {
    dummy.position.set(Math.random() * 50 - 25, Math.random() * 50, Math.random() * 50 - 25);
    dummy.rotation.set(Math.random() * Math.PI, Math.random() * Math.PI, 0);
    dummy.scale.setScalar(Math.random() + 0.5);

    dummy.updateMatrix();
    instancedMesh.setMatrixAt(i, dummy.matrix);
}

Объяснение:

  • dummy — вспомогательный объект, который позволяет удобно вычислять матрицы трансформации.
  • updateMatrix() — пересчитывает внутреннюю матрицу объекта на основе позиции, вращения и масштаба.
  • setMatrixAt(i, matrix) — задает матрицу для i-го экземпляра.

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

instancedMesh.instanceMatrix.needsUpdate = true;

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

InstancedMesh позволяет задавать индивидуальные цвета для каждого экземпляра, если включить instanceColor. Для этого материал должен поддерживать vertex colors:

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

for (let i = 0; i < count; i++) {
    colors.push(Math.random(), Math.random(), Math.random());
}

const colorAttribute = new THREE.InstancedBufferAttribute(new Float32Array(colors), 3);
instancedMesh.instanceColor = colorAttribute;

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


Анимация экземпляров

InstancedMesh отлично подходит для анимации большого количества объектов. Можно изменять матрицы на каждом кадре:

function animate() {
    requestAnimationFrame(animate);

    for (let i = 0; i < count; i++) {
        dummy.rotation.y += 0.01;
        dummy.updateMatrix();
        instancedMesh.setMatrixAt(i, dummy.matrix);
    }

    instancedMesh.instanceMatrix.needsUpdate = true;
    renderer.render(scene, camera);
}

animate();

Эффективность здесь достигается за счет того, что GPU обрабатывает все экземпляры одной командой отрисовки, а не каждой меш отдельно.


Советы по оптимизации

  1. Минимизировать количество материалов: лучше использовать один материал для всех экземпляров.
  2. Использовать простые геометрии: сложные геометрии уменьшают прирост производительности.
  3. Обновлять матрицы только при необходимости: постоянное обновление без изменений приведет к потере производительности.
  4. Использовать instanceColor для разнообразия: можно создавать визуальное разнообразие без создания новых мешей.
  5. Batching с InstancedMesh: объединение нескольких объектов в один InstancedMesh уменьшает количество draw calls.

Примеры практического применения

  • Лес с тысячами деревьев, где все деревья имеют одинаковую модель, но различаются положением и масштабом.
  • Частицы или звезды, где каждый объект имеет одинаковую форму, но индивидуальный цвет и анимацию.
  • Массовые объекты в играх: враги, пули, кристаллы, блоки.

Использование InstancedMesh значительно увеличивает производительность при рендеринге сцены с повторяющимися объектами и позволяет реализовывать сложные эффекты без нагрузки на CPU.