Работа с WebGL напрямую

В основе WebGL-подсистемы лежит принцип аппаратного ускорения через GPU, при котором большая часть вычислений перенесена из JavaScript в графический конвейер. В отличие от Canvas2D-рендера, где каждый объект отрисовывается последовательно на CPU, WebGL-слой оперирует буферами данных, шейдерами и состоянием контекста рендеринга.

Внутренняя архитектура OpenLayers WebGL можно условно разделить на три уровня:

  • Уровень источников данных (Sources) — предоставляет геометрию и атрибуты
  • Уровень WebGL-слоёв (WebGLLayers) — управляет привязкой данных к GPU
  • Уровень шейдеров (Shaders) — выполняет отрисовку на GPU

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


WebGL-конвейер и модель рендеринга

WebGL-рендер в OpenLayers основан на стандартном графическом конвейере:

  1. Подготовка геометрии
  2. Загрузка данных в буферы GPU
  3. Выполнение вершинного шейдера
  4. Растеризация примитивов
  5. Выполнение фрагментного шейдера
  6. Вывод в framebuffer

На уровне OpenLayers этот процесс абстрагирован, но при работе с WebGL напрямую важно понимать соответствие между JavaScript-данными и GPU-структурами.


Контекст WebGL и управление состоянием

WebGL-контекст создаётся один раз на канвасе карты и переиспользуется всеми WebGL-слоями. Контекст содержит:

  • буферы вершин (Vertex Buffers)
  • буферы индексов (Index Buffers)
  • uniform-переменные
  • состояния смешивания (blending)
  • матрицы трансформаций

Смена состояния WebGL — дорогая операция, поэтому OpenLayers минимизирует переключения программ шейдеров и буферов.


WebGLTile и растровый рендеринг

Для растровых данных используется слой WebGLTile, который агрегирует тайлы и отправляет их в GPU как текстуры.

Основные особенности:

  • каждый тайл = отдельная текстура
  • используется texture atlas при оптимизации
  • поддерживается динамическое перекодирование значений пикселей

Пример базовой конфигурации:

import WebGLTileLayer from 'ol/layer/WebGLTile';
import XYZ from 'ol/source/XYZ';

const layer = new WebGLTileLayer({
  source: new XYZ({
    url: 'https://tile-server/{z}/{x}/{y}.png'
  }),
  style: {
    color: [
      'interpolate',
      ['linear'],
      ['band', 1],
      0, 'blue',
      255, 'red'
    ]
  }
});

В данном случае выражение style компилируется в GLSL-эквивалент, который выполняется на GPU для каждого пикселя.


WebGLPoints и работа с векторными данными

Для точечных данных используется WebGLPoints, оптимизированный для миллионов объектов.

Особенность модели заключается в том, что каждая точка представляется как запись в буфере:

  • координаты (x, y)
  • атрибуты (цвет, размер, категория)
  • дополнительные пользовательские поля

Пример:

import WebGLPointsLayer from 'ol/layer/WebGLPoints';
import VectorSource from 'ol/source/Vector';

const layer = new WebGLPointsLayer({
  source: new VectorSource({
    features: featuresArray
  }),
  style: {
    symbol: {
      symbolType: 'circle',
      size: 8,
      color: [
        'case',
        ['>', ['get', 'value'], 50],
        'red',
        'green'
      ]
    }
  }
});

Вся логика условий трансформируется в GPU-выражения без участия CPU на этапе рендеринга.


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

WebGL-слой в OpenLayers использует автоматически генерируемые GLSL-шейдеры. Они состоят из двух основных частей:

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

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

  • перевод из EPSG:3857 в экранные координаты
  • применение матриц трансформации карты
  • передача атрибутов во фрагментный шейдер

Упрощённый пример:

attribute vec2 a_position;
uniform mat4 u_projectionMatrix;

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

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

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

precision mediump float;

uniform vec4 u_color;

void main() {
  gl_FragColor = u_color;
}

OpenLayers расширяет эту модель через декларативные style-выражения.


Стиль как компилируемая программа

Одной из ключевых особенностей WebGL-слоя является представление стиля как вычисляемого выражения, а не как статического объекта.

Пример выражений:

  • ['get', 'property']
  • ['+', a, b]
  • ['interpolate', ...]
  • ['case', condition, value1, value2]

Эти выражения транслируются в GLSL-код, который выполняется на GPU.

Пример цветовой логики:

style: {
  color: [
    'interpolate',
    ['linear'],
    ['get', 'temperature'],
    -20, 'blue',
    0, 'white',
    30, 'red'
  ]
}

На уровне GPU это превращается в серию условных ветвлений и интерполяций.


Буферизация данных и производительность

WebGL-рендер требует строгой работы с памятью GPU:

Основные принципы:

  • минимизация пересоздания буферов
  • батчинг геометрии
  • использование TypedArray
  • переиспользование атрибутов

Каждый слой стремится:

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

Динамическое обновление данных

При изменении данных происходит частичная пересборка буферов:

  • добавление новых features → append buffer
  • изменение свойств → update attribute buffer
  • удаление → reindex buffer

Важно, что GPU не получает JavaScript-объекты, только бинарные массивы.


Работа с координатами и проекциями

В WebGL-слое координаты заранее проецируются в пространство карты.

Типичный pipeline:

  1. исходные координаты (lon/lat)
  2. проекция EPSG:3857
  3. перевод в tile-space
  4. нормализация под clip-space

Это позволяет выполнять финальную трансформацию исключительно в vertex shader.


Ограничения WebGL-рендера

Несмотря на высокую производительность, модель имеет ограничения:

  • ограниченное число uniform-переменных
  • сложность отладки GLSL
  • отсутствие полноценной динамической памяти
  • зависимость от возможностей GPU
  • различия между WebGL1 и WebGL2

Особенно критичным является лимит на сложность выражений стиля, так как они компилируются в шейдер.


Отладка шейдеров

При работе с WebGL-слоем часто требуется анализ сгенерированного GLSL:

  • проверка компиляции shader program
  • анализ fragment shader output
  • контроль precision (lowp, mediump, highp)
  • визуализация intermediate values

Типичная проблема — переполнение precision, приводящее к артефактам цвета или геометрии.


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

OpenLayers позволяет расширять WebGL-пайплайн через кастомные стили и фильтры.

Пример концептуального фильтра:

  • вход: текстура или буфер атрибутов
  • обработка: GLSL функция
  • выход: модифицированный цвет/геометрия

Фрагментная логика может включать:

  • шумовые функции
  • градиентные карты
  • пороговые фильтры
  • псевдо-3D эффекты

WebGL и взаимодействие с событиями карты

WebGL-рендер не заменяет событийную модель. Hit detection реализуется через:

  • offscreen picking buffer
  • encoding feature id в цвет
  • обратное считывание пикселя (readPixels)

Это позволяет определять объект под курсором без CPU-итерации по features.


Производительность при больших объёмах данных

При работе с миллионами объектов ключевыми становятся:

  • spatial indexing (quadtrees)
  • tile-based partitioning
  • GPU instancing
  • минимизация state changes

Наиболее эффективная модель — сочетание WebGLPoints и тайловой сегментации данных.


Взаимодействие WebGL и Canvas2D слоёв

В одной карте могут сосуществовать разные рендереры:

  • WebGL слои — высокопроизводительная отрисовка
  • Canvas2D слои — сложная символика и legacy стили
  • DOM overlays — интерактивные элементы

Порядок компоновки контролируется z-index и internal render order.


Поток выполнения кадра

Каждый кадр WebGL-рендера включает:

  1. сбор изменений состояния карты
  2. пересчёт матрицы камеры
  3. обновление буферов (если нужно)
  4. запуск shader program
  5. отрисовка всех WebGL слоёв
  6. композитинг результата

Ключевая оптимизация — пропуск шагов при отсутствии изменений состояния.