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

В Three.js рендеринг сцены строится на последовательном выполнении пассов (passes). Каждый пасс представляет собой отдельный этап обработки сцены, который может включать в себя отрисовку объектов, постобработку эффектов или композитинг нескольких текстур. Понимание порядка выполнения пассов критически важно для оптимизации производительности и правильного отображения визуальных эффектов.

Основной цикл рендеринга в Three.js устроен следующим образом:

  1. Сборка сценографических данных: камера, объекты, свет.
  2. Выполнение пассов постобработки: эффекты bloom, blur, tone mapping.
  3. Финальный рендер в буфер кадра (framebuffer).

Каждый пасс может опираться на результаты предыдущего, поэтому изменение порядка выполнения напрямую влияет на визуальный результат. Например, эффект bloom должен применяться после базового рендеринга, иначе подсветка объектов будет некорректной.


RenderPass и EffectComposer

Three.js предоставляет EffectComposer, который позволяет объединять несколько пассов в цепочку и управлять ими:

const composer = new THREE.EffectComposer(renderer);
const renderPass = new THREE.RenderPass(scene, camera);
composer.addPass(renderPass);

const bloomPass = new THREE.UnrealBloomPass(
    new THREE.Vector2(window.innerWidth, window.innerHeight),
    1.5, 0.4, 0.85
);
composer.addPass(bloomPass);
  • RenderPass отвечает за базовый рендеринг сцены.
  • UnrealBloomPass или другие постэффекты должны идти после RenderPass.
  • Порядок добавления пассов в composer определяет их последовательность применения.

Важно понимать, что EffectComposer использует промежуточные рендер-таргеты (render targets). Каждый пасс пишет в текстуру, которая затем может использоваться следующими пассами. Это накладывает ограничения на производительность: больше пассов → больше операций чтения/записи в GPU.


Оптимизация количества пассов

Избыточное использование пассов негативно сказывается на FPS. Основные рекомендации:

  • Минимизировать количество сложных постэффектов.
  • Объединять эффекты, которые могут быть выполнены в одном шейдере.
  • Использовать masking и stencil buffer только при необходимости, так как они создают дополнительные вычислительные накладные расходы.
  • Включать renderToScreen только у последнего пасса.

Пример корректного использования renderToScreen:

bloomPass.renderToScreen = true; // финальный вывод

Если несколько пассов с renderToScreen = true, вывод будет некорректным, и часть GPU-ресурсов будет потрачена впустую.


Многопроходная сцена и прозрачность

При работе с прозрачными объектами важно учитывать порядок рендеринга. Three.js выполняет сортировку по глубине (depth sorting) перед рендерингом. Однако при нескольких пассах с постэффектами:

  • Объекты с прозрачностью должны рендериться после непрозрачных объектов.
  • Пассы, влияющие на прозрачность (например, fog или alpha blending), должны идти после RenderPass, который рендерит эти объекты.

Использование depthWrite и depthTest в материалах позволяет контролировать корректное наложение слоев:

material.transparent = true;
material.depthWrite = false;
material.depthTest = true;

Использование RenderTarget для повышения производительности

Эффективное использование THREE.WebGLRenderTarget позволяет:

  • Кэшировать промежуточные результаты сложных сцен.
  • Ограничивать размер текстур для постобработки (например, bloom можно рендерить в уменьшенном разрешении).
  • Избегать повторного рендеринга всей сцены при применении нескольких эффектов.

Пример создания render target:

const renderTarget = new THREE.WebGLRenderTarget(
    window.innerWidth / 2,
    window.innerHeight / 2,
    { minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter }
);

Здесь разрешение уменьшено вдвое, что снижает нагрузку на GPU при постобработке.


Совмещение прозрачных объектов и постэффектов

При работе с прозрачными объектами и эффектами типа bloom или SSAO рекомендуется использовать два подхода:

  1. Отдельные слои (layers): рендерить прозрачные и непрозрачные объекты на разных слоях.
  2. Двойной рендеринг: сначала непрозрачные объекты + постэффекты, затем прозрачные объекты с отдельным RenderPass.

Пример использования слоев:

opaqueObjects.layers.set(0);
transparentObjects.layers.set(1);

camera.layers.enable(0);
camera.layers.enable(1);

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


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

  • Минимизировать количество пассов и размер промежуточных текстур.
  • Использовать mask и stencil только при необходимости.
  • Сортировать объекты по непрозрачности для правильной работы Depth Test.
  • Контролировать renderToScreen — только у финального пасса.
  • Оптимизировать эффекты, комбинируя шейдеры.
  • Применять WebGLRenderTarget для кэширования сложных сцен.

Эти правила позволяют создать сложную визуализацию в Three.js без существенного падения FPS, сохраняя корректный порядок рендеринга и качество постобработки.