Тетрадь № 1 · фронтенд → GPU

Дневник о WebGL

Как рисовать прямо на видеокарте — для тех, кто знает React, но ни разу не писал шейдер.

Владелец:
фронтендер, которому стало любопытно

№1

Что такое WebGL

WebGL — JavaScript API для рисования на видеокарте.

Вы передаёте GPU вершины и две программы — шейдеры. GPU собирает из вершин треугольники и считает цвет каждого пикселя параллельно.

CPU

Несколько мощных ядер: сложная логика, задачи по очереди.

GPU

Тысячи простых ядер: одна программа для каждого пикселя одновременно.

Это не 3D-движок: сцен, камер и света нет. Только точки, линии, треугольники и шейдеры.

ЧтоКак рисуетКогда брать
CSS 3Dтрансформирует DOM-элементыкарточки, флипы, параллакс
Canvas 2D2D-фигуры по командамграфики, простые игры
WebGLтреугольники + шейдеры на GPU3D, тысячи объектов, эффекты
WebGPUновый GPU API: рендер и вычисленияновые проекты, вычисления на GPU

№2

Как это устроено

Конвейер: вершины → пиксели.

Вершинный шейдер считает положение каждой вершины. GPU находит пиксели внутри треугольника и для каждого запускает фрагментный шейдер — он возвращает цвет.

  1. Вершинымассив чисел в буфере
  2. Vertex shaderраз на вершину → позиция
  3. Растеризацияпиксели внутри треугольника
  4. Fragment shaderраз на пиксель → цвет
  5. Экранили текстура
обведено красным — то, что пишете вы; остальное делает GPU
attribute
данные каждой вершины (позиция, цвет) — как пропсы
uniform
одно значение на весь вызов (время, матрица) — как контекст
varying
из вершинного шейдера во фрагментный, с интерполяцией
texture
изображение или массив данных, читается по координатам

№3

Первый треугольник

Минимальная программа — пять шагов.

  1. 1

    Контекст — все вызовы WebGL — методы этого объекта

    canvas.getContext("webgl2")
  2. 2

    Шейдеры — исходник GLSL передаётся строкой

    gl.shaderSource(shader, source)
  3. 3

    Программа — ошибки компиляции надо запрашивать явно

    gl.linkProgram(program)
  4. 4

    Вершины — копируются в память GPU

    gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW)
  5. 5

    Отрисовка — три вершины — один треугольник

    gl.drawArrays(gl.TRIANGLES, 0, 3)

№4

Мой первый шейдер

Фрагментный шейдер: (координата, время) → цвет.

Он выполняется для каждого пикселя независимо. Здесь цвет — косинус от координаты и времени, поэтому градиент движется.

first-shader.frag
#version 300 es
precision highp float;

uniform vec2 u_resolution;
uniform float u_time;

out vec4 outColor;

// Выполняется для каждого пикселя: координата + время → цвет.
void main() {
  vec2 uv = gl_FragCoord.xy / u_resolution;
  vec3 color = 0.5 + 0.5 * cos(u_time + uv.xyx + vec3(0.0, 2.0, 4.0));
  outColor = vec4(color, 1.0);
}

№5

Миллион точек

Миллион точек на графике — без тормозов.

В SVG и Canvas 2D каждая точка — отдельная команда от JavaScript, и так на каждом кадре. В WebGL точки один раз загружаются в видеопамять и рисуются одним вызовом, а зум и сдвиг — два uniform: позиции пересчитывает вершинный шейдер.

scatter.vert
#version 300 es

in vec2 a_position;  // точка данных, координаты −1..1
in float a_group;    // сегмент: 0..3

uniform vec2 u_center;     // центр вида в координатах данных
uniform vec2 u_scale;      // зум с поправкой на пропорции окна
uniform float u_pointSize; // в пикселях буфера
uniform vec3 u_palette[4];

out vec3 v_color;

// Зум и сдвиг — два uniform: миллион позиций пересчитывает GPU,
// данные в видеопамяти не меняются.
void main() {
  gl_Position = vec4((a_position - u_center) * u_scale, 0.0, 1.0);
  gl_PointSize = u_pointSize;
  v_color = u_palette[int(a_group)];
}

→ колесо — зум к курсору, перетаскивание — сдвиг, двойной клик — сброс

№6

Шейдер поверх страницы

WebGL не видит DOM.

Шейдеру доступны только буферы и текстуры. Экспериментальный HTML-in-Canvas отрисовывает HTML внутри canvas в текстуру — с настоящими шрифтами, ховерами и выделением.

Поэтому вся страница здесь — внутри одного canvas, а окна — часть той же сцены: шейдер окна получает всё, что под ним.

r → r · (1 − k + k·r²)
линза: в центре лупа, у края — плоское стекло
  • Стекло — каждый осколок по-своему сдвигает и поворачивает изображение
  • Рябь — смещение по наклону волновой поверхности
  • Огонь — фрактальный шум, движущийся вверх, и цветовая шкала

→ откройте окна и поводите ими по странице и друг над другом

№7

Шейдеры из Winamp

Взял шейдеры из Winamp и встроил их сюда.

№8

Грабли

Ошибки WebGL не бросают исключений.

  • Один пульт на всех

    Всё состояние gl глобальное. Забыл переключить буфер — рисуешь не тем.

  • Ошибки молчат

    Шейдер не собрался — исключения нет, только чёрный экран. Проверяйте getShaderInfoLog.

  • Вызовы дороже вершин

    Один draw call на миллион треугольников быстрее тысячи мелких вызовов.

  • Мыло на ретине

    Размер буфера canvas = CSS-размер × devicePixelRatio.

  • Потеря контекста

    При сбое драйвера ресурсы GPU пропадают. Обрабатывайте webglcontextlost.

№9

Брать ли библиотеку

Сырой WebGL — как DOM API. Three.js — как React.

  • Three.jsсцена, камера, свет, модели — стандарт де-факто
  • React Three FiberThree.js как JSX-компоненты
  • Babylon.jsдвижок «с батарейками»: физика, редактор, WebGPU
  • PixiJSбыстрый 2D: спрайты и фильтры
  • OGL / twglтонкие помощники — когда нужен почти сырой WebGL

→ один полноэкранный шейдер — сырой WebGL; появились камера и модели — библиотека

№10

Что почитать