Если лень читать — сразу дам ссылки на важные ресурсы:
-
Demo: https://opensa.cc
-
New Trailer: https://www.youtube.com/watch?v=eA1gVWzANRU
Прошлые статьи серии:
В прошлых эпизодах я описывал создание первого прототипа, а затем исправление проблем с картой.
Настала пора самого интересного — сделать современную графику.
Изначально движок RenderWare не предоставляет возможностей динамических теней и освещения: все эти данные зашиты внутри объектов мира как статический prelight и как нарисованные тени (тоже статика).
Моим планом было накрутить эффектов, чтобы понять, на что способен браузер и как далеко можно зайти.
Перед началом, я написал пару тестовых сценариев, где камера пролетает над самыми тяжёлыми местами карты, чтобы измерять FPS. Замерять FPS я решил на каждом шаге своей работы.
Первый прогон, ещё без всех модификаций, показал не слишком оптимистичные результаты: мои улучшения карты, особенно высокополигональная растительность, дали серьёзную просадку по FPS. Причём просело неравномерно: за городом было 43 FPS, в Сан-Фиерро и Лас-Вентурасе — по 30, а в Лос-Сантосе, самом плотном городе, всего 18–19. То есть тяжёлый город лежал ещё до того, как я взялся за графику. Что ж, посмотрим, как будет дальше.
Вначале я подобрал систему управления цветом, выбирал из трёх вариантов: AgX, ACES, Neutral.
Остановился на ACES как на золотой середине между HDR-подходом Neutral и совсем блёклой картинкой AgX.
Сделал PBR-небо и правильный туман.
Картинка становилась всё лучше и лучше…
И наконец-то я сделал настоящий свет фар.
Но FPS всё падал и падал. В очередной проход, после докрутки каскадных теней и света, я сделал замер.
FPS скатился до 12–18. Это никуда не годилось, так как по сути мы имеем только пустой город, а его ещё надо наполнять машинами — и на этот счёт у меня большие идеи сделать эмуляцию жизни, а на неё нужны большие ресурсы.
Также имелся ряд проблем, которые надо будет решать. Например, тени вблизи выглядят ну очень хреново.
Подходы рендера 3D-графики в браузере
Если объяснять по-простому, то существует два способа рендера.
WebGL — довольно старая технология. У нас много Draw Call, а так как карта состоит из множества объектов, их подготовка осуществляется на уровне CPU и забивает main thread, поэтому появляются фризы и теряются FPS.
WebGPU — новая технология, позволяющая использовать все возможности видеокарты устройства: в ней мы можем убрать нагрузку с CPU, освободив main thread.
Моя текущая версия была полностью построена на WebGL, значит, надо переезжать на WebGPU. Но есть проблема — ThreeJS не в полной мере поддерживает WebGPU. Правда, тогда я этого ещё не знал, поэтому об этом позже.
WebGPU
Прежде чем брать и переписывать, я решил сделать отдельную сцену с эмуляцией стриминга: разместил 15к объектов с заменой 2к на лету в течение нескольких секунд.
Результат на ThreeJS оказался неутешительным.
На FPS в такой сцене смотреть бесполезно: 15к коробок стоят друг за другом, видеокарта много раз перерисовывает одно и то же место, и цифра ничего не значит. Смотреть надо на другое — сколько времени процессор тратит, чтобы собрать один кадр и отдать его видеокарте. Чем меньше, тем лучше.
И вот тут WebGPU в ThreeJS дал 27 миллисекунд против 10 на старом WebGL. То есть новая технология оказалась в два с половиной раза МЕДЛЕННЕЕ старой. Такой переезд ничего не даёт!
Попробовал Babylon. На обычном WebGL он оказался ещё хуже — втрое медленнее ThreeJS. Зато в режиме WebGPU он умеет один хитрый трюк: записать всю сцену один раз и дальше просто проигрывать эту запись. С ним сборка кадра ускорилась почти в сто раз. Такого я не ожидал.
Но стриминг всё портил. Запись у Babylon одна на весь мир: стоит добавить или убрать хоть один объект — и её надо перезаписывать целиком, а это 50 миллисекунд. У меня же карта грузит и выгружает объекты постоянно, пока едешь. То есть я получал бы заметный рывок картинки на каждой такой подгрузке. Babylon — отличное решение для статики, но в динамике он проседает.
Мы с Fable решили написать патч для ThreeJS. И патч сработал: сборка кадра ускорилась в пять раз, карта стала загружаться быстро, камера летала, машина ехала.
А FPS не вырос. Вообще. Кадр всё так же занимал треть секунды, игра шла на 3–4 FPS.
Дальше пошли раунды раскопок, и вот что выяснилось: время сидело не в процессоре. Всё, до чего можно дотянуться снаружи фреймворка, мы вычистили — сборка кадра в итоге стала быстрее раз в пятнадцать, память прижали. А кадр всё равно уходил в видеокарту и пропадал там на сотни миллисекунд. Причём если уменьшить разрешение в четыре раза, это время не менялось вообще — значит, дело не в том, что видеокарте много работы. Оно сидело внутри самого ThreeJS, в его прослойке к видеодрайверу. А туда снаружи не залезть — надо владеть этой прослойкой.
После очередных раундов кодинга Fable выдал мне это.
Весь проект был под угрозой. Что делать?
Создание своего ThreeJS
Оставалось только одно — писать свой ThreeJS, который будет WebGPU-first и сразу адаптирован под нагруженный стриминг.
Я не фанат таких поворотов и стараюсь избегать переписывания ядра, так как очень редко такие вещи дают реальный профит. Поэтому я решил в начале провести проверку.
Было создано отдельное приложение, я назвал его «Лаба», где мы с Fable шаг за шагом воспроизводили то, что умеет прод-версия.
Вначале отрендерили множество простых кубиков и кубиков с альфа-каналом. Результат — 120 fps. Неплохо, идём дальше.
Рендер одного из городов. 120 fps:
При этом на каждом шаге я делал замеры benchmark, о которых ниже.
Карта с базовым освещением. 120 fps.
Карта с ночным освещением — всё те же 120:
Глобальная переделка
В прошлой статье по улучшению карты я описал подход, в котором заимплементил ряд инструментов для обработки моделей и текстур, исправляя различные баги.
Её архитектура была сделана в unix-стиле, где каждый инструмент получает на вход игру и отдаёт пропатченную игру.
И я подумал: а что если — раз уж мы полностью меняем движок — сделать свой формат как моделей, так и текстур?
Что это может дать:
-
Мы можем зашить тени в LOD-объекты, а считать тени только для HD-сектора (равно как и свет).
-
Мы можем сложить текстуры в массивы, чтобы видеокарта брала их пачкой, и резать этим количество вызовов отрисовки. Классический вариант — свалить всё в один большой атлас — я тоже попробовал, и он оказался на 7 % хуже: в GTA текстуры замощают поверхность повторением, а в атласе повторять нечего.
-
Мы можем сделать абсолютно новые игровые механики, которые не предусматривает оригинальная игра.
Короче, начали делать дополнительный инструмент.
Так выглядят запечённые тени.
Конечно, без багов никуда.
Не буду долго томить: спустя множество раундов я получил инструмент, который не только улучшает графику ценой нулевых затрат в рантайме, но и делает модели легче, текстуры оптимальнее и так далее.
Возникла новая проблема — так как это новый формат, мы должны познакомить с ним и анимацию, и автомобили, и прочие вещи… Спустя время мы имели:
Промежуточные итоги:
После таких оптимистических цифр было принято решение — ПЕРЕПИСЫВАТЬ THREEJS!!!
В игре
Первый запуск в игре вышел комом, но — мир на движке, исторический скрин!
Куча багов, куда же без них.
Новый движок позволил сделать красивые облака.
Воду.
Реалистичное освещение было бы невозможно без преобразования карты. Вот как оно выглядит на карте без обработки, о которой я рассказывал в прошлой статье:
А вот как оно выглядит с обработкой:
Процесс работы
Такой большой пласт работы невозможен без постоянного контроля как качества, так и бенчмарков. Я делал замеры постоянно, реагировал на просадки FPS. В большинстве случаев они были связаны с багами.
Графика пока по уровню не полностью соответствует моим ожиданиям, но я буду над этим работать.
Также из наблюдений: Fable ошибается! И сильно! Была ситуация — две проблемы подряд: движок рассчитан только на одно авто, а авто рассчитано на один набор краски. То есть в мире бы ездили только авто одной марки с одним цветом. Это был не баг, а архитектурное решение.
Вообще работа с Fable мне напоминает работу с реально крутым разработчиком — Fable может спорить, отстаивать свои достижения, но все равно финальное слово за мной.
Подход с «Лабой» это настоящий опыт крупных компаний которые проводят масштабные рефакторинги — на малых фрагментах мы осуществляем гипотезу, потом мелкими шагами проводим замену, замеряя каждый шаг. На каждом этапе мы можем вернутсья к старой версии, так как удаление происходит только после полной миграции, мы можем сравнить и посмотреть, чего не хватает и как было сделано ранее. Последовательно пряча за флаги старые фичи и заменяя их новыми. И в финале мы просто деперкейтим устаревший код.
Итоги
Буквально за 2 недели я улучшил перфоманс с 16–18 до 100-120 FPS. Кадр стал собираться в 3–7 раз быстрее, а команд видеокарте движок отдаёт в 5–12 раз меньше. Всё это большими трудами и усилиями, в пока ещё не оконченном и не до конца подогнанном состоянии, но это огромный шаг вперёд, дающий мне хороший буфер производительности для имплементации самого важного — системы жизни в городе.
Мой следующий шаг — создать полноценный живой город, с персонажами и авто, дальностью прорисовки и прочим. Чтобы это смотрелось как мегаполис.
После этого я снова вернусь к графике, так как пока многие вещи находятся в процессе и мне нужно делать замеры на реально загруженном городе, чтобы понимать лимиты.
Продублирую полезные ссылки на важные ресурсы:
-
Demo: https://opensa.cc
-
New Trailer: https://www.youtube.com/watch?v=eA1gVWzANRU
Прошлые статьи серии:
Всем спасибо за внимание. Продолжение следует.
ссылка на оригинал статьи https://habr.com/ru/articles/1066360/