WebGPU: впечатления за два года

В конце 2020 года я с большим энтузиазмом взялся за изучение WebGPU. Для тех, кто не в курсе, поясню: это будущий веб-стандарт низкоуровнего графического API, который позволит браузерным приложениям эффективно задействовать возможности современных видеокарт. Замечательная особенность реализации WebGPU от Mozilla заключается в том, что ее можно использовать в нативных приложениях через C-интерфейс – я, разумеется, сразу занялся созданием собственной привязки WebGPU для D. На сегодняшний день у меня уже практически готов минимальный фреймворк для разработки WebGPU-приложений, исходники которого вы можете найти на GitHub: в текущей стадии он способен загружать и рендерить модели в формате OBJ с тестовой моделью освещения на основе GGX BRDF. Рендер прямой, безо всяких отложенных эффектов, также пока не поддерживается мипмаппинг. Тем не менее, кейс получился вполне достаточный для тестирования основных возможностей API.

Модель Cerberus, отрендеренная при помощи WebGPU

Этот фреймворк я писал довольно долго – в основном, из-за того, что wgpu-native жутко нестабилен, от версии к версии в инициализирующие структуры вносится очень много изменений. Часто бывает, что после очередного обновления приложение компилируется, но падает с какой-то экзотической ошибкой – без поллитры не разберешься (в итоге выясняется, что изменилась какая-нибудь константа, или стал обязательным nextInChain в одном из дескрипторов). Особым “удовольствием” было отлаживать шейдеры на WGSL в процессе стандартизации языка: то синитаксис атрибутов изменится, то разделитель полей в структурах… К тому же нестабильность API долгое время не давала мне определиться с архитектурой некоторых компонентов, ведь WebGPU имеет гораздо больше сущностей, чем OpenGL, и к ним нужно правильно подбирать модели данных.

Скажу честно: после OpenGL ко всем этим бинд-группам, очередям и command encoder’ам привыкнуть достаточно сложно. Порой не понимаешь, в какой класс лучше впихнуть очередную головоломную абстракцию наподобие WGPUBindGroupLayout или WGPURenderPassEncoder. Сложность в том, что сущности WebGPU сильно взаимосвязаны – одну не создашь без другой – и нужно заранее знать очень много информации, чтобы правильно проинициализировать конвейер.

Я почти сразу понял, что бинд-группы используются для раздельной передачи в шейдер ресурсов, обновляемых с различной частотой. Я делаю следующим образом:

Группа 0 – покадровые данные (видовая и проекционная матрицы)
Группа 1 – данные, обновляемые каждый проход (общие настройки сцены)
Группа 2 – свойства материала, текстуры
Группа 3 – свойства объекта (модельно-видовая матрица и др.)

Но нужно понимать, что этот лейаут не глобальный – он назначается для каждого пайплайна отдельно (поэтому и были придуманы эти пресловутые WGPUBindGroupLayout’ы). Вдобавок пайплайн в WebGPU неизменяемый – иными словами, если меняется какой-нибудь режим смешивания, то меняется вообще все. Такой подход может сильно обескуражить – за много лет пользования OpenGL его глобальное состояние стало для меня как родное! Тут вы не можете просто изменить конвейер так, как вам нужно – приходится создавать заранее несколько готовых пайплайнов на все случаи жизни и переключаться между ними функцией wgpuRenderPassEncoderSetPipeline. Способ управления пайплайнами сильно зависит от архитектуры вашего приложения, но в общем случае приходится городить достаточно сложный менеджер рендеринга, который создает проходы, задает им пайплайны, обновляет шейдерные ресурсы и подключает их в нужные моменты циклов перебора объектов сцены. Я до сих пор не уверен, что моя реализация этого менеджера годится для создания полноценного движка – надеюсь, что понимание придет в дальнейшем.

Буду ли я портировать Dagon на WebGPU? Отчасти – возможно, но перенести все функции движка с сохранением обратной совместимости, я думаю, нереально. Пока в этом и нет какой-то острой насущной необходимости, но начинать экспериментировать можно уже сейчас: API интересный, непривычный – рано или поздно привыкать все равно придется.

Hald CLUT

В Dagon появилась поддержка 3D-текстур, что позволило реализовать цветокоррекцию с использованием цветовых таблиц формата Hald CLUT. CLUT расшифровывается как color lookup table – таблица поиска цвета: в памяти хранится текстура, в которой стандартным цветам sRGB сопоставлены какие-то другие цвета – вместо оригинальных цветов пикселей на вывод идут значения, прочитанные из 3D-таблицы. Принцип примерно тот же, что использовался в индексированных цветовых режимах, только в данном случае таблица охватывает более широкий диапазон RGB. Чаще всего CLUT используется для имитации характерной «пленочной» цветовой гаммы на цифровых снимках, но ее возможности гораздо шире. В таблице цвета могут быть абсолютно любые – с математической точки зрения, она является функцией, которая переносит цвет из одного пространства в другое. Чем больше таблица, тем точнее ее охват.

Оргинал таблицы в формате Hald CLUT выглядит следующим образом (PNG можно скачать тут):

Если отредактировать это изображение в графическом редакторе – например, изменить яркость, контраст, насыщенность и т. д. – результат будет хранить информацию, необходимую для того, чтобы повторить эти же операции на другом изображении. Единственное условие: цветокоррекция должна выполняться для каждого пикселя параллельно и независимо от остальных. Если фильтр использует оконную свертку и другие алгоритмы, работающие с несколькими пикселями одновременно, то метод с использованием CLUT не будет с ним работать.

Преимущество Hald CLUT состоит в эффективном расположении значений – таблица размером 4096×4096 охватывает весь 24-битный диапазон sRGB (16777216 цветов) и при этом отлично сжимается в PNG. Для хранения таблицы важно использовать lossless-формат, так как сжатие с потерями вносит мелкие искажения в цвета, а в данном случае важно сохранить точность информации.

Еще одна немаловажная фича формата – прямая совместимость с 3D-текстурами OpenGL. Достаточно просто декодировать картинку в буфер RGB и создать из этого буфера текстуру функцией glTexImage3D – никаких промежуточных конвертаций не требуется. Эта текстура затем передается в шейдер постобработки, который выглядит совсем элементарно:

vec3 inputColor = texture(colorBuffer, texCoord).rgb;
vec3 outputColor = texture(lookupTable, inputColor).rgb;

В Dagon поддержка создания 3D-текстуры из двумерного буфера встроена в класс Texture. Нужно загрузить таблицу как ассет ImageAsset, создать текстуру и проинициализировать ее методом createHaldCLUT. Результат передается в стандартный стек постобработки (game.postProcessingRenderer):

ImageAsset aCLUT;

override void beforeLoad()
{
    aCLUT = addImageAsset("data/food.png");
}

override void afterLoad()
{
    Texture clut = New!Texture(assetManager);
    clut.createHaldCLUT(aCLUT.image, 256);
    game.postProcessingRenderer.colorLookupTable = clut;
    game.postProcessingRenderer.lutEnabled = true;
}

Поддерживаются таблицы любых разрешений, но вы должны сами правильно вычислить размер 3D-текстуры, соответствующей вашей CLUT. Например, для таблицы 4096×4096 это будет 256x256x256, как в моем примере. Если в этот параметр передать неправильное значение, то будет построена некорректная текстура (в релизе обязательно добавлю валидацию).

Пример использования на основе демки с автомобильной физикой – обработанное изображение и соответствующая таблица:

Итоги 2022 года

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

  • Вышли Dagon 0.13 и 0.14. В этих версиях были исправлены многие баги рендер-движка, улучшен загрузчик OBJ (добавлена поддержка квадов), произведен рефакторинг системы текстур, ускорена загрузка текстур через dlib. stb_image теперь опционален и используется как расширение dagon:stbi. Появилась поддержка формата сжатия ASTC. Изменены свойства материалов для достижения прямой совместимости с glTF. Появилась новая система текстурирования ландшафта. Добавлена поддержка радиального размытия. Удалено расширение dagon.ext.physics, так как теперь полноценно поддерживается физический движок Newton (расширение dagon.ext.newton). Добавлена экспериментальная интеграция UI-тулкита ImGui, а также поддержка валидации состояния OpenGL и более информативные отладочные сообщения.
  • Наконец-то выпустил dlib 1.0. В этой версии ускорена загрузка изображений, добавлена валидация при создании POSIX-потоков, улучшен модуль dlib.math.interpolation.hermite – добавлена функция вычисления производной для сплайна Эрмита. Исправлено несколько важных багов в математическом и геометрическом пакетах. Также вышло обновление dlib 1.1.0, в котором я добавил алгоритм обнаружения пересечения выпуклых тел MPR, а также исправил некоторые баги.
  • Я портировал игру DagoBan Матеуша Мушинского на актуальную версию Dagon. Этот клон Sokoban, написанный в качестве демонстрации возможностей тулкита Nuklear, является первой законченной игрой на основе Dagon.
  • Я написал три новые статьи на Medium: Almighty Alias, Metaprogramming with Alias Sequences, Game UI with Nuklear.
  • За этот год мне удалось собрать донатов на сумму $34.04 – они частично покрыли оплату хостинга и домена для сайта timurgafarov.ru. Огромное спасибо всем, кто перечислил деньги!

Ну и, конечно, не могу не назвать самые значимые для меня события в мире CG, СПО и геймдева:

  • Выход Unreal Engine 5 с очень впечатляющими фичами – особенно Lumen, технологией динамического глобального освещения. Но, каким бы крутым ни был UE, я не собираюсь отказываться от собственного движка)
  • Выход Newton 4, новой версии моего любимого физического движка. Жду теперь C API для создания привязки к D
  • Stable Diffusion – нейросеть, генерирующая изображения по текстовому описанию с пугающей реалистичностью.

10 лет на GitHub

В этом декабре исполняется ровно 10 лет с тех пор, как я зарегистрировался на GitHub! Хороший повод рассказать о своем опыте работы с платформой, порассуждать о системах контроля версий и, в целом, о том, как они повлияли на современное состояние IT.

(далее…)

Физика автомобиля

Демка с физикой автомобиля, которую я разработал в начале этого года для Patreon Sponsor Folder, теперь доступна на GitHub в репозитории https://github.com/gecko0307/vehicle-demo – с многочисленными улучшениями, которые я сделал за последние месяцы (в частности, появилась поддержка модели трения колес Pacejka Magic Formula). Пруф того, что на основе связки Dagon + Newton вполне можно написать гоночную игру. Как-нибудь, возможно, напишу подробную статью на эту тему.