Знакомство фронтендера с WebGL: почему WebGL?
Tags: webgl, javascript
Published: 2021-07-06 22:31:11

Мне и моему коллеге дизайнеру была поставлена задача разработать новую версию сайта-визитки компании. Коллега полгода учился работать с 3D-редакторами (в нерабочее время на Maxon Cinema 4D), поэтому он хотел использовать свои новые навыки при создании новой версии сайта. Его идея заключалась в том, что на каждой странице на первом экране будет крутиться какая-нибудь непонятная фигура с каким-нибудь красивым текстом. Выглядеть это должно было примерно так:

![Как должно выглядить](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oq9veban9vt802ju891m.jpg)

Реальных моделей пока не было, поэтому на первое время мне предоставили модель яблока.

Основная проблема заключалась в том, что у меня не было опыта работы с 3D, я очень плохо знал математику и геометрию, а также у меня никогда не было опыта работы с WebGL. В общем, в свои силы я верил слабо. По итогу с задачей я справился, и я хочу рассказать об этом опыте в небольшом цикле статей.

##Почему вообще WebGL?

Слово WebGL у меня ассоциируется с 3D. Я считаю, что больше нет нормальных способов отрендерить что-то в 3D без WebGL. Помимо того, что слово WebGL само по себе звучит очень круто, нашлись и другие причины выбрать эту технологию:

- Мы хотели интерактивности. Например, чтобы моделька реагировала на движение курсора мыши по двум осям. С помощью видео такое сделать невозможно.
- Адаптивность. Мы могли рисовать одну модельку под любые экраны и устройства (поддержка WebGL была подавляющей). Нам не нужно рендерить заранее кучу видюх под разные экраны и переживать о том, что на каком-то экране появится пикселизация.
- Проблемы с iOS. На каждой версии были свои приколы с фоновыми видео на телефоне. На одной из версий видео вообще могло не запуститься из-за политик Apple, а иногда нужно было проводить специальный обряд, чтобы фоновое видео заработало. Таких проблем с WebGL пока нет и, надеюсь, не будет.
- IE11 тоже поддерживает WebGL. Да, есть [нюанс](https://caniuse.com/#feat=webgl), но он даже не стоит внимания. 
- В SVG невозможно сделать полноценный 3D, только псевдо. Также браузеры плохо себя чувствуют, когда SVG элементов в DOM больше 10 000. Это показали мои попытки отрендерить модельку в SVG.

Я думаю, первого пункта хватит, чтоб принять решение в пользу webGL.

##С чего начал ?

Модель яблока была в формате [OBJ](https://en.wikipedia.org/wiki/Wavefront_.obj_file), другие форматы я решил не рассматривать. Это текстовый формат и это придавало мне какой-то уверенности в том, что решений в интернете для него должно быть много.  

Я знал про существования библиотек `three.js`, `Babylon.js` и `PixiJS` (это вообще 2D-рендер). Вес 3D-библиотек огромный, как бы они не были сжаты. Не хотелось пускать таких монстров к себе на сайт, у меня и так был 100кб react-dom, куда еще больше? Да и чтоб разобраться в 3D-библиотеках, все равно нужно было иметь какие-то представления о 3D-графике. 
Я гуглил «webgl obj model render» и находил только онлайн-просмотрщики или какие-то узко специфичные решения, которые запустить не смог. 
Также искал демки на CodePen, но я не нашел ничего подходящего. А если что-то находил, не мог понять, что вообще происходит и что мне делать.
Понял, что нужно начать с основ, без базовых знаний webGL задачу не выполнить.

##Погружение в WebGL

Не знаю как так получилось, но в интернете на глаза ресурсы по WebGL мне не попадались, поэтому я пошел [Телеграм-чат @webgl_ru](https://t.me/webgl_ru) (найти его было просто) и спросил: 
— как начать в WebGL фронту?

Похоже, что в чат постоянно заходили подобные мне ребята с аналогичными вопросами, поэтому у ребят из чата уже был заготовлен [список ресурсов](https://telegra.ph/Poleznye-resursy-11-08), который они мне и скинули. Впоследствии участники данного чата мне еще не раз помогли, за что им огромное спасибо.

Из списка, который мне скинули, я выбрал ресурс [WebGL Fundamentals](https://webglfundamentals.org/), у которого было довольно говорящее название, а также перевод на русский. Обычно я не вижу ничего ужасного в английской документации, но WebGL казался мне чем-то инородным и страшным, а также состоящим из подходов, которые доселе не были мне знакомы. То, что WebGL рендерит это всё через Canvas — это было единственное, что я знал об этой технологии.

###Что вообще такое WebGL

Первое, что бросается в глаза, — это необычный API. Привычное нам браузерное API — это просто вызов методов у каких-то встроенных объектов/классов, тогда как апи WebGL это как будто вы программно настраиваете repl node.js, а потом прокидываете в этот repl строки javascript кода и получаете какой-то результат из этого. 
В случаи webgl вы настраиваете внутри браузера обрезанную версию [OpenGL](https://ru.wikipedia.org/wiki/OpenGL)(библиотека которая заставляет наши видеокарты что-то рисовать) и прокидываете в нее код на языке [GLSL](https://ru.wikipedia.org/wiki/OpenGL_Shading_Language). GLSL  обрезанный C подобный язык, погрузится несложно. Как будто пишешь на es3 javascript.

Если обобщить, то работа на webgl выглядит так:
- Получаете доступ к webgl (по сути к openGL, но версия обрезанная и потому этому называется webgl).
- Выставляете какие-нить специальные флаги, которые могут менять работу рендера.
- Пишите программу на GLSL. Сама программа это просто функции которые принимает данные и выплевывает какой-то результат. Данные это точки в координатной системе, а также цвет этой точки. Тип как указать положение div 1х1 через absolute и поставить его центр на высоте 300 пикселей, покрасив в красный.
- В случае 3D нужно еще указать глубину точки, ну это пока не важно.
- Пространство в webgl имеет координаты от -1 до 1, потому, нам нужно преобразовывать координаты разных фигур в координаты от -1 до 1, если они за пределами. ВОТ ОНА МАТЕМАТИКА.
- Пропускаете через апи координаты, цвета и другие параметры.
- PROFIT! Мы получаем 2D/3D картинку.

###Шейдеры
Выше я говорил про программы на GLSL, программа всегда состоит из 2 шейдеров. Шейдер есть функция.
Каждая программа состоит из вершинного шейдера (Vertex Shader) и фрагментного шейдера (Fragment Shader).  
Вершинный шейдер - позволяет разметить пространство, а фрагментный - закрашивает это пространство. Так работают видеокарты. 
Сначала им нужно выставить точки в пространстве, потом соединить эти точки невидимыми линиями, а потом закрасить каждый пиксель внутри получившийся фигуры.
Если приводить пример из жизни, у вас есть стенка 1м х 1м и есть художник по имени Видеокарта. Вот вы и ему говорите:
> — Поставь мне точку на 30 сантиметров от верха и 50 см слева, потом точку в 50х50, потом в 70х80, соедини мне это линиями и закрась получившееся пространство красным.

Сами шейдеры выглядит так:  
Вершинный шейдер (Vertex Shader)
```glsl
// атрибут, который будет получать данные которые мы передали. атрибут === переменная/пропс
attribute vec4 a_position;
 
// все шейдеры имеют функцию main
// стандартная тема для компилируемых языков
void main() {
 
  // gl_Position - специальная переменная вершинного шейдера,
  // которая отвечает за установку положения
  gl_Position = a_position;
}
```
Фрагментный шейдер (Fragment Shader)
```glsl
// фрагментные шейдеры не имеют точности по умолчанию, поэтому нам необходимо её
// указать. mediump подойдет для большинства случаев. Он означает "средняя точность"
precision mediump float;
 
void main() {
  // gl_FragColor - специальная переменная фрагментного шейдера.
  // Она отвечает за установку цвета.
  gl_FragColor = vec4(1, 0, 0, 1); // вернёт красный
}
```

В вершинном шейдере вы увидели `attribute`. Шейдере есть несколько типов переменных (дальше копипаста с webglfundamentals.org):

>Атрибуты и буферы  
>Буферы - это массивы бинарных данных, загруженных в графический процессор. Обычно буферы содержат вещи вроде положений вершин, нормалей, координат текстур, цветов вершин и т.д., хотя вы вольны положить в них что угодно.  
>Атрибуты определяют, каким образом данные из ваших буферов передаются в вершинный шейдер. Например, вы можете поместить положения вершин в буфер как три 32-битных числа с плавающей точкой на одно положение. Вы указываете конкретному атрибуту, откуда брать положения вершин, какой тип данных используется (три 32-битных числа с плавающей точкой), начиная с какого индекса в буфере начинаются положения вершин и какое количество байтов нужно получить от одного положения до следующего.  
>Доступ к буферам не произвольный. Вместо этого вершинный шейдер выполняется заданное количество раз и каждый раз, когда он выполняется, выбирается следующее значение каждого из указанных буферов и назначается атрибуту.

>Uniform-переменные  
>Uniform-переменные - это глобальные переменные, которые устанавливаются перед выполнением программы шейдера.

>Текстуры  
>Текстуры - это массивы данных, к которым есть произвольный доступ в программе шейдера. Чаще всего в текстуру помещается картинка, но текстура - это просто набор данных и вы можете запросто поместить в неё что-то отличное от набора цветов.

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


Вершинный шейдер выполняется на каждую порцию x,y,z (z может не указываться, если рисуем в 2D) координат. Каждые такие координаты создают вершину. А дальше уже эти вершины объединяются в треугольники (полигоны) и потом фрагментным шейдером эти треугольники закрашиваются.  

**Вы спросите, почему именно треугольники?**

В процессе обучения я на это не обращал внимания, но когда начал предпринимать попытки нарисовать модельку тоже удивился, но оказывается любую фигуру можно нарисовать через ТРЕУГОЛЬНИКИ (ПОЛИГОНЫ) и потому, добавлять другие фигуры бессмысленно.  
**Треугольник есть абсолют**.  
В webgl можно рисовать только треугольниками, линиями и точками.  
- Если рисовать через линии, то будут только отрисовываться грани между вершинами, но все что внутри фигуры не будет закрашиваться.
- Если рисовать через точки, то будут отрисовываться только точки! Удивительно!


###Матрицы
Также я узнал про матрицы. Это пришло из математики и для js разраба это выглядит как массив из 9 или 12 чисел (12 для 3D). 
Матрицы решают вопросы того как трансформировать модель (а точней вершины), чтоб поставить модельку в нужное место в пространстве, увеличить или покрутить. 
Матрицы еще позволяют создавать камеры, то есть менять вид обзора и другое. Вы могли с ними встречаться, если работали `transform: matrix(...n)` в css.  
Матрицы один из **фундаментов 2D/3D графики**. Наверно одна из немногих вещей которой можно пользоваться не разбираясь как она работает.  
Достаточно запомнить, что чтоб применить несколько трансформаций нужно просто матрицы друг на друга перемножить и результат передать в шейдер. 
Матрица 3х3 для 2D трансформаций, а 4х4 для 3D трансформаций.  
Хорошие люди уже все за нас написали [gl-matrix](http://glmatrix.net/). Нам нужно вызывать только знакомые на слух название методов и получать нужный результат. 
Более подробно про матрицы можно узнать на [webgl fundumentals](https://webglfundamentals.org/webgl/lessons/ru/webgl-2d-matrices.html).

###Hello world на webgl
Так как все же выглядит hello world код на webgl? Что вообще требуется для того, чтоб это запустить и нарисовать треугольник?
- Нужно получить ссылку на canvas элемент.
- Получить из него webgl контекст, то есть то что нам позволит общаться с webgl и рисовать через него.
- Создать программу из вершинного шейдера и фрагментного шейдера.
- Получить ссылки на переменные из шейдеров.
- Присвоить переменным - данные.
- Запустить функцию drawArrays у webgl.
- Вуаля, мы получили наш треугольник.



И после такого количества кода (по ссылке), мы получаем треугольник.

{% codepen https://codepen.io/dimensi/pen/GRJrgjy %}

Честно говоря, это безумное количества кода ради одного треугольника немного остудило желание, но автор учебника объяснил, что все это можно убрать под хелперы. 
Посмотрев на этот пример, можно понять безумные размеры 3D либ.

Продолжение [здесь](https://dev.to/dimensi/weggl-2-3o0m).