vgpu — это библиотека с открытым исходным кодом, которую мы создали в Vercel для создания производительных шейдеров для Интернета. После нескольких месяцев внутреннего использования мы наконец были готовы выпустить его. У нас была библиотека. У нас была целевая страница. Но одной важной детали все еще не хватало.
За неделю до запуска на целевой странице vgpu все еще не было своего героя. Команда предложила вернуть культовый визуальный элемент Vercel: стеклянную призму, разделяющую свет на красный, зеленый и синий.
Впервые я увидел призму на конференции Next.js Conf 2022, еще до того, как присоединился к Vercel. Это полностью поразило меня. Теперь пришло время создать свою собственную версию и каким-то образом привести ее в соответствие с оригиналом.
Я начал с самого буквального подхода: моделирования физики света. Для каждого пикселя на стене я направлял лучи назад через призму и проверял, достигают ли они источника света. Каждый успешный луч придавал цвет этому пикселю. Чтобы шейдер оставался управляемым, я использовал всего 16 сэмплов, чередуя их в каждом кадре и накапливая результаты с течением времени.

Это было довольно дорого и не выглядело резким, поэтому я продолжал изучать эффект. Я попросил GPT нарисовать эффект в формате SVG, чтобы объяснить мне, как он работает, и он сделал следующее:

Наблюдение за тем, как свет распространяется наружу по прямым линиям, натолкнуло меня на мысль: я мог бы нарисовать его в виде 3D-сетки. Сетка — это группа точек, соединенных в поверхность. Разместив эти точки по краям источника света, я смог нарисовать весь луч как набор фигур.

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

На тот момент это были всего лишь линии; у них не было поверхности, которую мог бы затенить графический процессор. Чтобы превратить их в видимый луч, я соединил соседние пути, чтобы сформировать треугольные грани сетки, получив гораздо более гладкий (и более дешевый) результат.

Несмотря на то, что световая сетка работала, в сцене все еще чего-то не хватало: стекла! К счастью, я уже создал шейдер стекла для Ева.dev герой, которого я мог адаптировать.

Шейдер использует карту окружения для захвата окружения стекла. Представьте себе, что вы разместили камеру в центре сцены и сделали шесть снимков, по одному в каждом направлении. Вместе эти изображения образуют кубическую карту: представление окружающей среды на 360°.

С помощью этой техники вы можете легко имитировать отражения от окружающей среды.
Установив шейдер стекла, я добавил цветение и плавающие частицы, чтобы завершить сцену в темном режиме:

Теперь пришло время поработать над светлым режимом. Легкий режим — это всегда вызов; добавлять свет на белый фон не имеет смысла (если только вы не используете HDR, и Twitter вас за это поджарит). Поэтому решение состоит в том, чтобы затемнить фон, оставив место для появления «света».
Я начал с создания некоторых концепций с помощью GPT и в конечном итоге создал это изображение, сгенерированное ИИ:

Как только у меня появилась концепция, я начал работать в обратном направлении от окончательного изображения. Я разбил его на отдельные элементы и придумал, как воссоздать каждый из них в сцене.
Например, на стене есть свет и тени, проникающие через окно. Он также имеет тонкую текстуру, которая создает ощущение настоящей стены. Призма отбрасывает тень и имеет внутри едва уловимое затенение.
Поскольку это была действительно сложная композиция, я создал визуализатор для конвейера рендеринга. Вы можете думать о создании шейдера как о смешивании множества изображений и математических вычислений, поэтому такой график помог мне понять, как я «смешивал вещи».

Давайте разберем это:
Призма отбрасывает тень. Моей первой мыслью было вычислить тень в реальном времени, но это может оказаться дорогостоящим; поскольку призма на самом деле не движется, мы могли бы нарисовать тень, вставив текстуру на стену.


Затем я сосредоточился на деталях стены. На концептуальном изображении мы видим небольшие «шишки» на стене. Мы можем добиться этого эффекта, создав карту нормалей.
Ключевой оптимизацией производительности было преобразование карты нормалей в статическое изображение, поэтому дорогостоящий расчет шума выполняется только один раз при запуске.

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

Вы можете получить доступ к режиму отладки по адресу https://vgpu.sh?debug. Если вы переключитесь между светлым/темным режимом и высоким/низким качеством, вы увидите изменение графика.
Адаптация к каждому устройству
Даже с учетом этих оптимизаций герой по-прежнему был слишком требователен к некоторым устройствам. Чтобы решить эту проблему, я создал облегченные версии обоих шейдеров. Они используют меньше сэмплов и меньше деталей, сохраняя при этом общий эффект визуально схожим.
Правило простое: начните с высокого качества, затем при необходимости переключайтесь на низкое качество. Для принятия решения система использует три сигнала:
- Уровень графического процессора: Если устройство имеет графический процессор низкого уровня или является мобильным устройством, герой запускается в режиме низкого качества.
- Уровень заряда батареи: Если у ноутбука заряд батареи менее 30% и он не заряжается, герой переключается в режим низкого качества, чтобы снизить энергопотребление.
- Частота кадров: Если устройство не может поддерживать стабильную частоту кадров, герой переключается в режим низкого качества.
Это позволяет более мощным устройствам воспроизводить полный эффект, сохраняя при этом плавность воспроизведения во всех остальных случаях.
В конце концов, создание такого героя – это сплошной дым и зеркала. Речь идет о балансе производительности и восприятия. Ему не обязательно идеально моделировать реальность. Ему просто нужно выглядеть убедительно и работать гладко.
Код этого героя вместе с другими примерами доступен в папке репозиторий vgpu на GitHub.