Дневник разработчиков Captain of industry № 52: оптимизация транспортных средств и океана
Здравствуйте и добро пожаловать на 52-ю страницу «Капитанского дневника». Я капитан Марек, и на этой неделе я поделюсь некоторыми подробностями о своей недавней работе по оптимизации транспортных средств и симуляции океана. Это немного более техническая тема, поэтому вкратце скажу, что из-за проблем с амфибийными транспортными средствами мы оптимизировали транспортные средства и симуляцию океана, и теперь вы сможете наслаждаться игрой с частотой кадров на 10–15 % выше! Читайте дальше, если хотите узнать, как были достигнуты эти успехи и какое отношение это имеет к амфибийной технике.
Рендеринг транспортных средств
Транспортные средства в Captain of Industry смоделированы в 2D. Для этого есть много причин, в основном связанных с производительностью и сложностью реализации (поиск пути в 3D становится очень сложным и происходит очень быстро). Однако игра выполнена в 3D, поэтому для каждого кадра необходимо вычислять высоту и 3D-ориентацию всех транспортных средств на основе их 2D-положения.
Расчёт положения транспортного средства не представляет особой сложности:
Вычислите двумерные координаты четырёх «угловых точек» транспортного средства на основе его двумерной позы (положения и поворота). Эти четыре точки обычно находятся там, где расположены колёса или где заканчиваются гусеницы.
Преобразуйте эти двумерные точки в трёхмерные, вычислив высоту рельефа в каждой точке. При этом учитываются такие исключения, как пандусы для транспортных средств, где транспорт движется по альтернативной поверхности, а не по рельефу.
Проведите трёхмерную плоскость через эти точки.
Вычислите трёхмерную позу (положение и поворот) на основе полученной трёхмерной плоскости.
Проблема с амфибийными транспортными средствами
Когда мы разрабатывали машины-амфибии, одним из препятствий, очевидно, был океан! Я имею в виду возможность того, что машины будут плавать по волнам. Дело в том, что наш океан полностью смоделирован на графическом процессоре (подробнее об этом в выпуске № 35), и центральный процессор вообще ничего не знает о поверхности океана.
На столе лежали два решения:
Загружайте сгенерированные текстуры океанских волн из памяти графического процессора в каждом кадре и заставляйте центральный процессор вычислять высоту океанских волн в четырёх угловых точках каждого транспортного средства. Это довольно просто реализовать, но передача данных из графического процессора в центральный требует немалых затрат, а работа центрального процессора, необходимая для вычисления высоты волн, значительна (сотни инструкций).
Пусть графический процессор вычисляет высоту океанских волн в точках расположения транспортных средств. Центральный процессор загружает в графический процессор список двумерных точек интереса, а вычислительный шейдер эффективно вычисляет соответствующую высоту океанских волн. Это будет значительно быстрее, чем предыдущее решение.
Зная, что первое решение приведёт к значительной дополнительной нагрузке на процессор, мы выбрали второе решение, и я за него взялся.
Когда я писал вычислительный шейдер для оценки высоты волн, мне пришло в голову, что у нас уже есть текстура высоты рельефа на графическом процессоре для рендеринга рельефа. Почему бы не использовать её? И почему бы не создать ещё одну текстуру высоты для специальной поверхности транспортного средства и не использовать её? Это позволило бы нам полностью отказаться от выборки высоты транспортного средства на центральном процессоре.
И это, друзья мои, то, что мы часто называем «кроличьей норой». Из задачи, на которую отводился один день, внезапно вырастает проект, на который уходит целая неделя!
Но прежде чем я прыгнул в эту кроличью нору, я выполнил домашнее задание и проанализировал производительность вычисления положения транспортного средства на центральном процессоре. Я подумал, что если на это уходит менее 1 % времени обновления симуляции, то оптимизировать это не стоит. Я провёл анализ и, держитесь крепче, вычисление положения транспортного средства заняло около 10 % времени симуляции!
Снимок экрана из инструмента профилирования, показывающий, что обновление 217 грузовиков заняло 1 мс, а всех транспортных средств — 1,4 мс, что составляет около 14 % от общего времени моделирования.
Увидев этот результат, я понял, что оптимизация этого кода определённо стоит дополнительных усилий. Поэтому я написал код, который позволяет эффективно хранить текстуру поверхности транспортного средства на графическом процессоре и использовать её вместе с текстурами океана и ландшафта для полного преобразования 2D-поз транспортных средств в 3D на графическом процессоре. Последний вычислительный шейдер принимает 2D-позы и смещения углов транспортных средств для всех транспортных средств и вычисляет окончательные 3D-позы.
Тестирование показало, что вычисление положения 270 транспортных средств занимало около 1 мс, а теперь на центральном процессоре это происходит практически мгновенно (требуется лишь скопировать в память небольшой массив).
Снимок экрана инструмента профилирования, показывающий, что оптимизация вычисления положения транспортного средства сократила время обновления транспортного средства с 1,4 мс до 0,4 мс
И самое приятное то, что вычисление 200 поз на графическом процессоре требует так мало ресурсов, что мы можем вычислять в 10 раз больше обновлений транспортных средств без замедления работы, в то время как центральный процессор будет линейно замедляться с каждым дополнительным транспортным средством.
Возможно, вам интересно, почему процессор работает так «медленно». Дело в том, что каждое транспортное средство выбирает четыре точки, а для интерполяции каждой точки требуется четыре значения высоты, то есть 16 значений для каждого транспортного средства. Поверхность транспортного средства хранится в словаре, поэтому добавьте к этому 16 обращений к словарю и множество операторов if и вызовов методов. Кроме того, используются тригонометрические функции и множество арифметических операций. Для более чем 200 транспортных средств это даёт значительный результат. Очевидной неэффективности не было, просто много обращений к памяти и математических вычислений (я почти уверен, что узким местом была память).
Стоит отметить, что из-за задержек при передаче данных с центрального процессора на графический транспортные средства теперь отстают на несколько тактов моделирования. Я написал интеллектуальную систему минимизации задержек, поэтому, если ваш компьютер обрабатывает результаты достаточно быстро, задержка может составлять всего один такт, а в худшем случае — до четырёх тактов.
Оптимизация океана
Транспортные средства-амфибии создавали ещё одну проблему, связанную с океаном. Теперь, когда мы можем моделировать транспортные средства, «парящие над волнами», в настройках рендеринга появилась возможность отключить реалистичную симуляцию океана и просто использовать плоскость с анимированными текстурами. Проблема в том, что, хотя анимированные текстуры придают низкодетализированному океану некоторую глубину, трёхмерная сетка представляет собой просто плоскую поверхность. Как только вы размещаете на ней транспортные средства, иллюзия глубины исчезает, и она выглядит как плоская поверхность.
Итак, у нас было два варианта:
Просто смиритесь с этим, в любом случае это низкокачественный вариант. Добавьте возможность полностью отказаться от вычисления волн в вычислительном шейдере, который оценивает положение транспортного средства.
Попробуйте оптимизировать смоделированный океан и убрать из игры опцию «плоский океан».
Вам это не напоминает кроличью нору? Проблему с потенциально высокой сложностью и неопределёнными результатами? Да, и что мы делаем перед тем, как нырнуть в кроличью нору? Анализируем! Я хотел узнать, насколько медленнее работает смоделированный океан.
Согласно моим тестам, на рендеринг смоделированного океана уходило в 10 раз больше времени, а на менее мощных графических процессорах этот показатель мог быть ещё выше.
Когда я представил свои выводы на совещании и сказал, что, по моему мнению, стоит стремиться к более высоким показателям, Джереми мягко напомнил мне, что мою реализацию симуляции океана, безусловно, можно оптимизировать, и дал мне несколько советов. Так что все за работу, мы выбираем второй вариант!
Я не хочу вдаваться в технические подробности, но должен упомянуть, что наш океан использует так называемое обратное быстрое преобразование Фурье (ОБПФ) для эффективного моделирования и суммирования сотен синусоидальных волн различной частоты и амплитуды. Кстати, эту же технологию используют в кино и так называемых «ААА»-играх.
IFFT должен выполняться в несколько этапов, в нашем случае 8, и моя первоначальная реализация вызывала вычислительный шейдер для каждого этапа. Это естественная вещь, поскольку каждый этап должен завершиться до начала следующего. Однако, как указал Джереми, эти этапы могут быть “объединены” в один вызов вычислительного шейдера. Это возможно благодаря функции GroupMemoryBarrierWithGroupSync , которая позволяет всем потокам в группе потоков ожидать друг друга, чтобы их промежуточные результаты могли быть повторно использованы на следующем этапе. Как вы можете себе представить, синхронизация групп потоков выполняется намного быстрее, чем вызов нового вычислительного шейдера.
Объединённые ядра IFFT позволили нам ускорить работу в 4–5 раз, но я чувствовал, что можно добиться большего. Моей второй целью было сократить объём работы на кадр. Мы по-прежнему выполняли четыре IFFT на кадр, а это большой объём работы. Я хотел реже выполнять фактическое моделирование и просто использовать линейную интерполяцию между кадрами — по сути, то же самое, что мы делаем для моделирования игры, но для океанских волн.
Я не буду вдаваться в технические подробности, но отмечу, что я решил моделировать океан 10 раз в секунду и просто интерполировать промежуточные значения. С точки зрения реализации мы храним три набора данных: два интерполируемых и один вычисляемый, и они чередуются циклически. Организовать всё это было непросто, но и не так сложно, как может показаться.
Самым большим преимуществом этого метода является то, что отдельные вычисления IFFT могут быть вызваны в разных кадрах, что дополнительно снижает нагрузку на каждый кадр в 3-4 раза. Что еще более удивительно, так это то, что чем больше кадров в секунду вы получите, тем меньше будет нагрузка на каждый кадр. Для систем, которые запускают COI со скоростью 40 + кадров в секунду, будут кадры, в которых IFFT даже не выполняется на графическом процессоре.
Вот окончательные результаты тестирования. Если рассматривать только нагрузку на вычислительное ядро графического процессора, то старое IFFT занимало 0,8 мс на кадр, а после всех оптимизаций — 0,05 мс на кадр. Это ускорение в 16 раз!
Снимок экрана из профилировщика графического процессора, на котором показана итоговая нагрузка на вычислительные шейдеры океана (фиолетовые прямоугольники) за кадр — около 0,05 мс.
Я протестировал это на пустой карте Нью-Хейвена, и старый плоский океан выдавал 352 кадра в секунду, старый причудливый океан — 301 кадр в секунду, а тот же океан после оптимизации — 343 кадра в секунду. Таким образом, новый океан не такой дешёвый, как старый, но разница невелика, поэтому мы решили убрать старую опцию из игры.
Окончательный ориентир
Подводя итог, можно сказать, что для того, чтобы машины-амфибии заработали, мы оптимизировали расчёт положения транспортных средств и рендеринг океана. В конце игры на McRib с 270 транспортными средствами общий FPS вырос с 54,8 до 61,5, то есть на 12%! Вот более подробные цифры:
Результаты тестирования: время синхронизации сократилось с 3,29 до 3,03 мс (на 8 % быстрее), время обновления симуляции — с 11,5 до 9,65 мс (на 16 % быстрее), а время обновления рендеринга — с 3,58 до 3,14 мс (на 12 % быстрее).
Путевые точки поездов
В обновлении 4 появятся облегчённые железнодорожные станции, также известные как путевые точки! Мы учли ваши отзывы о том, что зоны ожидания и вспомогательные станции, не требующие погрузочных модулей, занимают слишком много места.
Зал ожидания с продуманным расположением станций для экономии места. Тем не менее более 50 % пространства не используется.
Путевая точка поезда работает точно так же, как станция, с точки зрения расписания поездов, но к ней нельзя прикрепить модули. Однако для неё не нужно больше места, чем для самого железнодорожного пути, поэтому её можно построить на параллельных путях без дополнительного пространства. Более того, путевые точки можно размещать даже на эстакадах. Путевая точка будет доступна в базовой игре.
Атомный локомотив
В последнем выпуске «Капитанского дневника» я вкратце упомянул, что в грядущем дополнении «Поезда» появятся новые локомотивы, и многие обсуждали самый необычный из них — ядерный локомотив. Я видел много предположений о том, насколько он будет большим и будет ли ему нужно топливо или вода, поэтому позвольте мне прояснить ситуацию.
Атомный локомотив состоит из трёх частей: служебного вагона (часть A), вагона с ядерным реактором и турбиной (часть B) и вагона-конденсатора (часть C). Длина всего локомотива составляет 25 плиток (50 метров), что эквивалентно пяти тепловозам первого уровня.
Атомный локомотив, трёхсекционный гигант. Он очень тяжёлый, очень мощный и очень редко нуждается в дозаправке.
В блоке A находится экипаж из двух человек. В блоке B расположен защищённый ядерный реактор и паровая турбина, которая вырабатывает электричество для всех ведущих осей. Чтобы локомотив был автономным и не нуждался в дозаправке водой, в блоке C происходит конденсация отработанного пара с возвратом воды в реактор.
Поскольку вода циркулирует по замкнутому контуру, единственное, что нужно дозаправлять, — это ядерное топливо. Для этого необходимо заменить весь реактор. Самое интересное, что реактор нужно менять только раз в 100 лет!
А если по какой-то причине вам недостаточно локомотива, состоящего из трёх секций, можно присоединить несколько пар секций B-C к одной секции A.
Русское сообщество по игре в ТГ
https://t.me/captainofindustryru
ДС
https://discord.gg/GXxWHqG9M8
















































