В трёхмерной графике важной задачей является уменьшение количества объектов, которые должны быть обработаны и отрисованы графическим процессором. Одним из базовых методов оптимизации служит frustum culling — отсечение объектов, находящихся вне области видимости камеры.
Камера в трёхмерной сцене формирует усечённую пирамиду видимости (view frustum). Эта фигура определяется шестью плоскостями:
Любой объект сцены, полностью находящийся вне этой области, не может быть виден камерой и поэтому может быть исключён из процесса рендеринга.
В библиотеке Babylon.js механизм frustum culling встроен в систему сцены и применяется автоматически при рендеринге.
Каждый кадр рендеринга проходит несколько этапов обработки сцены:
Проверка выполняется на основе 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 для определения видимости объекта.
Babylon.js вычисляет frustum из матрицы проекции и матрицы вида камеры.
Основные параметры камеры, влияющие на форму frustum:
Пример настройки камеры:
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 и количество объектов, проходящих проверку видимости.
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.
Во время рендеринга Babylon.js автоматически выполняет frustum
culling для всех Mesh объектов сцены.
Внутренний процесс:
Scene.render()
→ evaluateActiveMeshes()
→ mesh.isInFrustum()
→ добавление в activeMeshes
→ renderActiveMeshes()
Список activeMeshes содержит только те объекты,
которые:
Получить список активных мешей можно через:
scene.activeMeshes
Этот список формируется заново каждый кадр.
В некоторых случаях необходимо отключить отсечение для определённого объекта.
Например:
Для этого используется свойство:
mesh.alwaysSelectAsActiveMesh = true;
Пример:
const skybox = BABYLON.MeshBuilder.CreateBox("sky", {size: 1000}, scene);
skybox.alwaysSelectAsActiveMesh = true;
Такой объект всегда будет добавляться в activeMeshes
независимо от положения относительно frustum.
Bounding volumes обновляются автоматически при изменении трансформации объекта.
Если геометрия изменяется вручную, требуется обновление:
mesh.refreshBoundingInfo();
Это необходимо после:
Пример:
mesh.updateVerticesData(
BABYLON.VertexBuffer.PositionKind,
newPositions
);
mesh.refreshBoundingInfo();
Без обновления bounding volumes frustum culling может работать некорректно.
Иногда bounding box оказывается слишком маленьким для сложной анимации. Это может приводить к преждевременному отсечению объекта.
Babylon.js позволяет расширять bounding box:
mesh.getBoundingInfo().boundingBox.extendSize
Пример:
mesh.getBoundingInfo().boundingBox.extendSize.scaleInPlace(2);
После изменения требуется обновление:
mesh.refreshBoundingInfo();
Это особенно важно для:
Если сцена статична, вычисление frustum culling можно выполнять только один раз.
Babylon.js предоставляет метод:
scene.freezeActiveMeshes();
После этого:
activeMeshes перестаёт пересчитыватьсяПример:
scene.executeWhenReady(() => {
scene.freezeActiveMeshes();
});
Для возврата к обычному поведению:
scene.unfreezeActiveMeshes();
Этот метод значительно снижает нагрузку CPU в статичных сценах.
Параметр camera.maxZ напрямую влияет на frustum.
Слишком большая дальняя плоскость:
Пример оптимальной настройки:
camera.minZ = 0.5;
camera.maxZ = 200;
Чем меньше диапазон minZ – maxZ, тем эффективнее
работает отсечение.
Для отладки сцены иногда требуется увидеть границы frustum.
Плоскости frustum можно получить так:
const planes = BABYLON.Frustum.GetPlanes(
camera.getTransformationMatrix()
);
Каждая плоскость содержит:
plane.normal
plane.d
Эти данные можно использовать для построения отладочной геометрии.
Если объект является частью иерархии (parent-child),
frustum culling учитывает мировые трансформации.
Пример:
childMesh.parent = parentMesh;
Bounding volumes дочернего объекта пересчитываются в мировых координатах.
Это позволяет корректно определять видимость даже при сложных иерархиях сцены.
Frustum culling тесно связан с системой Level of Detail (LOD).
Алгоритм работы:
Пример:
mesh.addLODLevel(50, lodMesh1);
mesh.addLODLevel(100, lodMesh2);
Если объект находится вне frustum, система LOD не выполняется, что дополнительно снижает нагрузку.
Несмотря на эффективность, метод имеет ограничения.
Frustum culling не учитывает перекрытие объектов. Если объект находится внутри frustum, но закрыт другим объектом, он всё равно будет отправлен на рендеринг.
Для решения этой проблемы применяются дополнительные техники:
Babylon.js поддерживает аппаратные occlusion queries как дополнительный уровень оптимизации.
Frustum culling особенно эффективен в сценах с большим количеством объектов.
Пример:
| Количество объектов | Видимых | Отсечённых |
|---|---|---|
| 1000 | 150 | 850 |
| 10000 | 1200 | 8800 |
Без frustum culling все объекты отправлялись бы на GPU, что значительно снижало бы производительность.
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-рендеринге.