PostProcessEffect

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

Постобработка широко используется в графических движках и системах визуализации данных. После того как все слои нарисованы во внутренний буфер кадра, Deck.gl передаёт результат цепочке эффектов, которые могут модифицировать пиксели перед выводом на экран.

Типичные сценарии применения:

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

Архитектура системы эффектов Deck.gl

Deck.gl поддерживает специальную систему эффектов, которая подключается через свойство effects.

Упрощённая схема работы выглядит следующим образом:

  1. Выполняется рендеринг всех слоёв.
  2. Результат сохраняется во Framebuffer.
  3. Запускается цепочка эффектов.
  4. Каждый эффект обрабатывает изображение.
  5. Финальный кадр выводится на экран.

Пример подключения эффектов:

const deck = new Deck({
  initialViewState,
  controller: true,
  layers: [...],
  effects: [...]
});

Каждый объект внутри массива effects реализует определённый визуальный эффект.


Место PostProcessEffect в иерархии классов

Класс наследуется от базовой системы эффектов Deck.gl.

Упрощённая структура:

Effect
 └── PostProcessEffect

Effect определяет общий интерфейс жизненного цикла, а PostProcessEffect реализует обработку готового изображения через шейдеры.


Импорт PostProcessEffect

В современных версиях Deck.gl импорт выполняется следующим образом:

import {PostProcessEffect} from '@deck.gl/core';

После импорта можно создавать экземпляры эффектов и передавать их в массив effects.


Создание эффекта

Конструктор принимает два основных аргумента:

new PostProcessEffect(module, props)

где:

  • module — шейдерный модуль обработки;
  • props — параметры эффекта.

Пример:

const effect = new PostProcessEffect(shaderModule, {
  intensity: 1.0
});

Шейдерный модуль

Основой любого PostProcessEffect является shader module.

Модуль обычно содержит:

const shaderModule = {
  name: 'custom-effect',

  fs: `
    uniform float intensity;

    vec4 customEffect_filterColor(
      vec4 color,
      vec2 texSize,
      vec2 texCoord
    ) {
      return vec4(color.rgb * intensity, color.a);
    }
  `,

  uniforms: {
    intensity: 1.0
  }
};

Deck.gl автоматически интегрирует такой модуль в конвейер постобработки.


Принцип работы фильтра

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

Типичная сигнатура:

vec4 custom_filterColor(
  vec4 color,
  vec2 texSize,
  vec2 texCoord
)

Аргументы:

Параметр Описание
color текущий цвет пикселя
texSize размеры текстуры
texCoord координаты пикселя

Возвращаемое значение:

return vec4(...);

Именно оно станет новым цветом пикселя.


Простейший пример изменения яркости

Увеличение яркости изображения:

const brightnessModule = {
  name: 'brightness',

  fs: `
    uniform float brightness;

    vec4 brightness_filterColor(
      vec4 color,
      vec2 texSize,
      vec2 texCoord
    ) {
      return vec4(
        color.rgb * brightness,
        color.a
      );
    }
  `,

  uniforms: {
    brightness: 1.5
  }
};

const brightnessEffect =
  new PostProcessEffect(brightnessModule);

Все цвета становятся ярче в полтора раза.


Инверсия цветов

Пример создания негативного изображения:

const invertModule = {
  name: 'invert',

  fs: `
    vec4 invert_filterColor(
      vec4 color,
      vec2 texSize,
      vec2 texCoord
    ) {
      return vec4(
        1.0 - color.rgb,
        color.a
      );
    }
  `
};

const invertEffect =
  new PostProcessEffect(invertModule);

Результат:

Красный   → Голубой
Зелёный   → Пурпурный
Синий     → Жёлтый
Белый     → Чёрный

Перевод изображения в оттенки серого

Часто используется при аналитической визуализации.

const grayscaleModule = {
  name: 'grayscale',

  fs: `
    vec4 grayscale_filterColor(
      vec4 color,
      vec2 texSize,
      vec2 texCoord
    ) {

      float gray =
        dot(
          color.rgb,
          vec3(0.299, 0.587, 0.114)
        );

      return vec4(
        vec3(gray),
        color.a
      );
    }
  `
};

Здесь применяется стандартная формула вычисления яркости.


Управление параметрами эффекта

Параметры передаются через объект свойств.

Создание:

const effect =
  new PostProcessEffect(module, {
    intensity: 2.0
  });

Объявление в модуле:

uniform float intensity;

Использование:

return vec4(
  color.rgb * intensity,
  color.a
);

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


Изменение параметров во время работы

Например, управление эффектом через интерфейс пользователя.

let intensity = 1.0;

const effect =
  new PostProcessEffect(module, {
    intensity
  });

При изменении значения:

effect.setProps({
  intensity: 2.5
});

Deck.gl обновит uniform-переменные на стороне GPU.


Несколько эффектов одновременно

Deck.gl поддерживает композицию эффектов.

const deck = new Deck({
  layers,
  effects: [
    grayscaleEffect,
    brightnessEffect
  ]
});

Последовательность имеет значение.

Вариант №1:

[
  grayscaleEffect,
  brightnessEffect
]

Сначала изображение станет серым, затем повысится яркость.

Вариант №2:

[
  brightnessEffect,
  grayscaleEffect
]

Сначала увеличится яркость, затем выполнится преобразование в серый цвет.

Результат может существенно отличаться.


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

Координаты пикселя доступны через:

texCoord

Диапазон:

(0.0, 0.0)
       ↓
(1.0, 1.0)

Это позволяет создавать пространственные эффекты.

Пример затемнения краёв:

float distanceFromCenter =
  distance(
    texCoord,
    vec2(0.5)
  );

float factor =
  1.0 - distanceFromCenter;

return vec4(
  color.rgb * factor,
  color.a
);

Получается эффект виньетирования.


Реализация виньетки

Полный пример:

const vignetteModule = {
  name: 'vignette',

  fs: `
    vec4 vignette_filterColor(
      vec4 color,
      vec2 texSize,
      vec2 texCoord
    ) {

      float d =
        distance(
          texCoord,
          vec2(0.5)
        );

      float factor =
        smoothstep(
          0.8,
          0.2,
          d
        );

      return vec4(
        color.rgb * factor,
        color.a
      );
    }
  `
};

Эффект создаёт плавное затемнение по краям изображения.


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

Размеры изображения доступны через:

texSize

Пример:

float width = texSize.x;
float height = texSize.y;

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

  • размытия;
  • фильтров резкости;
  • edge detection;
  • антиалиасинга.

Реализация простого размытия

Упрощённый вариант blur:

vec2 offset =
  vec2(1.0) / texSize;

vec4 sum = vec4(0.0);

sum += texture2D(texture, texCoord);
sum += texture2D(texture, texCoord + offset);
sum += texture2D(texture, texCoord - offset);

return sum / 3.0;

Алгоритм усредняет соседние пиксели.

В реальных приложениях обычно применяются:

  • Gaussian Blur;
  • Kawase Blur;
  • Dual Blur.

Пользовательские Uniform-переменные

Модуль может содержать любое количество uniform-параметров.

Пример:

uniform float intensity;
uniform float radius;
uniform vec3 tint;

Передача значений:

new PostProcessEffect(module, {
  intensity: 2,
  radius: 5,
  tint: [1, 0, 0]
});

В шейдере:

color.rgb *= tint;

Цветовое тонирование

Наложение цветового оттенка:

const tintModule = {
  name: 'tint',

  fs: `
    uniform vec3 tint;

    vec4 tint_filterColor(
      vec4 color,
      vec2 texSize,
      vec2 texCoord
    ) {

      return vec4(
        color.rgb * tint,
        color.a
      );
    }
  `
};

Пример:

new PostProcessEffect(
  tintModule,
  {
    tint: [1.0, 0.8, 0.8]
  }
);

Изображение получает красноватый оттенок.


Создание эффекта ночного видения

Комбинация нескольких операций:

float gray =
  dot(
    color.rgb,
    vec3(
      0.299,
      0.587,
      0.114
    )
  );

return vec4(
  vec3(
    gray * 0.1,
    gray,
    gray * 0.1
  ),
  color.a
);

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


Производительность PostProcessEffect

Постобработка выполняется на каждом кадре.

Стоимость зависит от:

  • разрешения экрана;
  • количества проходов;
  • сложности вычислений;
  • числа обращений к текстурам;
  • количества активных эффектов.

При разрешении:

1920 × 1080

обрабатывается более двух миллионов пикселей каждый кадр.

Даже небольшое усложнение шейдера может существенно повлиять на FPS.


Оптимизация эффектов

Рекомендуемые практики:

Минимизировать выборки текстур

Плохо:

20 texture2D(...)

Лучше:

5 texture2D(...)

Избегать сложной математики

Дорогие операции:

pow()
exp()
log()

Быстрее:

x * x

Объединять эффекты

Вместо:

effectA
effectB
effectC

часто эффективнее:

combinedEffect

с одним проходом обработки.


Ограничения PostProcessEffect

Следует учитывать несколько особенностей:

Эффект не знает о слоях

После рендеринга остаётся только изображение.

Недоступны:

Layer
Object
Feature
Attribute

Доступны только пиксели итогового кадра.

Ограниченная информация о сцене

Нельзя напрямую определить:

  • какой слой нарисовал пиксель;
  • какой объект находится под пикселем;
  • какие данные использовались при рендеринге.

Полноэкранная обработка

Даже если изменяется небольшая область изображения, вычисления всё равно выполняются для всего кадра.


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

Геоинформационные системы

  • подсветка важных областей;
  • повышение читаемости карты;
  • ночной режим.

Аналитические панели

  • цветовые фильтры;
  • улучшение контраста;
  • акцентирование данных.

Научная визуализация

  • тепловые карты;
  • псевдоцветовое кодирование;
  • обработка результатов моделирования.

3D-визуализация

  • Bloom;
  • HDR-тонмаппинг;
  • SSAO;
  • глубинное размытие.

Сравнение PostProcessEffect и Layer Extensions

Возможность PostProcessEffect Layer Extension
Обработка отдельных объектов Нет Да
Работа после рендеринга Да Нет
Доступ к пикселям кадра Да Нет
Изменение геометрии Нет Да
Полноэкранные фильтры Да Ограниченно
Эффекты камеры Да Нет

Layer Extensions предназначены для модификации поведения слоёв, тогда как PostProcessEffect работает с уже сформированным изображением.


Жизненный цикл эффекта

Во время работы Deck.gl проходит несколько этапов:

  1. Создание эффекта.
  2. Инициализация ресурсов GPU.
  3. Рендеринг слоёв.
  4. Запуск шейдера постобработки.
  5. Передача результата следующему эффекту.
  6. Вывод финального изображения.
  7. Освобождение ресурсов при уничтожении объекта.

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