Depth texture и пользовательское использование буфера глубины

Three.js предоставляет мощный набор инструментов для работы с глубиной сцены. Буфер глубины (depth buffer) содержит информацию о расстоянии от камеры до каждого фрагмента в сцене и используется для корректного отображения объектов в 3D-пространстве, особенно при наложении полупрозрачных объектов или при вычислении теней. Управление этим буфером открывает возможности для создания сложных визуальных эффектов, таких как постобработка, глубинное затенение и эффекты SSAO (Screen Space Ambient Occlusion).


Создание Depth Texture

Depth Texture — это текстура, которая хранит значения глубины сцены. В Three.js она создаётся с помощью THREE.DepthTexture и может быть привязана к THREE.WebGLRenderTarget для последующего использования.

const renderTarget = new THREE.WebGLRenderTarget(window.innerWidth, window.innerHeight, {
    minFilter: THREE.NearestFilter,
    magFilter: THREE.NearestFilter,
    format: THREE.RGBAFormat,
    depthTexture: new THREE.DepthTexture(),
    depthBuffer: true
});

Ключевые моменты:

  • depthTexture — объект THREE.DepthTexture, который будет хранить глубину.
  • depthBuffer: true гарантирует создание буфера глубины на рендер-таргете.
  • Фильтры NearestFilter используются для точного чтения глубины без интерполяции.

Использование Depth Texture в шейдерах

Depth Texture часто применяется в шейдерах для различных эффектов, например, для создания глубинной размытия (Depth of Field) или эффектов тумана. Основная идея заключается в том, чтобы считывать значения глубины и использовать их для вычислений внутри фрагментного шейдера.

uniform sampler2D depthTexture;
uniform float cameraNear;
uniform float cameraFar;

float readDepth(sampler2D depthSampler, vec2 uv) {
    float z_b = texture2D(depthSampler, uv).x;
    float z_n = 2.0 * z_b - 1.0;
    return (2.0 * cameraNear * cameraFar) / (cameraFar + cameraNear - z_n * (cameraFar - cameraNear));
}

Принцип работы:

  1. Значение глубины в текстуре (z_b) находится в диапазоне [0, 1].
  2. Оно переводится в нормализованные координаты устройства (NDC).
  3. Конвертируется обратно в линейную глубину камеры (z_n) для корректных вычислений.

Эта функция позволяет применять глубинные эффекты с точным учётом перспективы камеры.


Настройка камеры и рендер-таргета

При работе с глубиной важно учитывать параметры камеры. Линейная интерпретация глубины зависит от near и far камеры:

const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 100);

Совет: уменьшение значения near и увеличение far увеличивает диапазон глубины, но снижает точность буфера глубины. Для постобработки и эффектов SSAO часто требуется сбалансированное значение этих параметров.

Рендер сцены в текстуру с глубиной выполняется так:

renderer.setRenderTarget(renderTarget);
renderer.render(scene, camera);
renderer.setRenderTarget(null);

После этого renderTarget.depthTexture готова к использованию в шейдерах.


Применение Depth Texture в постобработке

Depth Texture идеально подходит для постобработки через THREE.ShaderPass или кастомные шейдеры:

  • Depth of Field: вычисление размытия пикселей в зависимости от их глубины.
  • Screen Space Ambient Occlusion (SSAO): затемнение областей, где объекты близко друг к другу.
  • Fog & Atmospheric Effects: плавная имитация тумана с использованием глубины сцены.

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

const material = new THREE.ShaderMaterial({
    uniforms: {
        tDiffuse: { value: renderTarget.texture },
        tDepth: { value: renderTarget.depthTexture },
        cameraNear: { value: camera.near },
        cameraFar: { value: camera.far }
    },
    vertexShader: vertexShaderSource,
    fragmentShader: fragmentShaderSource
});

В фрагментном шейдере tDepth используется для корректного расчёта глубины и создания визуальных эффектов.


Глубина и прозрачность

При работе с полупрозрачными объектами важно учитывать порядок рендеринга и буфер глубины. Традиционно, объекты с альфа-каналом рендерятся после всех непрозрачных объектов. Для глубоких эффектов можно использовать отдельный рендер-таргет для прозрачных объектов или применять технику depth peeling, где слои прозрачности обрабатываются поочерёдно.


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

  • Формат глубины: THREE.DepthFormat с THREE.UnsignedShortType или THREE.UnsignedIntType уменьшает использование памяти.
  • Размер рендер-таргета: уменьшение разрешения текстуры глубины ускоряет постобработку без заметной потери качества.
  • Пересчёт буфера глубины: следует минимизировать количество рендеров сцены в разные таргеты, используя один общий Depth Texture для нескольких эффектов.

Интерактивное использование глубины

Depth Texture можно применять не только для постобработки, но и для взаимодействия с объектами сцены. Например, для выбора объектов по глубине или создания эффекта «прилипшего света» на ближайших объектах. Для этого используется чтение значения глубины с помощью renderer.readRenderTargetPixels или шейдерных вычислений, что позволяет вычислять точные координаты в 3D-пространстве.


Итоги по функционалу

  • Depth Texture хранит линейную глубину сцены.
  • Она интегрируется с WebGLRenderTarget и может быть использована в кастомных шейдерах.
  • Эффекты, основанные на глубине, включают SSAO, Depth of Field, туман, прозрачность и интерактивные эффекты.
  • Оптимизация работы с буфером глубины критична для производительности, особенно при высоких разрешениях.

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