Raycasting и instancedMesh

Raycasting — это метод определения пересечения луча с объектами сцены, который широко используется для взаимодействия пользователя с 3D-сценой: выбор объектов мышью, столкновения, навигация и многое другое. В Three.js класс THREE.Raycaster обеспечивает удобный API для реализации этих задач.

Создание Raycaster

Для начала создаётся объект луча:

const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();

mouse хранит координаты курсора в нормализованных координатах WebGL:

  • x: от -1 (левый край) до 1 (правый край)
  • y: от -1 (нижний край) до 1 (верхний край)

Определение позиции луча

Луч устанавливается через метод setFromCamera:

raycaster.setFromCamera(mouse, camera);

camera — объект камеры (THREE.PerspectiveCamera или THREE.OrthographicCamera). После этого raycaster готов искать пересечения с объектами сцены.

Получение пересечений

Метод intersectObjects возвращает массив объектов, с которыми луч пересёкся:

const intersects = raycaster.intersectObjects(scene.children, true);
  • Первый параметр — массив объектов для проверки.
  • Второй параметр (true) позволяет искать пересечения рекурсивно, включая дочерние объекты.

Каждый элемент массива intersects содержит:

  • object — объект, с которым произошло пересечение.
  • point — координаты точки пересечения.
  • distance — расстояние от луча до точки пересечения.
  • face — информация о грани, через которую произошло пересечение.
  • uv — координаты текстуры в точке пересечения.

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

Raycasting часто применяется для:

  • Выбора объектов мышью.
  • Определения коллизий в 3D-пространстве.
  • Управления интерактивными эффектами (подсветка, анимация, изменение цвета).

Пример использования для подсветки объекта при наведении мыши:

if (intersects.length > 0) {
    intersects[0].object.material.color.set(0xff0000);
}

InstancedMesh в Three.js

THREE.InstancedMesh позволяет эффективно отображать тысячи одинаковых объектов с минимальной нагрузкой на GPU, используя один геометрический буфер и отдельные трансформации для каждого экземпляра.

Создание InstancedMesh

const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
const count = 1000;

const mesh = new THREE.InstancedMesh(geometry, material, count);
scene.add(mesh);
  • geometry и material общие для всех экземпляров.
  • count — количество экземпляров.

Установка трансформаций для экземпляров

Каждому экземпляру присваивается матрица трансформации:

const dummy = new THREE.Object3D();

for (let i = 0; i < count; i++) {
    dummy.position.set(Math.random() * 10, Math.random() * 10, Math.random() * 10);
    dummy.rotation.set(Math.random() * Math.PI, Math.random() * Math.PI, Math.random() * Math.PI);
    dummy.scale.set(1, 1, 1);
    
    dummy.updateMatrix();
    mesh.setMatrixAt(i, dummy.matrix);
}
  • dummy используется как вспомогательный объект для генерации матриц.
  • setMatrixAt(i, matrix) задаёт положение, поворот и масштаб отдельного экземпляра.

Обновление InstancedMesh

Если требуется динамическое обновление:

mesh.instanceMatrix.needsUpdate = true;

Это уведомляет Three.js о том, что матрицы экземпляров изменились.

Raycasting с InstancedMesh

InstancedMesh поддерживает raycasting, но пересечение возвращает не сам объект, а индекс экземпляра через instanceId:

const intersects = raycaster.intersectObject(mesh);

if (intersects.length > 0) {
    const instanceId = intersects[0].instanceId;
    mesh.setColorAt(instanceId, new THREE.Color(0xff0000));
    mesh.instanceColor.needsUpdate = true;
}
  • setColorAt позволяет менять цвет отдельного экземпляра.
  • Необходимо включить поддержку instanceColor при создании материала:
material = new THREE.MeshStandardMaterial({ vertexColors: true });
mesh.instanceColor = new THREE.InstancedBufferAttribute(new Float32Array(count * 3), 3);

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

Использование InstancedMesh значительно уменьшает количество отдельных draw calls. Для сцен с тысячами одинаковых объектов:

  • Стандартный Mesh на каждую копию создаёт отдельный draw call.
  • InstancedMesh объединяет их в один draw call, позволяя GPU рендерить массив объектов быстрее.

Raycasting для InstancedMesh работает эффективно, но при большом количестве экземпляров стоит применять ограничивающие объёмы (Bounding Box или Bounding Sphere) для предварительной фильтрации.


Совмещение Raycasting и InstancedMesh

Комбинация этих технологий позволяет создавать интерактивные сцены с тысячами объектов и быстрым выбором:

  1. Создаётся InstancedMesh с большим количеством объектов.
  2. Пользователь перемещает курсор, координаты переводятся в нормализованные значения.
  3. Raycaster определяет пересечения, возвращая индекс экземпляра.
  4. На основе instanceId изменяются цвет, анимация или другие свойства выбранного объекта.

Эта схема позволяет создавать игры, визуализации данных, симуляции частиц и интерактивные панели с минимальной нагрузкой на рендеринг и высокий отклик на взаимодействие.