Vertex и fragment шaders

Роль шейдеров в архитектуре WebGL и Deck.gl

В основе визуализации Deck.gl лежит WebGL-пайплайн, в котором графическая обработка данных делится между двумя ключевыми программами: vertex shader и fragment shader. Эти этапы выполняются на GPU и определяют, каким образом геометрия преобразуется в пиксели на экране.

Vertex shader отвечает за обработку вершин геометрии. На этом этапе выполняются преобразования координат, вычисление атрибутов, передача данных в следующий этап пайплайна.

Fragment shader определяет цвет каждого пикселя, который попадает в финальное изображение. Он работает уже с интерполированными данными после растеризации.

В Deck.gl оба типа шейдеров являются частью слоя (Layer) и могут быть переопределены через систему shaders в классе слоя.


Архитектура шейдеров в Deck.gl

Каждый слой Deck.gl содержит собственный набор шейдеров, обычно организованных как:

  • vertexShader
  • fragmentShader
  • modules (GLSL-модули)
  • defines (константы препроцессора)
  • uniforms (передаваемые параметры)

Базовая структура слоя с кастомными шейдерами:

import {ScatterplotLayer} from '@deck.gl/layers';

const layer = new ScatterplotLayer({
  id: 'scatter',
  data: [],
  getPosition: d => d.position,
  getRadius: d => d.radius,
  getFillColor: d => d.color,

  shaders: {
    vertexShader: `
      attribute vec3 positions;
      attribute float sizes;

      varying float vSize;

      void main(void) {
        vSize = sizes;
        gl_Position = project_position_to_clipspace(positions);
        gl_PointSize = sizes;
      }
    `,

    fragmentShader: `
      precision highp float;

      varying float vSize;

      void main(void) {
        gl_FragColor = vec4(1.0, 0.5, 0.2, 1.0);
      }
    `
  }
});

Vertex Shader в контексте Deck.gl

Vertex shader в Deck.gl работает с абстракцией геопространственных данных. В отличие от «чистого» WebGL, здесь координаты часто находятся в системе WGS84 или локальных мировых координатах.

Ключевая особенность Deck.gl — наличие встроенной системы проекций через project_* функции.

Основные задачи vertex shader:

  • преобразование координат из модели в clip space
  • вычисление размеров примитивов
  • передача данных в fragment shader через varyings
  • применение трансформаций (вращение, масштабирование, смещения)

Пример ключевой функции Deck.gl:

gl_Position = project_position_to_clipspace(position);

Эта функция инкапсулирует сложную цепочку преобразований:

  1. долгота/широта → world coordinates
  2. world → viewport
  3. viewport → clip space

Атрибуты и varyings в vertex shader

Vertex shader получает данные через атрибуты:

  • attribute vec3 positions
  • attribute vec4 colors
  • attribute float radius

Deck.gl автоматически связывает эти атрибуты с функциями доступа слоя (getPosition, getColor, и т.д.).

Передача данных в fragment shader осуществляется через varying:

varying vec4 vColor;
varying float vRadius;

Пример:

void main(void) {
  vColor = colors;
  vRadius = radius;

  gl_Position = project_position_to_clipspace(positions);
}

Fragment Shader: финальная стадия рендеринга

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

Основные задачи:

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

Пример базового fragment shader:

precision highp float;

varying vec4 vColor;

void main(void) {
  gl_FragColor = vColor;
}

Работа с прозрачностью и blending

Deck.gl активно использует альфа-канал. Fragment shader часто возвращает цвета с прозрачностью:

gl_FragColor = vec4(color.rgb, 0.5);

При этом итоговое отображение зависит от режима blending WebGL:

  • SRC_ALPHA
  • ONE_MINUS_SRC_ALPHA

Неправильная настройка приводит к артефактам наложения слоёв.


Использование uniform-переменных

Uniforms позволяют передавать данные из JavaScript в шейдер без привязки к вершинам.

Пример:

new ScatterplotLayer({
  id: 'layer',
  data: [],
  uniforms: {
    opacity: 0.7
  }
});

GLSL:

uniform float opacity;

void main(void) {
  gl_FragColor = vec4(1.0, 0.0, 0.0, opacity);
}

Uniforms часто используются для:

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

GLSL-модули Deck.gl

Deck.gl предоставляет модульную систему GLSL:

  • project32
  • picking
  • lighting
  • fp64

Подключение модулей:

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

const layer = new ScatterplotLayer({
  data: [],
  coordinateSystem: COORDINATE_SYSTEM.LNGLAT
});

В шейдере появляются дополнительные функции:

  • project_position
  • project_normal
  • getPickingColor

Precision и особенности WebGL

Fragment shader почти всегда начинается с определения precision:

precision highp float;

В WebGL ES ограничения точности влияют на:

  • дальние координаты
  • плавность градиентов
  • вычисления освещения

Deck.gl использует high precision для минимизации ошибок в геопространственных данных.


Взаимодействие vertex и fragment shader через varyings

Связь между стадиями строго типизирована:

Vertex shader:

varying vec3 vPosition;

Fragment shader:

varying vec3 vPosition;

Передача происходит с интерполяцией, что означает:

  • значения смешиваются между вершинами
  • влияет на градиенты и плавность
  • может создавать артефакты при резких переходах

Динамическая генерация шейдеров в Deck.gl

Deck.gl позволяет модифицировать шейдеры на лету через Jav * aScript:

const layer = new ScatterplotLayer({
  shaders: {
    inject: {
      'vs:#decl': `
        varying float vDebug;
      `,
      'vs:#main-end': `
        vDebug = positions.x;
      `,
      'fs:#decl': `
        varying float vDebug;
      `,
      'fs:#main-start': `
        if (vDebug > 0.5) {
          gl_FragColor.rgb *= 2.0;
        }
      `
    }
  }
});

Этот механизм основан на инъекциях в predefined точки шейдера.


Координатные преобразования в vertex shader

Deck.gl скрывает сложность работы с географическими координатами.

Типичный pipeline:

  • input: longitude/latitude
  • transform: projection (Web Mercator или other)
  • output: clip space

В GLSL:

vec4 projected = project_position(vec4(position, 1.0));
gl_Position = projected;

Обработка пиксельных эффектов во fragment shader

Fragment shader используется для реализации:

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

Пример радиального градиента:

precision highp float;

varying vec2 uv;

void main(void) {
  float dist = length(uv);
  float alpha = 1.0 - smoothstep(0.0, 1.0, dist);

  gl_FragColor = vec4(0.2, 0.6, 1.0, alpha);
}

Производительность шейдеров в Deck.gl

Критические факторы производительности:

  • количество вершин
  • сложность fragment shader (дороже vertex shader)
  • branching (if/else в GLSL)
  • использование текстур

Fragment shader является наиболее дорогой частью pipeline, так как выполняется на каждый пиксель.


Оптимизация вычислений в vertex shader

Часто вычисления переносятся из fragment shader в vertex shader:

  • нормализация данных
  • предварительные расчёты цвета
  • вычисление флагов состояния

Причина — vertex shader выполняется реже (по количеству вершин, а не пикселей).


Итоговая модель взаимодействия шейдеров в Deck.gl

Пайплайн можно представить как последовательность:

  1. JavaScript слой Deck.gl
  2. атрибуты → vertex shader
  3. трансформации координат
  4. rasterization
  5. fragment shader
  6. blending и вывод изображения