Создание AAA-движка для GTA San Andreas в браузере и переписывание ThreeJS

Прокомментировать Просмотры: 14

AGX

Если нет времени на долбое чтение, держите прямые ссылки на ключевые материалы:

Предыдущие публикации цикла:

В ранних частях я разбирал сборку стартового прототипа и устранение багов с ландшафтом.

Пришло время для самого увлекательного этапа — внедрения современной визуальной составляющей.

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

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

Перед стартом я подготовил пару тестовых маршрутов, где камера на высокой скорости проносилась над наиболее тяжелыми участками карты для точного мониторинга FPS. Замерять кадровую частоту я условился на каждом отдельном этапе апгрейда.

Первичный прогон без каких-либо модификаций выдал довольно удручающие цифры: мои доработки мира, особенно детализированная растительность, ощутимо ударили по производительности. Причем просадки распределились неравномерно: за городом фиксировалось 43 FPS, в Сан-Фиерро и Лас-Вентурасе — ровно по 30, а в самом плотном и перегруженном Лос-Сантосе — всего 18–19. Иными словами, тяжелые локации задыхались еще до того, как я прикоснулся к графике. Что ж, посмотрим, как ситуация изменится дальше.

В первую очередь я занялся тональной компрессией и цветокоррекцией, выбирая между тремя алгоритмами: AgX, ACES и Neutral.

ACES
ACES
Neutral
Neutral

Мой выбор пал на ACES как на идеальный компромисс между избыточно HDR-ориентированным Neutral и чересчур плоским, блеклым AgX.

Затем я внедрил PBR-небо и настроил корректную объемную дымку.

City
City

Визуальный ряд стремительно преображался…

Cars
Cars
City
City
Night
Night
Night 2
Night 2
Shadows
Shadows

И наконец-то мне удалось реализовать полноценное свечение фар.

Car lights
Car lights
Car lights 2
Car lights 2

Тем временем кадровая частота продолжала стремительно падать. Очередные замеры после внедрения каскадных теней и источников света зафиксировали неприятную картину.

Low FPS

Low FPS

Показатели скатились до 12–18 FPS. Ни о какой стабильности не могло быть и речи, ведь перед нами пока пустой мегаполис, который еще предстоит нагрузить трафиком. В планах масштабная симуляция жизни горожан, а она потребует колоссальных ресурсов.

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

Shadows
Shadows
Shadows 2
Shadows 2

Подходы рендера 3D-графики в браузере

Если говорить совсем упрощенно, на выбор есть два принципиальных метода отрисовки.

WebGL — уже порядком устаревший стандарт. Огромное количество вызовов отрисовки (Draw Calls) в сочетании с массой мелких объектов на карте полностью ложатся на плечи процессора, перегружая главный поток выполнения (main thread), что неизбежно порождает микрофризы и потерю кадров.

WebGPU — современная технология, задействующая весь потенциал дискретного графического ускорителя и разгружающая CPU, оставляя главный поток свободным.

Мой проект на тот момент целиком базировался на WebGL, так что напрашивался неминуемый переход на WebGPU. Однако выяснился неприятный нюанс: Three.js на тот момент не обладал полноценной поддержкой WebGPU. Впрочем, тогда я об этом еще не подозревал.

WebGPU

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

Результаты прогона на Three.js оказались обескураживающими.

Test
Test

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

И здесь WebGPU в связке с Three.js показал удручающие 27 миллисекунд против 10 мс у проверенного WebGL. Новая технология оказалась в два с половиной раза медленнее ветерана! Простой переезд ничего не решал.

Я также протестировал Babylon.js. На классическом WebGL он повел себя еще хуже — отстал от Three.js ровно в три раза. Зато в режиме WebGPU у него нашелся занятный козырь: возможность один раз записать всю сцену в буфер и в дальнейшем просто воспроизводить этот снимок. Время сборки кадра сократилось почти в сто раз, чего я никак не ожидал.

Но динамический стриминг сводил весь этот бонус на нет. Запись в Babylon монолитна для всего мира: стоит добавить или удалить хотя бы один единственный объект, как всю последовательность приходится пересоздавать заново, а это тяжелые 50 миллисекунд. Моя же игра непрерывно подгружает и выгружает куски карты прямо во время движения. В результате мы бы получали заметный рывок картинки при каждом стриминге. Babylon прекрасен для статичных проектов, но пасует в условиях открытого динамического мира.

В итоге мы с Fable подготовили кастомный патч для Three.js. Решение сработало: формирование кадра ускорилось в пять раз, карта стала подгружаться молниеносно, камера плавно летала, а машина уверенно катила по дороге.

Block
Block
Low FPS
Low FPS

А вот FPS остался на прежнем уровне. Вообще не изменился. Кадр по-прежнему отнимал треть секунды, а игра еле плелась на 3–4 FPS.

Дальнейшие раскопки показали удивительное: узкое горлышко крылось вовсе не в центральном процессоре. Все внешние издержки за пределами фреймворка мы успешно вычистили — сборку кадра удалось ускорить в пятницу раз, потребление памяти прижали. Однако готовый кадр уходил в видеокарту и бесследно исчезал там на сотни миллисекунд. Примечательно, что четырехкратное уменьшение разрешающей способности вообще не влияло на это время, значит, проблема была не в объемах работы для GPU. Затык прятался глубоко внутри самого Three.js, на уровне его прослойки к видеодрайверу. А добраться туда извне невозможно без полного контроля над этим слоем.

После очередной серии бессонных ночей за кодингом Fable выдал мне неутешительный вердикт.

Stuck
Stuck

Весь амбициозный проект оказался под угрозой срыва. Как быть дальше?

Создание своего ThreeJS

Оставался единственный радикальный выход — писать собственный аналог Three.js, спроектированный с расчетом на WebGPU-first и изначально адаптированный под агрессивный потоковый стриминг.

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

Мы создали отдельное изолированное приложение под кодовым названием «Лаба», где совместно с Fable постепенно воссоздавали весь необходимый функционал рабочей версии.

First Render
First Render

Сперва отрендерили массив примитивных кубов, в том числе с поддержкой альфа-прозрачности. Результат — стабильные 120 FPS. Отличный задел, движемся дальше.

Рендеринг одного из внутриигровых мегаполисов. Те же 120 FPS:

Map
Map

Параллельно на каждом шаге я снимал подробные бенчмарки.

Карта с базовыми источниками света. 120 FPS.

Map Lights
Map Lights

Ночной режим освещения — показатели держатся на отметке 120:

Map Night
Map Night

Глобальная переделка

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

Её архитектура строилась по канонам Unix-философии: каждая мини-утилита принимает на выходе оригинальный проект и отдает модифицированную, пропатченную версию.

Packer
Packer

Тогда мне пришла в голову мысль: раз уж мы кардинально меняем весь движок, почему бы не спроектировать собственные оригинальные форматы как для моделей, так и для текстур?

Какие привилегии это открывает:

  • Мы можем запекать тени прямо в LOD-объекты, просчитывая их детализацию исключительно для HD-секторов (аналогично поступая со светом).

  • Появилась возможность упаковывать текстуры в массивы (Texture Arrays), чтобы видеокарта забирала их пакетно, тем самым радикально снижая количество вызовов отрисовки. Классический вариант со слиянием всего в один огромный атлас я тоже опробовал, но он оказался на 7 % медленнее: в GTA текстуры активно используют тайлинг (повторение), а в атласе замостить поверхность повтором невозможно.

  • Мы получаем полную свободу для реализации абсолютно уникальных игровых механик, изначально не заложенных в оригинальном проекте.

Сказано — сделано, принялись писать дополнительный инструментарий.

Вот так выглядят запеченные тени.

Shadows
Shadows

Разумеется, на старте не обошлось без курьезных багов.

Bug 1
Bug 1
Bug 2
Bug 2
Bug 3
Bug 3

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

Вскоре всплыла новая трудность: новый формат потребовал подружить с собой анимации, транспортные средства и прочие элементы… Немного времени спустя мы добились стабильной работы:

Car
Car

Промежуточные достижения:

Results
Results

Вдохновившись столь воодушевляющими цифрами, было принято волевое решение — ПЕРЕПИСЫВАТЬ THREE.JS!

В игре

Первый запуск непосредственно внутри игры вышел комом, но это полноценный игровой мир на нашем движке — исторический кадр!

In Game

In Game

Куда же без кучи забавных багов.

Bug 1
Bug 1
Bug 2
Bug 2
Bug 3
Bug 3
Bug 4
Bug 4

Новый движок позволил реализовать живописные облачные объемы.

Clouds
Clouds

И реалистичную воду.

Water
Water

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

Lights Bug
Lights Bug
Normals Bug
Normals Bug

А вот результат после применения патчей:

Fix
Fix

Процесс работы

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

Визуальная часть пока не полностью удовлетворяет моим эстетическим запросам, но я продолжу над ней трудиться.

Отдельно стоит упомянуть забавные казусы: Fable иногда совершает масштабные просчеты! Был случай, когда подряд всплыло две архитектурные проблемы: движок был завязан на рендер строго одной модели машины, а сама машина — на один единственный цветовой пресет. Иными словами, по всему миру колесили бы исключительно автомобили одной марки и расцветки. Формально это не баг, а заложенное архитектурное ограничение.

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

Подход с «Лабой» в точности повторяет лучшие практики крупных корпораций при проведении масштабных рефакторингов: мы проверяем гипотезы на миниатюрных изолированных полигонах, затем мелкими шажками внедряем изменения, фиксируя метрики на каждом шаге. На любом этапе у нас сохранялась возможность откатиться к старой версии, поскольку старый код удалялся только после полной и бесшовной миграции. Мы могли легко сравнить поведение систем, выявить нехватающие детали и последовательно прятать старый функционал за флагами функциональности (feature flags), подменяя его новыми модулями. А в самом конце мы просто провели деприкацию устаревшего кодовой базы.

Итоги

Всего за две недели мне удалось поднять производительность с унылых 16–18 до плавных 100–120 FPS. Время сборки кадра сократилось в 3–7 раз, а количество отправляемых видеокарте команд уменьшилось в 5–12 раз. Путь был тернистым, результаты пока еще требуют полировки, но это колоссальный прорыв, дающий отличный запас производительности для реализации важнейшего компонента — симуляции городской жизни.

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

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

Дублирую полезные ссылки на ключевые ресурсы:

Предыдущие статьи цикла:

Всем спасибо за внимание. Продолжение следует.

 

Источник

Поделиться:

Похожие статьи

Поиск по играм, новостям и статьям…

Введите не менее двух символов

Введите не менее двух символов