Frustum culling и его настройка

В трёхмерной графике важной задачей является уменьшение количества объектов, которые должны быть обработаны и отрисованы графическим процессором. Одним из базовых методов оптимизации служит frustum culling — отсечение объектов, находящихся вне области видимости камеры.

Камера в трёхмерной сцене формирует усечённую пирамиду видимости (view frustum). Эта фигура определяется шестью плоскостями:

  • Near plane — ближняя плоскость отсечения
  • Far plane — дальняя плоскость отсечения
  • Left plane — левая граница
  • Right plane — правая граница
  • Top plane — верхняя граница
  • Bottom plane — нижняя граница

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

В библиотеке Babylon.js механизм frustum culling встроен в систему сцены и применяется автоматически при рендеринге.


Принцип работы frustum culling

Каждый кадр рендеринга проходит несколько этапов обработки сцены:

  1. Обновление трансформаций объектов
  2. Формирование матриц камеры
  3. Вычисление плоскостей frustum
  4. Проверка объектов на пересечение с frustum
  5. Рендеринг только видимых объектов

Проверка выполняется на основе bounding volumes — упрощённых геометрических оболочек, описывающих размеры объекта.

В Babylon.js используются два основных типа ограничивающих объёмов:

  • Bounding Sphere — ограничивающая сфера
  • Bounding Box — ограничивающий параллелепипед

Проверка происходит следующим образом:

  1. Сначала выполняется быстрый тест bounding sphere vs frustum.
  2. При необходимости выполняется более точный тест bounding box vs frustum.

Такой подход уменьшает вычислительную нагрузку.


Bounding volumes в Babylon.js

Каждый Mesh автоматически получает объект BoundingInfo, содержащий информацию об ограничивающих объёмах.

Пример получения данных bounding volumes:

const mesh = scene.getMeshByName("house");

const boundingInfo = mesh.getBoundingInfo();

const boundingBox = boundingInfo.boundingBox;
const boundingSphere = boundingInfo.boundingSphere;

Свойства bounding sphere:

boundingSphere.centerWorld
boundingSphere.radiusWorld

Свойства bounding box:

boundingBox.minimumWorld
boundingBox.maximumWorld

Эти структуры используются системой frustum culling для определения видимости объекта.


Формирование frustum камеры

Babylon.js вычисляет frustum из матрицы проекции и матрицы вида камеры.

Основные параметры камеры, влияющие на форму frustum:

  • fieldOfView (FOV) — угол обзора
  • aspect ratio — соотношение сторон
  • minZ — ближняя плоскость
  • maxZ — дальняя плоскость

Пример настройки камеры:

const camera = new BABYLON.FreeCamera(
    "camera",
    new BABYLON.Vector3(0, 5, -10),
    scene
);

camera.minZ = 0.1;
camera.maxZ = 1000;
camera.fov = BABYLON.Tools.ToRadians(60);

Изменение этих параметров влияет на объём frustum и количество объектов, проходящих проверку видимости.


Проверка попадания объекта во frustum

Babylon.js предоставляет методы для проверки видимости объекта.

Проверка bounding box:

const frustumPlanes = BABYLON.Frustum.GetPlanes(camera.getTransformationMatrix());

const isVisible = mesh.isInFrustum(frustumPlanes);

Проверка bounding sphere:

mesh.getBoundingInfo().boundingSphere.isInFrustum(frustumPlanes);

Метод isInFrustum возвращает true, если объект пересекает хотя бы одну часть frustum.


Автоматическое frustum culling в сцене

Во время рендеринга Babylon.js автоматически выполняет frustum culling для всех Mesh объектов сцены.

Внутренний процесс:

Scene.render()
   → evaluateActiveMeshes()
       → mesh.isInFrustum()
       → добавление в activeMeshes
   → renderActiveMeshes()

Список activeMeshes содержит только те объекты, которые:

  • находятся внутри frustum
  • не скрыты
  • имеют включённый рендеринг

Получить список активных мешей можно через:

scene.activeMeshes

Этот список формируется заново каждый кадр.


Отключение frustum culling для объекта

В некоторых случаях необходимо отключить отсечение для определённого объекта.

Например:

  • огромные объекты
  • объекты со сложной анимацией
  • объекты, частично выходящие за пределы frustum

Для этого используется свойство:

mesh.alwaysSelectAsActiveMesh = true;

Пример:

const skybox = BABYLON.MeshBuilder.CreateBox("sky", {size: 1000}, scene);

skybox.alwaysSelectAsActiveMesh = true;

Такой объект всегда будет добавляться в activeMeshes независимо от положения относительно frustum.


Управление вычислением bounding info

Bounding volumes обновляются автоматически при изменении трансформации объекта.

Если геометрия изменяется вручную, требуется обновление:

mesh.refreshBoundingInfo();

Это необходимо после:

  • изменения вершин
  • модификации буферов
  • процедурной генерации геометрии

Пример:

mesh.updateVerticesData(
    BABYLON.VertexBuffer.PositionKind,
    newPositions
);

mesh.refreshBoundingInfo();

Без обновления bounding volumes frustum culling может работать некорректно.


Расширение bounding volumes

Иногда bounding box оказывается слишком маленьким для сложной анимации. Это может приводить к преждевременному отсечению объекта.

Babylon.js позволяет расширять bounding box:

mesh.getBoundingInfo().boundingBox.extendSize

Пример:

mesh.getBoundingInfo().boundingBox.extendSize.scaleInPlace(2);

После изменения требуется обновление:

mesh.refreshBoundingInfo();

Это особенно важно для:

  • skinned meshes
  • морфинга
  • процедурной анимации

Замораживание списка активных мешей

Если сцена статична, вычисление frustum culling можно выполнять только один раз.

Babylon.js предоставляет метод:

scene.freezeActiveMeshes();

После этого:

  • список activeMeshes перестаёт пересчитываться
  • frustum culling не выполняется каждый кадр

Пример:

scene.executeWhenReady(() => {
    scene.freezeActiveMeshes();
});

Для возврата к обычному поведению:

scene.unfreezeActiveMeshes();

Этот метод значительно снижает нагрузку CPU в статичных сценах.


Оптимизация дальних плоскостей

Параметр camera.maxZ напрямую влияет на frustum.

Слишком большая дальняя плоскость:

  • увеличивает количество потенциально видимых объектов
  • ухудшает точность depth buffer
  • снижает эффективность culling

Пример оптимальной настройки:

camera.minZ = 0.5;
camera.maxZ = 200;

Чем меньше диапазон minZ – maxZ, тем эффективнее работает отсечение.


Визуализация frustum

Для отладки сцены иногда требуется увидеть границы frustum.

Плоскости frustum можно получить так:

const planes = BABYLON.Frustum.GetPlanes(
    camera.getTransformationMatrix()
);

Каждая плоскость содержит:

plane.normal
plane.d

Эти данные можно использовать для построения отладочной геометрии.


Frustum culling и иерархия трансформаций

Если объект является частью иерархии (parent-child), frustum culling учитывает мировые трансформации.

Пример:

childMesh.parent = parentMesh;

Bounding volumes дочернего объекта пересчитываются в мировых координатах.

Это позволяет корректно определять видимость даже при сложных иерархиях сцены.


Взаимодействие с системой LOD

Frustum culling тесно связан с системой Level of Detail (LOD).

Алгоритм работы:

  1. Проверка попадания объекта во frustum
  2. Выбор уровня детализации
  3. Рендеринг соответствующего LOD

Пример:

mesh.addLODLevel(50, lodMesh1);
mesh.addLODLevel(100, lodMesh2);

Если объект находится вне frustum, система LOD не выполняется, что дополнительно снижает нагрузку.


Ограничения frustum culling

Несмотря на эффективность, метод имеет ограничения.

Frustum culling не учитывает перекрытие объектов. Если объект находится внутри frustum, но закрыт другим объектом, он всё равно будет отправлен на рендеринг.

Для решения этой проблемы применяются дополнительные техники:

  • Occlusion culling
  • Portal culling
  • Hardware occlusion queries

Babylon.js поддерживает аппаратные occlusion queries как дополнительный уровень оптимизации.


Влияние количества объектов на производительность

Frustum culling особенно эффективен в сценах с большим количеством объектов.

Пример:

Количество объектов Видимых Отсечённых
1000 150 850
10000 1200 8800

Без frustum culling все объекты отправлялись бы на GPU, что значительно снижало бы производительность.


Лучшие практики использования frustum culling

1. Минимизировать размеры bounding volumes

Большие bounding box уменьшают эффективность отсечения.

2. Ограничивать дальнюю плоскость камеры

Меньший диапазон minZ-maxZ улучшает работу алгоритма.

3. Использовать freezeActiveMeshes для статичных сцен

Это снижает CPU-нагрузку.

4. Обновлять bounding info после изменения геометрии

Иначе возможны ошибки видимости.

5. Отключать culling только при необходимости

alwaysSelectAsActiveMesh должен использоваться ограниченно.


Внутренний алгоритм проверки пересечения

Проверка объекта выполняется относительно каждой из шести плоскостей frustum.

Для каждой плоскости вычисляется расстояние:

distance = normal · point + d

Если все вершины bounding box находятся с отрицательной стороны одной из плоскостей, объект полностью вне frustum и может быть отброшен.

Если хотя бы часть объёма пересекает frustum, объект считается видимым.

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