Написание шейдеров

Визуализация в deck.gl опирается на низкоуровневую графическую модель WebGL, где основная логика рендеринга выполняется на GPU через шейдеры. Шейдеры представляют собой небольшие программы, написанные на GLSL (OpenGL Shading Language), выполняемые параллельно на графическом процессоре.

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

Шейдеры делятся на два основных типа:

Vertex Shader

  • отвечает за трансформацию вершин
  • переводит координаты из модели в clip space
  • передаёт данные в fragment shader

Fragment Shader

  • определяет цвет пикселя
  • отвечает за освещение, текстуры, визуальные эффекты

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

Внутри deck.gl шейдеры не пишутся «с нуля» для каждого слоя. Используется модульная система:

  • базовые GLSL-модули (геометрия, математика, цвета)
  • шейдерные хелперы (project, coordinate systems, fp64)
  • пользовательские вставки (custom shader hooks)

Шейдер слоя собирается из нескольких источников:

  • vertexShader
  • fragmentShader
  • modules
  • defines
  • injects

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


Базовая структура кастомного слоя

Кастомные шейдеры обычно подключаются через Layer или CompositeLayer.

Ключевые элементы:

  • attribute — данные вершин (позиции, цвета, индексы)
  • uniform — глобальные параметры (время, масштаб, камера)
  • varying — передача данных между vertex и fragment shader

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

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

class CustomLayer extends Layer {
  getShaders() {
    return {
      vertexShader: VERTEX_SHADER,
      fragmentShader: FRAGMENT_SHADER,
      modules: ['project32', 'picking']
    };
  }
}

Атрибуты и передача данных в шейдер

В deck.gl данные проходят через AttributeManager. Он автоматически синхронизирует JavaScript-массивы с GPU-буферами.

Типичные атрибуты:

  • positions — координаты вершин
  • colors — RGBA
  • indices — индексация геометрии
  • sizes — размеры объектов

В GLSL это выглядит как:

attribute vec3 positions;
attribute vec4 colors;

varying vec4 vColor;

Связь между CPU и GPU полностью управляется фреймворком, что снижает необходимость ручного управления буферами WebGL.


Модель трансформаций координат

Одна из ключевых задач шейдеров — преобразование координат.

В deck.gl используется многоступенчатая система:

  1. Model space (локальные координаты объекта)
  2. World space (глобальная сцена)
  3. View space (камера)
  4. Clip space (проекция)

Шейдерная функция проектирования часто выглядит так:

vec4 position_world = modelMatrix * vec4(positions, 1.0);
vec4 position_clip = project_common_position_to_clipspace(position_world);

Модуль project32 или project64 обеспечивает корректную работу с большими координатами (например, геоданные).


Работа с цветами и визуальными эффектами

Fragment shader в deck.gl часто отвечает за визуальные эффекты:

  • интерполяция цвета
  • градиенты
  • прозрачность
  • псевдо-3D эффекты

Пример:

precision highp float;

varying vec4 vColor;

void main() {
  gl_FragColor = vColor;
}

Расширенные эффекты могут учитывать:

  • расстояние до камеры
  • глубину сцены
  • интенсивность света
  • выбор объекта (picking)

Picking и интерактивность

deck.gl включает встроенную систему выбора объектов (picking). Она реализуется через специальные шейдерные проходы.

Принцип:

  • каждый объект получает уникальный picking color
  • при клике рендерится offscreen buffer
  • цвет декодируется в ID объекта

В шейдере:

varying vec3 pickingColor;

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

Это позволяет реализовать:

  • hover эффекты
  • selection
  • tooltip-интерактивность

Использование модулей GLSL

deck.gl использует систему модулей для повторного использования GLSL-кода.

Основные модули:

  • project32 — 32-битная проекция
  • project64 — высокоточная проекция
  • picking — логика выбора объектов
  • lighting — освещение
  • math — вспомогательные функции

Модули подключаются декларативно:

modules: ['project32', 'picking']

И автоматически внедряются в шейдерный код.


Инъекции (shader injections)

Одной из мощных особенностей является система inject.

Она позволяет встраивать код в заранее определённые точки шейдера:

  • vs:#decl — объявления переменных
  • vs:#main-start — начало vertex shader
  • fs:#main-end — конец fragment shader

Пример:

injects: {
  'vs:#main-start': `
    vColor.rgb *= 0.5;
  `
}

Это позволяет модифицировать поведение слоя без переписывания всей шейдерной программы.


Обработка больших координат и fp64

При работе с геоданными возникает проблема точности float32.

deck.gl решает её через:

  • двойную точность (fp64)
  • разложение координат на high/low части

Пример GLSL:

vec2 highLowSum(vec2 a, vec2 b);

Модуль project64 позволяет сохранять точность при масштабах планеты.


Оптимизация шейдеров

Производительность шейдеров зависит от:

  • количества атрибутов
  • ветвлений (if/else)
  • precision qualifiers
  • количества varying-переменных

Практики оптимизации:

  • минимизация условных операторов
  • использование step, smoothstep
  • предвычисление данных на CPU
  • уменьшение передачи данных через varying

Связь шейдеров с системой слоёв

Каждый слой deck.gl — это:

  • JavaScript-логика
  • геометрия
  • атрибуты
  • шейдеры

Шейдеры не существуют отдельно — они часть декларативной модели слоя.

Типичный цикл:

  1. обновление props
  2. пересборка атрибутов
  3. компиляция шейдеров (при необходимости)
  4. рендеринг WebGL

Комбинирование пользовательских и встроенных эффектов

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

  • встроенные модули
  • кастомные GLSL функции
  • динамические uniforms

Пример:

uniform float opacity;

void main() {
  gl_FragColor = vec4(vColor.rgb, vColor.a * opacity);
}

Это обеспечивает гибкость при сохранении архитектурной целостности.


Управление состоянием через uniforms

Uniform-переменные позволяют передавать динамические данные:

  • время анимации
  • масштаб карты
  • параметры взаимодействия

Пример:

uniform float time;

void main() {
  float pulse = sin(time);
}

Uniform обновляются на каждом кадре без пересборки шейдера.


Роль шейдеров в визуальной выразительности deck.gl

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

  • плотность данных через цвет
  • пространственные зависимости
  • интерактивные эффекты
  • визуализацию потоков и кластеров

Гибкость GLSL в deck.gl позволяет строить сложные системы визуализации, где GPU становится основным вычислительным слоем, а JavaScript — управляющим уровнем.