WebGL кастомные слои

Механизм пользовательских слоёв основан на интерфейсе CustomLayerInterface, который позволяет внедрять произвольную WebGL-логику в графический конвейер рендеринга карты. Слой интегрируется непосредственно в сцену, управляемую движком Mapbox, и получает доступ к уже инициализированному WebGL-контексту.

Ключевая особенность подхода заключается в совместном использовании контекста WebGL. Карта не создаёт отдельный gl для каждого слоя; вместо этого все кастомные слои работают в одном контексте с базовыми тайлами, линиями, полигонами и символами. Это исключает дорогостоящие переключения контекста и позволяет синхронизировать отрисовку с внутренним рендер-циклом.

Структура слоя описывается объектом с обязательными полями:

  • id — уникальный идентификатор слоя
  • type — всегда "custom"
  • renderingMode"2d" или "3d"
  • onAdd(map, gl) — инициализация ресурсов
  • render(gl, matrix) — основной рендеринг кадра

Дополнительно может использоваться prerender (в некоторых версиях или расширенных реализациях) для подготовки состояния перед основным проходом.


Жизненный цикл кастомного слоя

Инициализация через onAdd

Метод onAdd(map, gl) вызывается один раз при добавлении слоя в стиль карты. В этот момент доступны:

  • экземпляр карты
  • WebGL-контекст, уже настроенный Mapbox
  • текущая конфигурация матрицы и параметров рендера

Типичный сценарий инициализации включает:

  • компиляцию шейдеров
  • создание буферов вершин (VBO)
  • загрузку текстур
  • подготовку uniform-переменных

Важно учитывать, что контекст уже управляется системой Mapbox, поэтому изменение глобальных состояний WebGL (например, отключение depth test или изменение blend mode) должно выполняться аккуратно и восстанавливаться после использования.


Основной цикл render

Метод render(gl, matrix) вызывается на каждом кадре при необходимости перерисовки слоя. Входные данные:

  • gl — активный WebGLRenderingContext
  • matrix — комбинированная матрица проекции и камеры

Матрица используется для преобразования координат из географического пространства в clip space. Обычно она умножается на позицию вершины в вершинном шейдере:

gl_Position = u_matrix * vec4(a_position, 1.0);

Слой должен самостоятельно управлять:

  • привязкой буферов (bindBuffer)
  • активацией шейдерной программы (useProgram)
  • установкой uniform-переменных
  • вызовом drawArrays или drawElements

Работа с координатной системой

Внутренняя система координат Mapbox основана на проекции Mercator. В кастомных слоях не используется прямое преобразование широты и долготы в WebGL. Вместо этого передаются уже преобразованные координаты в пространстве tile/mercator.

Матрица matrix, передаваемая в render, учитывает:

  • масштаб
  • поворот карты
  • наклон (pitch)
  • смещение центра

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


Управление состоянием WebGL

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

Типичные ошибки:

  • изменение blendFunc без восстановления
  • оставление активированного depthMask(false)
  • утечка привязанных буферов
  • конфликт активных шейдерных программ

Рекомендуется сохранять состояние перед изменениями:

const prevProgram = gl.getParameter(gl.CURRENT_PROGRAM);
const prevArrayBuffer = gl.getParameter(gl.ARRAY_BUFFER_BINDING);

После завершения рендера состояние возвращается к исходному.


Шейдерная модель

Кастомные слои почти всегда используют собственные GLSL-шейдеры.

Вершинный шейдер

Основная задача — преобразование координат:

attribute vec3 a_position;
uniform mat4 u_matrix;

void main() {
    gl_Position = u_matrix * vec4(a_position, 1.0);
}

Фрагментный шейдер

Отвечает за цвет и визуальные эффекты:

precision mediump float;

uniform vec4 u_color;

void main() {
    gl_FragColor = u_color;
}

В более сложных случаях добавляются:

  • нормали и освещение (для 3D)
  • текстурирование
  • процедурные эффекты
  • анимация через uniform time

2D и 3D режимы рендеринга

Параметр renderingMode определяет поведение слоя в пайплайне.

2D режим

  • интеграция с плоской картой
  • отсутствие глубинного буфера
  • упрощённая композиция

Используется для:

  • тепловых карт
  • точечных визуализаций
  • геометрических оверлеев

3D режим

  • участие в depth testing
  • корректное взаимодействие с 3D-террейном
  • поддержка наклона камеры

Используется для:

  • объёмных объектов
  • экструдированных данных
  • WebGL-моделей

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

Кастомные слои оказывают прямое влияние на FPS, так как выполняются в основном рендер-цикле.

Критические факторы:

  • количество draw calls
  • размер передаваемых буферов
  • частота обновления данных
  • использование динамических или статических VBO

Оптимизация часто сводится к:

  • батчингу геометрии
  • минимизации uniform updates
  • кэшированию буферов
  • снижению сложности шейдеров

Особенно важно избегать пересоздания WebGL-ресурсов внутри render, так как это приводит к фрагментации GPU-памяти и деградации производительности.


Взаимодействие с источниками данных

Кастомный слой не обязан быть изолированным. Он может использовать данные из:

  • GeoJSON источников
  • растровых тайлов
  • векторных тайлов Mapbox
  • внешних API

Типичный подход — синхронизация слоя с map.on('data', ...), где обновляются буферы при изменении источников.


Синхронизация с камерой

Для корректного поведения слоя необходимо учитывать параметры камеры:

  • zoom
  • bearing
  • pitch

Эти значения косвенно уже включены в matrix, однако иногда требуется дополнительная логика:

  • фиксирование объектов в экранных координатах
  • параллакс-эффекты
  • масштабирование независимое от zoom

Типовые архитектуры кастомных слоёв

Статический слой

  • один набор буферов
  • рендер без обновлений данных
  • минимальная нагрузка на CPU

Динамический слой

  • обновление данных каждый кадр
  • использование time-based анимаций
  • часто применяются в визуализации потоков

Гибридный слой

  • частичное обновление буферов
  • разделение статической и динамической геометрии
  • оптимальный баланс производительности

Интеграция с UI и событиями карты

Кастомные слои тесно связаны с событиями карты:

  • изменение масштаба (zoom)
  • перемещение (move)
  • вращение (rotate)
  • загрузка стиля (load)

Слой может реагировать на эти события и пересчитывать внутреннее состояние. При этом рендер остаётся синхронным с внутренним циклом Mapbox GL.


Распространённые ограничения

Архитектура накладывает ряд ограничений:

  • отсутствие прямого контроля над swap chain
  • невозможность создания отдельного WebGL context
  • зависимость от порядка отрисовки Mapbox
  • ограниченный контроль над depth buffer в 2D режиме

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