Регистрация кастомных эффектов

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

Базовая идея заключается в том, что эффекты подключаются к сцене как массив объектов, каждый из которых реализует жизненный цикл: инициализацию, обновление параметров и применение к кадру рендера.


Место эффектов в пайплайне рендеринга

Kepler.gl использует многоуровневый рендеринг:

  • слои (layers) формируют геометрию;
  • deck.gl WebGL контекст выполняет отрисовку;
  • пост-эффекты применяются поверх итогового буфера кадра.

Эффекты не изменяют данные слоёв напрямую. Вместо этого они работают с итоговым framebuffer, используя WebGL шейдеры или встроенные утилиты deck.gl.


Базовая модель эффекта

В экосистеме deck.gl эффект обычно реализуется через наследование от Effect:

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

class MyCustomEffect extends Effect {
  constructor(props) {
    super(props);
    this.props = props;
  }

  initialize({gl}) {
    // создание ресурсов WebGL
  }

  preDraw({gl, time}) {
    // подготовка перед рендером сцены
  }

  draw({gl, width, height}) {
    // пост-обработка кадра
  }

  cleanup() {
    // освобождение ресурсов
  }
}

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


Подключение эффектов через конфигурацию карты

Kepler.gl хранит конфигурацию визуализации в объекте mapState / mapStyle / visState. Эффекты передаются как часть состояния карты через API добавления данных.

Наиболее распространённый способ — использование addDataToMap:

dispatch(
  addDataToMap({
    datasets: data,
    options: {
      centerMap: true,
      readOnly: false
    },
    config: {
      mapStyle: {
        styleType: 'dark'
      },
      visState: {
        effects: [
          {
            id: 'my-effect',
            type: 'MyCustomEffect',
            props: {
              intensity: 0.5
            }
          }
        ]
      }
    }
  })
);

В этом случае Kepler.gl интерпретирует объект эффекта как часть визуального состояния и передаёт его в слой deck.gl при инициализации сцены.


Регистрация кастомного эффекта в контексте приложения

Чтобы Kepler.gl мог корректно создать экземпляр эффекта по строковому идентификатору type, необходимо расширить фабрику эффектов.

Типичный подход — создание реестра эффектов:

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

class HeatPulseEffect extends Effect {
  initialize({gl}) {
    this.gl = gl;
  }

  draw({gl, time}) {
    // кастомная логика шейдера
  }
}

const effectRegistry = {
  HeatPulseEffect
};

export default effectRegistry;

Далее этот реестр интегрируется в слой инициализации Kepler.gl через расширение конфигурации приложения.


Интеграция через Redux и KeplerGl reducer

Kepler.gl активно использует Redux, поэтому регистрация эффектов часто выполняется на уровне store enhancer или middleware.

Пример расширения состояния:

const initialState = {
  customEffects: {
    HeatPulseEffect
  }
};

И последующее использование при сборке карты:

const store = createStore(
  rootReducer,
  applyMiddleware(
    keplerGlMiddleware({
      customEffects: effectRegistry
    })
  )
);

Таким образом, при десериализации конфигурации Kepler.gl получает доступ к пользовательским эффектам.


Использование deck.gl post-processing эффектов

Часть эффектов в Kepler.gl не требует полной реализации класса Effect. deck.gl предоставляет готовые пост-эффекты:

  • освещение сцены;
  • размытие;
  • шум;
  • наклон и перспектива.

Они подключаются как готовые модули:

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

const effects = [
  new BrightnessContrastEffect({
    contrast: 1.2,
    brightness: 0.1
  })
];

Kepler.gl может принимать такие эффекты напрямую, если они сериализуемы или создаются на этапе инициализации карты.


Параметризация эффектов через visState

Каждый эффект может иметь динамические параметры, которые синхронизируются с состоянием приложения:

  • интенсивность;
  • радиус воздействия;
  • временные коэффициенты;
  • цветовые коэффициенты.

Пример структуры состояния:

visState: {
  effects: [
    {
      id: 'pulse',
      type: 'HeatPulseEffect',
      props: {
        speed: 1.5,
        intensity: 0.8,
        color: [255, 100, 50]
      }
    }
  ]
}

Изменение этих параметров через UI Kepler.gl приводит к пересозданию или обновлению эффекта без перезагрузки сцены.


Обновление эффекта в runtime

Эффекты поддерживают реактивное обновление. При изменении состояния Kepler.gl выполняет diff конфигурации и вызывает обновление props у существующего эффекта.

Типовой механизм:

updateState(newProps) {
  this.props = {
    ...this.props,
    ...newProps
  };
}

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


Работа с WebGL ресурсами

При создании кастомного эффекта важно учитывать управление GPU-ресурсами:

  • текстуры должны освобождаться в cleanup;
  • буферы не должны пересоздаваться на каждом кадре;
  • шейдеры компилируются один раз при initialize.

Пример:

cleanup() {
  if (this.texture) {
    this.texture.delete();
  }
}

Ограничения и совместимость

При регистрации кастомных эффектов необходимо учитывать:

  • несовместимость с серверным рендерингом (SSR);
  • зависимость от WebGL контекста;
  • ограничения сериализации состояния Kepler.gl;
  • возможные конфликты с встроенными эффектами deck.gl.

Некорректная реализация эффекта может привести к блокировке render loop или утечкам GPU памяти.


Организация нескольких эффектов одновременно

Kepler.gl поддерживает стек эффектов, где каждый эффект применяется последовательно. Порядок в массиве имеет значение:

  • первый эффект получает исходный framebuffer;
  • каждый следующий работает с результатом предыдущего;
  • финальный эффект формирует итоговое изображение.
effects: [
  new BloomEffect(),
  new NoiseEffect(),
  new ColorCorrectionEffect()
]

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

Эффекты могут взаимодействовать с кастомными слоями deck.gl, используемыми внутри Kepler.gl. В этом случае эффект может опираться на:

  • атрибуты слоя;
  • глобальное состояние карты;
  • временные данные (animation time).

Это позволяет строить комплексные визуализации, где слой и эффект формируют единое поведение сцены.