Для рендеринга сложной графики на веб-страницах существует библиотека веб-графики, или сокращенно WebGL. Разработчик интерфейса Дмитрий Васильев рассказал о программировании на GPU с точки зрения верстальщика, о том, что такое WebGL и как мы использовали эту технологию для решения задачи визуализации больших данных о погоде.
— Разрабатываю интерфейсы в екатеринбургском офисе Яндекса.
Я начинал в спортивной группе.
Мы разрабатывали специальные спортивные проекты во время чемпионатов мира по хоккею и футболу, Олимпийских игр, Паралимпийских игр и других крутых мероприятий.
Также я участвовал в разработке специального поисковика, посвященного новой сочинской трассе.
Ссылка со слайда
Кроме того, через полтора часа мы перезапустили сервис «Работа над ошибками».А потом началась работа в Pogoda, где я занимался поддержанием функциональности API, его развитием, написанием инфраструктуры вокруг этого API и написанием привязок узлов для обученных формул машинного обучения.
Дальше началась более интересная работа.
Принимал участие в редизайне наших метеорологических сервисов.
Десктопы, тачки.
Приведя в порядок стандартные прогнозы, мы решили сделать прогноз, которого нет ни у кого.
Этот прогноз представлял собой прогноз движения осадков по территориям.
Существуют специальные метеорадары, обнаруживающие осадки в радиусе 2000 км; они знают его плотность и расстояние до него.
Используя эти данные и машинное обучение для прогнозирования их будущих перемещений, мы сделали такая визуализация на карте.
Можно перемещать вперед и назад.
Ссылка со слайда
Мы посмотрели отзывы людей.Людям это понравилось.
Стали появляться всякие мемы, были прикольные картинки, когда Москву затопило к чертям.
Поскольку формат всем понравился, мы решили пойти дальше и посвятить ветру следующий прогноз.
Уже есть сервисы, которые показывают прогнозы ветра.
Это пара крутых, которые действительно выделяются.
Посмотрев на них, мы поняли, что хотим сделать то же самое — или хотя бы не хуже.
Поэтому мы решили визуализировать частицы, которые плавно движутся по карте в зависимости от скорости ветра, и оставлять за собой какой-то след, чтобы их было видно, можно было увидеть траекторию ветра.
Поскольку мы уже сделали классную карту с осадками с использованием 2D-холста, мы решили сделать то же самое с частицами.
Посоветовавшись с дизайнером, мы поняли, что нам нужно заполнить частицами около 6% экрана, чтобы получился крутой эффект.
Для отрисовки такого количества частиц стандартным подходом наш минимальный тайминг составил 5 мс.
Если мы думаем, что нам все равно нужно перемещать частицы и создавать какую-то красоту вроде рисования хвоста частиц, то можно предположить, что мы выпадем из минимального тайминга в 40 мс, чтобы показать плавную анимацию, чтобы произвести не менее 25 кадров в секунду.
Проблема в том, что здесь каждая частица будет обрабатываться последовательно.
А что, если обрабатывать их параллельно? Явную разницу в работе центрального и графического процессоров продемонстрировали «Разрушители мифов» на одной из конференций.
Выкатили машину с установленным пейнтбольным маркером, задачей которого было нарисовать смайлик одним цветом.
Примерно за 10 секунд он нарисовал эту картинку.
( Ссылка на видео — ок.
ред.)
Потом ребята выкатили байдарку, которая представляет собой ГПУ, и парой вертелов нарисовали Мону Лизу.
Это примерно разница между скоростями вычислений процессора и графического процессора.
Чтобы воспользоваться такими возможностями браузера, была придумана технология WebGL.
Что это? С этим вопросом я пошёл в Интернет. Добавив пару слов с анимацией частиц и ветром, я нашел пару статей.
Ссылки со слайда: первый , второй
Одна из них — демо Владимира Агафонкина, инженера из Mapbox, который сделал именно ветер на WebGL и дал ссылку на блог Криса Уэллонса, в котором рассказывалось о том, как перемещать и хранить состояние частиц на GPU. Берем и копируем.Мы ожидаем такого результата.
Здесь частицы движутся плавно.
Что мы получаем, я не понимаю что.
Пытаемся разобраться в коде.
Совершенствуемся, но снова получаем неудовлетворительный результат. Забираемся еще глубже – вместо ветра получаем дождь.
Хорошо, мы решаем сделать это сами.
Существуют фреймворки для работы с WebGL. Почти все они направлены на работу с 3D-объектами.
Нам не нужны эти 3D-возможности.
Нам нужно только нарисовать частицу и переместить ее.
Поэтому мы решили сделать все вручную.
В настоящее время существует две версии технологии WebGL. Вторая версия, что круто, имеет высокую современную версию языка программирования, на которой программа запускается в графическом адаптере и может осуществлять прямые вычисления, а не только рендеринг.
Но у него плохая совместимость.
Что ж, решаем использовать старый проверенный WebGL 1, имеющий хорошую поддержку, кроме Opera Mini, которая никому не нужна.
WebGL состоит из двух частей.
Это JS, который выполняет состояние программ, работающих на видеокарте.
А есть компоненты, которые работают непосредственно на видеокарте.
Начнем с JS. WebGL — это просто подходящий контекст для элемента холста.
Более того, при получении этого контекста выделяется не просто конкретный объект, выделяются аппаратные ресурсы.
А если мы запустим в браузере что-нибудь красивое на WebGL, а потом решим поиграть в Quake, то вполне возможно, что и эти ресурсы потеряются, и контекст может потеряться, и вся ваша программа сломается.
Поэтому при работе с WebGL тоже нужно слушать потерю контекста и уметь его восстановить.
Вот почему я подчеркнул, что init существует.
Тогда вся работа JS сводится к сборке программ, которые работают на графическом процессоре, отправке их на видеокарту, настройке некоторых параметров и команде «рендеринг».
В WebGL, если вы посмотрите на сам элемент контекста, вы увидите кучу констант. Эти константы означают ссылки на адреса в памяти.
На самом деле они не являются константами во время работы программы.
Потому что если контекст утерян и восстановлен заново, может быть выделен другой пул адресов, и для текущего контекста эти константы будут другими.
Поэтому почти все операции в WebGL на стороне JS выполняются через утилиты.
Никто не хочет заниматься рутинной работой по поиску адресов и прочей хрени.
Перейдем к тому, что выполняется на самой видеокарте — программе, состоящей из двух наборов инструкций, написанных на C-подобном языке GLSL. Эти инструкции называются вершинным шейдером и фрагментным шейдером.
Из их пары создается программа.
Чем отличаются эти шейдеры? Вершинный шейдер задает поверхность, на которой что-то должно быть нарисовано.
После установки примитива, который должен быть нарисован, вызывается фрагментный шейдер, попадающий в этот диапазон.
В коде это выглядит так.
В шейдере есть раздел объявления переменных, которые задаются извне, из JS; определены их тип и название.
А также основная секция, выполняющая необходимый для этой итерации код. В большинстве случаев ожидается, что вершинный шейдер установит переменную gl_Position в некоторую координату в 4D-пространстве.
Это x, y, z и ширина пространства, знать которую на данный момент особо не обязательно.
Фрагментный шейдер ожидает установки цвета определенного пикселя.
В этом примере мы выбираем цвет пикселя из подключенной текстуры.
Чтобы перенести это на JS, вам просто нужно обернуть исходный код шейдера в переменные.
Эти переменные затем преобразуются в шейдеры.
Это контекст WebGL, мы создаём шейдеры из исходников, параллельно создаём программу и с помощью программы привязываем пару шейдеров.
Получаем рабочую программу.
Попутно проверяем, что компиляция шейдеров прошла успешно и программа собралась успешно.
Мы говорим, что вам нужно использовать эту программу, потому что программ для разных значений рендеринга может быть несколько.
Мы настраиваем его и приказываем рисовать.
Получается какая-то картинка.
Мы залезли глубже.
В вершинном шейдере все вычисления выполняются в пространстве от -1 до 1, независимо от размера выходной точки.
Например, пространство от -1 до 1 может занимать весь экран 1920x1080. Чтобы нарисовать треугольник в центре экрана, вам нужно нарисовать поверхность, охватывающую координату 0, 0.
Фрагментный шейдер работает в пространстве от 0 до 1, а цвета здесь производятся четырьмя компонентами: R, G, B, Alpha.
В примере с CSS вы, возможно, встречали подобное цветовое обозначение при использовании процентов.
Чтобы что-то визуализировать, вам нужно указать, какие данные вы хотите визуализировать.
Конкретно для треугольника мы определяем типизированный массив из трех вершин, каждая из которых состоит из трех компонентов: x, y и достаточно.
В данном случае вершинный шейдер выглядит так, будто получает текущую пару точек, координату, а также выводит эту координату на экран.
Здесь как есть, без преобразований, ставим точку на экране.
Фрагментный шейдер может раскрашивать константы, переданные из JS, также без дополнительных вычислений.
Причём, если какие-то переменные во фрагментном шейдере передаются извне или из предыдущего шейдера, то точность должна быть указана.
В этом случае достаточно средней точности, и почти всегда ее достаточно.
Перейдем к JS. Мы присваиваем переменным одни и те же шейдеры и объявляем функцию, которая будет создавать эти шейдеры.
То есть создается шейдер, загружается в него исходник, а затем компилируется.
Делаем два шейдера, вершинный и фрагментный.
После этого создаем программу и загружаем в нее уже скомпилированные шейдеры.
Мы связываем программу, потому что шейдеры могут обмениваться переменными друг с другом.
И на этом этапе проверяется соответствие типов переменных, которыми обмениваются эти шейдеры.
Мы говорим: используйте эту программу.
Далее мы создаем список вершин, которые хотим визуализировать.
В WebGL есть интересная особенность для некоторых переменных.
Чтобы изменить конкретный тип данных, нужно задать глобальный контекст редактирования array_buffer, а затем что-то загрузить по этому адресу.
Не существует явного присвоения каких-либо данных переменной.
Здесь все делается через включение некоторого контекста.
Также необходимо установить правила чтения из этого буфера.
Видно, что мы указали массив из шести элементов, но программе нужно объяснить, что каждая вершина состоит из двух компонентов, тип которых — float, это делается в последней строке.
Чтобы установить цвет, программа ищет адрес переменной u_color и устанавливает значение для этой переменной.
Задаем цвет, красный 255, зеленый 0,8, синий 0 и полностью непрозрачный пиксель — цвет желтый.
И мы говорим, чтобы выполнить эту программу с использованием примитивов треугольника, в WebGL можно рисовать точки, линии, треугольники, треугольники сложной формы и так далее.
И сделайте три вершины.
Вы также можете указать, что массив, используемый для рисования, должен считаться с самого начала.
Ссылка со слайда
Если немного усложнить пример, то можно добавить зависимость цвета от положения курсора.
При этом фпс зашкаливает.
Чтобы рисовать частицы по всему миру, нужно знать скорость ветра в каждой точке этого мира.
Чтобы увеличить и как-то переместить карту, нужно создать контейнеры, соответствующие текущему положению карты.
Чтобы перемещать сами частицы, нам нужно придумать формат данных, который можно будет обновлять с помощью графического процессора.
Сделайте сам рисунок и рисунок поезда.
Все данные мы делаем через текстуру.
Мы используем 22 канала для определения горизонтальной и вертикальной скорости, где нулевая скорость ветра соответствует середине цветового диапазона.
Это примерно 128. Поскольку скорость может быть как отрицательной, так и положительной, цвет мы задаем относительно середины диапазона.
В результате получается вот такая картина.
Чтобы загрузить его на карту, нам нужно его разрезать.
Для подключения изображения к карте воспользуемся стандартным инструментом Яндекс.
Карты «Слой», в котором определим адрес, с которого нам нужно получить вырезанные тайлы, и добавим этот слой на карту.
Ссылка со слайда
Получаем картинку, где неприятный зеленый цвет кодирует скорость ветра.
Далее нам нужно получить место, в котором мы будем рисовать саму анимацию, и это место должно соответствовать координатам карты, ее перемещениям и другим действиям.
По умолчанию мы предполагаем, что будем использовать слой, но слой карты создает холст, из которого он немедленно извлекает 2D-контекст, который ему удается захватить.
Но если мы попробуем взять из холста, у которого уже есть контекст другого типа, и взять из него GL-контекст, то в результате мы получим null. Если вы получите к нему доступ, программа выйдет из строя.
Ссылка со слайда
Поэтому мы использовали Pane, это контейнеры для слоев, и добавили туда наш холст, из которого уже взяли нужный нам контекст.Чтобы как-то расположить частицы на экране и иметь возможность их перемещать, использовался формат положения частиц в текстуре.
Как это работает? Для оптимизации создана квадратная текстура, известен размер ее стороны.
Рисуя частицы по порядку и зная порядковый номер частицы и размеры текстуры, в которой они хранятся, можно вычислить конкретный пиксель, в котором закодировано положение на реальном экране.
В самом шейдере это выглядит как чтение индекса, который нужно отрисовать, текстуры с текущим положением частиц и размера стороны.
Далее мы определяем координаты x, y для этой частицы, считываем это значение и декодируем его.
Что это за магия: rg/255 + ba? Для положения частиц мы используем 20 двойных каналов.
Цветовой канал имеет значение от 0 до 255, и для экрана 1080 мы не можем размещать частицы в любой позиции экрана, потому что максимум, в котором мы можем разместить частицу, — это 255 пикселей.
Поэтому в одном канале мы храним знания о том, сколько раз частица прошла 255 пикселей, а во втором канале мы храним точное значение того, сколько раз она прошла после.
Далее вершинный шейдер должен преобразовать эти значения в свою рабочую область, то есть из -1 в 1, и установить эту точку на дисплее.
Чтобы просто посмотреть на наши частицы, просто покрасьте их в белый цвет. В GLSL есть такой сахар, что если мы определим тип переменной и передадим его в константу, то эта константа будет распределена по всем четырем компонентам, например.
Нарисовав такую программу, мы видим набор одинаковых квадратов.
Попробуем добавить им красоты.
Для начала добавим зависимость этих квадратов от текущей скорости ветра.
Мы просто читаем текущую скорость и соответствующие текстуры для каждой частицы.
Получаем длину вектора, соответствующую абсолютной скорости в точке, и прибавляем эту скорость к размеру частицы.
Далее, чтобы не рисовать квадраты, во фрагментном шейдере отсекаем все пиксели, выходящие за пределы радиуса, не входящие в радиус вписанной окружности.
То есть наш шейдер превращается в такую штуку.
Вычисляем расстояние до нарисованного пикселя от центра.
Если оно превышает половину пространства, то мы его не показываем.
Мы получаем более разнообразную картину.
Дальше нужно как-то переместить эти вещи.
Поскольку WebGL 1 не может что-то вычислять или работать напрямую с данными, мы воспользуемся возможностью рендеринга программ в специальные компоненты — кадровые буферы.
Например, к буферам кадров можно прикрепить текстуры, которые можно обновлять.
Если фреймбуфер не объявлен, то рендеринг по умолчанию выполняется на экране.
Переключая вывод одной текстуры положения на другую, мы можем обновлять их одну за другой, а затем использовать для рендеринга.
Сама процедура обновления позиции выглядит так: читаем текущую позицию, добавляем к ней текущий вектор скорости и добавляем, кодируем в новый цвет.
В коде это выглядит как чтение текущей позиции, декодирование, чтение текущей скорости, приведение скорости к нормальной, добавление двух составляющих, цветовое кодирование.
В результате получается вот такая картина.
Состояние частиц постоянно меняется, и появляется какая-то анимация.
Если вы запустите такую анимацию на 5–10 минут, то увидите, что все частицы прибудут в конечный пункт назначения.
Они все соскользнут в воронку.
У вас получится такая картинка.
Теги: #JavaScript #Визуализация данных #webgl #Геоинформационные сервисы #Яндекс погода #api Яндекс #ветер
-
Бета-Тестирование Flash Player 10 Началось
19 Oct, 24 -
Как Ускорить Миграцию Zabbix В Timescaledb
19 Oct, 24 -
Выпущены Две Новые Книги По Symfony
19 Oct, 24 -
Смотри Рекламу - Пей Напитки
19 Oct, 24