Vertex и fragment shaders

В основе рендеринга Mapbox GL JS лежит WebGL, а ключевым механизмом его работы выступают программируемые шейдеры. Каждый кадр карты формируется через графический конвейер, где геометрия преобразуется и окрашивается с помощью двух основных типов шейдеров — vertex shader и fragment shader. Их взаимодействие определяет, как векторные данные превращаются в пиксели на экране.

Mapbox GL JS использует WebGL 1.0 (и частично WebGL 2.0 в современных версиях окружений), а значит шейдеры пишутся на GLSL ES (OpenGL Shading Language for Embedded Systems). Эти программы выполняются непосредственно на GPU, обеспечивая высокую производительность при отрисовке больших объемов геоданных.


Vertex shader: преобразование геометрии

Vertex shader выполняется для каждой вершины геометрии. В контексте Mapbox GL JS вершины представляют собой точки, из которых формируются линии, полигоны и символы. Основная задача vertex shader — преобразование координат из пространств данных в экранное пространство.

Основные функции vertex shader

  • преобразование координат из tile space в clip space
  • применение матричных преобразований (projection matrix)
  • вычисление параметров, передаваемых в fragment shader
  • подготовка данных для интерполяции

Типичный vertex shader в Mapbox GL JS включает работу с матрицами:

attribute vec2 a_pos;

uniform mat4 u_matrix;
uniform float u_zoom;

varying vec2 v_pos;

void main() {
    vec4 projected = u_matrix * vec4(a_pos, 0.0, 1.0);
    gl_Position = projected;
    v_pos = a_pos;
}

Пространства координат

В процессе работы vertex shader оперирует несколькими системами координат:

  • Tile coordinates — координаты внутри тайла (0–4096 в Mapbox Vector Tiles)
  • Clip space — нормализованное пространство WebGL (-1 до 1)
  • Screen space — экранные координаты после перспективного деления

Mapbox GL JS выполняет большую часть математики трансформации в uniform-матрице u_matrix, которая объединяет:

  • матрицу проекции
  • матрицу камеры
  • трансформацию тайла

Интерполяция данных

Vertex shader передает данные в fragment shader через varying переменные. Эти значения автоматически интерполируются для каждого пикселя внутри примитива.

varying float v_line_distance;
varying vec2 v_texcoord;

Пример использования — вычисление градиента вдоль линии или передача координат для текстурирования символов.


Особенности работы в Mapbox GL JS

В отличие от классических 3D движков, vertex shader в Mapbox GL JS оптимизирован под:

  • 2D и псевдо-3D картографическую проекцию
  • тайловую систему координат
  • динамическое изменение масштаба (zoom-dependent attributes)

Zoom играет ключевую роль: многие атрибуты интерполируются в зависимости от уровня приближения, что позволяет менять толщину линий, размеры символов и другие параметры без пересоздания геометрии.


Fragment shader: вычисление цвета пикселя

Fragment shader выполняется для каждого пикселя, покрываемого примитивом. Его задача — определить итоговый цвет фрагмента, включая прозрачность.

В Mapbox GL JS именно fragment shader отвечает за визуальное оформление слоев: заливки, линии, символы, эффекты и стилизацию данных.

Основные функции fragment shader

  • вычисление цвета пикселя
  • применение текстур
  • работа с градиентами и стилями
  • обработка прозрачности (alpha blending)
  • реализация эффектов (blur, dash, glow)

Базовая структура fragment shader

precision mediump float;

varying vec2 v_pos;

uniform vec4 u_color;

void main() {
    gl_FragColor = u_color;
}

В этом примере каждый пиксель получает одинаковый цвет, заданный через uniform.


Работа с интерполяцией

Fragment shader получает интерполированные значения из vertex shader, что позволяет создавать плавные переходы:

varying float v_opacity;

uniform vec4 u_fill;

void main() {
    gl_FragColor = vec4(u_fill.rgb, u_fill.a * v_opacity);
}

Такой подход используется, например, для плавного изменения прозрачности вдоль линии.


Текстуры и спрайты

В Mapbox GL JS fragment shader активно работает с текстурами, особенно для символов и иконок.

uniform sampler2D u_texture;
varying vec2 v_texcoord;

void main() {
    vec4 color = texture2D(u_texture, v_texcoord);
    gl_FragColor = color;
}

Это основной механизм отрисовки иконок в symbol layers.


Дашированные линии

Одна из характерных задач fragment shader — реализация пунктирных линий без разбиения геометрии:

varying float v_line_distance;
uniform float u_dash_array;

void main() {
    float pattern = mod(v_line_distance, u_dash_array);
    
    if (pattern > u_dash_array * 0.5) {
        discard;
    }

    gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);
}

Использование discard позволяет пропускать пиксели, создавая визуальные разрывы.


Взаимодействие vertex и fragment shaders

Связь между двумя шейдерами осуществляется через varyings. Vertex shader определяет геометрическую основу, fragment shader — визуальную интерпретацию.

Типичный поток данных:

  1. Vertex shader получает вершины из буфера
  2. Применяет матрицу трансформации
  3. Передает интерполируемые значения
  4. Fragment shader вычисляет цвет каждого пикселя

Оптимизация в Mapbox GL JS

Производительность шейдеров критична при рендеринге больших карт. Основные стратегии оптимизации включают:

  • минимизацию вычислений в fragment shader
  • перенос логики в vertex shader
  • использование precomputed attributes
  • уменьшение условных операторов
  • отказ от динамических циклов

Fragment shader особенно чувствителен к сложным операциям, так как выполняется для каждого пикселя экрана.


Zoom-адаптивные вычисления

Mapbox GL JS активно использует зависимость от zoom уровня. Это реализуется через uniform-переменные и интерполяции:

uniform float u_zoom;

float scale = smoothstep(10.0, 15.0, u_zoom);

Такие вычисления позволяют изменять визуальные свойства без пересборки буферов.


Кастомизация шейдеров в стилях

Mapbox GL JS предоставляет возможность модификации шейдеров через custom layers. Это позволяет внедрять собственную GLSL-логику в рендеринг карты.

Пример структуры custom layer:

const customLayer = {
    id: 'custom-layer',
    type: 'custom',
    renderingMode: '2d',
    onAdd(map, gl) {
        // инициализация буферов и шейдеров
    },
    render(gl, matrix) {
        // передача uniform и отрисовка
    }
};

Внутри таких слоев можно использовать собственные vertex и fragment shaders, интегрированные в pipeline Mapbox GL JS.


Роль шейдеров в стиле карты

Шейдеры напрямую определяют визуальный язык карты:

  • цветовые схемы слоев
  • визуализация плотности данных
  • эффекты высоты и перспективы
  • анимации изменения состояния
  • реакция на масштабирование и поворот

Fragment shader часто становится местом реализации визуальных эффектов, тогда как vertex shader отвечает за геометрическую основу и трансформации.


Заключительные технические аспекты архитектуры

Внутренняя модель Mapbox GL JS строится вокруг унифицированного набора шейдеров, где различные типы слоев (fill, line, circle, symbol) используют специализированные вариации vertex и fragment программ.

Эта архитектура обеспечивает:

  • единый рендеринговый pipeline
  • повторное использование GPU ресурсов
  • предсказуемую производительность
  • масштабируемость при росте объема данных