Drawcall: что это и как уменьшить

Draw Call — это команда, которую движок WebGL отправляет видеокарте для отрисовки определённого объекта на экране. В контексте Three.js каждый объект сцены с уникальным материалом обычно формирует отдельный draw call. Чем больше объектов и материалов используется, тем больше draw call, и тем выше нагрузка на графический процессор. Это напрямую влияет на FPS и общую производительность сцены, особенно на мобильных устройствах.

Каждый draw call включает в себя:

  • Состояние рендеринга (активные шейдеры, текстуры, буферы).
  • Команды по отрисовке вершин и фрагментов.
  • Применение материалов и трансформаций объекта.

Даже при небольшой геометрической сложности сцены чрезмерное количество draw call может привести к торможению.


Факторы, увеличивающие количество Draw Call

  1. Множество объектов с уникальными материалами Каждый объект с отдельным материалом создаёт отдельный draw call. Например, сцена с сотней кубов, где у каждого своя текстура, генерирует сто draw call, даже если геометрия идентична.

  2. Использование прозрачных материалов Прозрачные объекты требуют сортировки по глубине и дополнительных вычислений, что увеличивает количество draw call и сложность рендеринга.

  3. Частые смены состояний WebGL Изменение текстур, шейдеров или параметров рендеринга между объектами вынуждает движок завершать один draw call и запускать новый.

  4. Сложная иерархия объектов Группы объектов с множеством дочерних элементов, каждый со своим материалом, создают цепочку draw call.


Методы уменьшения количества Draw Call

1. Объединение геометрий (Geometry Merging)

Three.js предоставляет BufferGeometryUtils.mergeBufferGeometries(), позволяющий объединять несколько геометрий в одну. Объединённая геометрия с одним материалом создаёт всего один draw call вместо множества.

Пример:

import { BufferGeometryUtils } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
import { BoxGeometry, Mesh, MeshStandardMaterial } from 'three';

const geometries = [];
for (let i = 0; i < 50; i++) {
  const box = new BoxGeometry(1, 1, 1);
  box.translate(i * 2, 0, 0);
  geometries.push(box);
}
const mergedGeometry = BufferGeometryUtils.mergeBufferGeometries(geometries);
const material = new MeshStandardMaterial({ color: 0x00ff00 });
const mesh = new Mesh(mergedGeometry, material);
scene.add(mesh);

Ключевой эффект: количество draw call снижается до одного.


2. Использование InstancedMesh

InstancedMesh позволяет рендерить один и тот же объект несколько раз с разными трансформациями (позиция, масштаб, поворот) в рамках одного draw call.

Пример:

import { InstancedMesh, BoxGeometry, MeshStandardMaterial, Matrix4 } from 'three';

const geometry = new BoxGeometry(1, 1, 1);
const material = new MeshStandardMaterial({ color: 0xff0000 });
const count = 100;

const instancedMesh = new InstancedMesh(geometry, material, count);

const matrix = new Matrix4();
for (let i = 0; i < count; i++) {
  matrix.setPosition(Math.random() * 10, Math.random() * 10, Math.random() * 10);
  instancedMesh.setMatrixAt(i, matrix);
}

scene.add(instancedMesh);

Преимущество: сотни или тысячи объектов рендерятся за один draw call.


3. Атлас текстур

Создание texture atlas позволяет объединять несколько текстур в одну большую текстуру. Это уменьшает количество материалов и, как следствие, draw call.

Особенности:

  • Координаты UV объектов корректируются под новую текстуру.
  • Подходит для сцен с множеством одинаковых объектов (например, плитки, листья деревьев).

4. Оптимизация материалов

  • Сокращение числа разных шейдеров. Каждый шейдер создаёт отдельный draw call.
  • Использование общих материалов для одинаковых объектов.
  • Минимизация прозрачных объектов и сложных эффектов (emissive, bump, normal maps), если это не критично визуально.

5. Лодирование сложных моделей

Сложные модели можно разбивать на логические группы, объединяя геометрию и используя instancing для повторяющихся частей. Это особенно полезно для:

  • Архитектуры (здания с одинаковыми окнами)
  • Природных элементов (деревья, камни)
  • Игровых объектов (юниты, детали интерьера)

Инструменты для анализа Draw Call

Three.js не показывает draw call напрямую в стандартной сборке, но можно использовать:

  • stats.js — плагин для отслеживания FPS и draw call.
  • renderer.info.render.calls — свойство WebGLRenderer, которое хранит количество вызванных draw call за кадр.

Пример:

console.log(renderer.info.render.calls);

Регулярный мониторинг позволяет выявлять узкие места и оптимизировать сцену.


Практические рекомендации

  • Для динамических объектов лучше использовать InstancedMesh.
  • Для статических объектов оптимально применять mergeBufferGeometries.
  • Для текстурного разнообразия — использовать texture atlas.
  • Минимизировать прозрачные материалы и шейдерные эффекты без критичной визуальной необходимости.
  • Всегда проверять renderer.info.render.calls, чтобы понимать, сколько draw call реально создаётся на кадр.

Эти подходы обеспечивают значительное улучшение производительности и позволяют Three.js справляться с большими сценами без падения FPS.