Хабрахабр

[Перевод] Физика игрового торнадо: как реализована аэродинамика в Just Cause 4 (трафик)

Жак Кернер — старший инженер-разработчик ПО в Avalanche Studios.

Как будто раньше игра была недостаточно безумной

Введение

Серия игр Just Cause и Avalanche Studios известны своей технологией открытого мира, обеспечивающего разнообразный и увлекательный игровой процесс. В последней версии игры — Just Cause 4 — добавлены ветер и погодные катаклизмы, ставшие новинкой в стеке технологий, углубляющих игровой процесс. Но экстремальные природные условия изначально задумывались не просто как способ симуляции более правдоподобного мира. Ярость природы управляется силами зла, противостоящими Рико Родригесу. Мы намеревались сделать так, чтобы ветер проявлялся более явно и экстремальные погодные условия не выглядели как внезапные события, чуждые этому миру. В этой статье представлены техники, разработанные нами для реализации ветра во всех его проявлениях с физической точки зрения, а также реакции на него всех объектов.

[Под катом около 120 МБ файлов GIF]

Тропический шторм в JC4 — ранний концепт (Volta)

Предложение, от которого мы не могли отказаться

Когда разработка Just Cause 3 приближалась к завершению, большая часть команды перешла к препродакшену Just Cause 4, а крошечное ядро осталось работать над патчами для JC3 и скачиваемым контентом («DLC»). Мы с Хэмишем Янгом, ведущий программист и ведущий дизайнер транспортных средств, сосредоточились на DLC Mech Land Assault. Мы должны были стать ведущим дизайнером физики (и механики игрока) и ведущим программистом физики JC4, но нас полностью поглотило DLC в тот момент, когда создавался дизайн новой серии франшизы, определялись её привлекательные особенности и важные функции. На этот раз был создан масштабный прототип для проверки новых базовых механик и реакции Рико на ветер. Был написан черновик части сюжета, первоначальное направление развития утвердил издатель Square Enix. Единственное, что оставалось — освоить концепцию. Но как это сделать, не поставив при этом под удар производительность? Как только мы приступили к проекту, то атаковали проблему с двух фронтов: 1. установили широкие ограничения, чтобы избежать наихудших сценариев (спойлер: нам это не удалось) 2. разобрались с различными проявлениями экстремальных погодных условий, и в частности, ураганного ветра, чтобы создать систему, которая обеспечивает реалистичное поведение, но хорошо масштабируется под нужное количество и плотность объектов.

Песчаная буря в JC4 — ранний концепт-арт (Volta)

Контроль ущерба

«Узким местом» в симуляции реального времени и особенно игр в открытом мире, является количество сталкивающихся физических тел. Основные траты возникают из-за вычисления коллизий множества подвижных тел, сталкивающихся друг с другом и со статичным окружением (рельефом, зданиями). Именно поэтому физические движки наподобие Havok разделяют активные и неактивные тела. Активные тела проверяются на коллизии с другими телами и требуют полных вычислительных затрат. Если активное тело не движется в течение нескольких кадров, то физический движок помечает его как неактивное, и с этого момента оно может полностью игнорироваться, пока его не «разбудит» приблизившееся активное тело. Такие неактивные тела обычно покоятся на земле и проверки коллизий между ними и землёй больше не выполняются. Очевидно, что повсеместное присутствие ветра в открытом мире станет угрозой этой системе, поэтому нам важно было сделать так, чтобы ветер оставался умеренным и чисто косметическим в случае воздействия на большие площади, или сильным и физически активирующим при возникновении в небольших объёмах и местах, где количество потенциально активных тел невелико. Я рассказал об этих ограничениях нашему дизайнерскому отделу на ранних этапах разработки, чтобы он не оказался в ловушке. Поначалу казалось, что дизайнеры прислушались и решили ограничить траектории экстремальных погодных событий заранее установленными маршрутами разрушений, в пределах которых должны действовать более строгие правила построения мира. И часть этих ограничений они действительно соблюдали. Но искушение было слишком велико, а давление коллег, желавших сделать игру как можно лучше, оказалось непреодолимым. О чём я только думал? Сразу же после этого дизайнеры создали торнадо высотой в несколько километров, проходящее прямо по столице страны — самой густонаселённой части острова. Это торнадо смело не только половину столицы, но и часть ни о чём не подозревавших программистов, не слишком строго придерживавшихся этой базовой особенности игры.

Вьюга в JC4 — ранний концепт-арт (Volta)

Общий подход

В целом, физике экстремальных погодных условий в JC4 требовались следующие ингредиенты:

  • Модель аэродинамического сопротивления, применяемая ко всем динамическим объектам мира, учитывающая их форму и размеры
  • Источники ветра, соответствующие по форме и распределению ветра экстремальным погодным условиям (шторм, торнадо), но также обеспечивающие паттерны распространения ветра по миру
  • Оптимизация вышеописанных систем, чтобы игра умещалась в бюджет вычислительных нагрузок

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

После её завершения каждый объект внезапно научился достаточно реалистичным образом реагировать на ветер. Сначала мы создали модель аэродинамического сопротивления. Но при отсутствии источников ветра сложно было сказать, как они поведут себя в условиях экстремального ветра. Например, если любой объект падает со скалы или отбрасывается взрывом, все объекты замедляются и реалистично вращаются в воздухе, что само по себе уже стоило приложенных усилий. Она поднимала все динамические объекты и крутила их достаточно реалистичным образом, не занимая при этом всё время процессора. Хотя мы гордились собой и тем, что опыт и интуиция помогли нам быстро найти удовлетворявший требованиям производительности подход к первым двум проблемам, мы решили, что нам сильно повезло, когда первая версия торнадо начала работать именно так, как мы и ожидали. Директор JC4 Франческо Антоллини, который очень часто спрашивал о ситуации с торнадо, сразу же после выпуска системы появился возле моего рабочего места и выразил своё облегчение: думаю, я упустил возможность пристыдить его за то, что он сомневался во мне; но на самом деле я ощущал то же самое.

И всё это оказалось возможным на тех же самых целевых платформах (XBox One и PlayStation 4). С физической точки зрения JC4 стала прорывом по сравнению с JC3: это более плотный, заполненный, живой и красивый мир, разрушать который намного интереснее. Наш технический директор Дейв Барретт сразу же воспользовался нашим преимуществом: у нас были необходимые показатели производительности для JC3, поэтому руководителю отдела разработки движка Даниэлю Пьерони дали задачу определить, какие максимальные затраты производительности и памяти можно использовать на каждый технический аспект. Неудивительно, что оптимизация заняла очень много времени. Огромную часть этого бюджета Havok занимал для распознавания возможных коллизий между объектами, вычисления контактов, разрешения ограничений и «интегрирования» движения всех активных тел для определения их позиции через 33 мс симулируемого времени. На физику нам отвели щедрые 8,5 мс на 4 потоках из 33 мс времени процессора, выделенные на создание кадра с частотой 30 раз в секунду. В основном нам удавалось удерживаться в пределах бюджета, хотя скорее всего об этом лучше спросить у Даниэля. Я примерно подсчитал, что наш бюджет на все вычисления, связанные с аэродинамикой и ветром должен быть равен примерно 1 мс на 4 потоках. Безусловно, его, как и многое другое, можно ещё больше оптимизировать, адаптировав к работе на GPU. Представленное ниже решение достаточно быстро для реального времени при вычислении на современных ЦП нескольких сотен объектов.

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

Ветер в JC4 — ранний концепт-арт (Ironklad Studios)

Модель сопротивления

Наша цель при создании модели сопротивления заключалась в оценке сил сопротивления и крутящих моментов, которые бы приблизительно напоминали те, которые прикладываются к телу произвольной формы в реальном мире. Заметьте, насколько мы здесь были неамбициозными — никто не требовал огромного реализма. Однако у нас были кое-какие требования. Во-первых, силы сопротивления должны противодействовать движению тела. Во-вторых, сила должна вычисляться из базового уравнения аэродинамического сопротивления, т.е. изменяться в соответствии с площадью поверхности фигуры и квадратом скорости движения по воздуху. Наконец, каким-то образом должны учитываться форма и размер тела, чтобы объекты с одинаковой площадью поверхности, но очень разной формы вели себя по-разному. У меня было искушение просто аппроксимировать форму каждого тела по описывающему его параллелограмму, но я подумал, что нужно нечто большее, потому что было очевидно — форма многих объектов может значительно отличаться от ящика или даже небольшого количества ящиков. Предыдущий опыт работы с системами, грубо аппроксимирующими объём 3D-объектов с помощью вокселей или сфер, заставил меня искать решение получше. Мне предлагали подбирать коэффициенты сопротивления для каждого объекта вручную, но я хотел избежать ещё одного этапа ручной настройки в уже и так сложном конвейере. Поэтому я продолжал искать автоматизированный способ. И я рад, что не отступился.

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

В «тяжёлых» сценах мы запросто могли достичь более 100 000 граней. «Единственная» проблема заключалась в том, что суммирование вклада тысяч треугольников для каждой отдельной фигуры во время выполнения игры было на порядок выше требуемого уровня затрат. Первый метод «грубой силы» заключался в предварительном вычислении аэродинамических сил и крутящих моментов, возникающих при перемещении тела с различными скоростями в нескольких направлениях в неподвижном воздухе при вращении с разной угловой скоростью в разных направлениях. Было бы здорово, если бы могли вычислять силы предварительно. Это можно представить как виртуальный туннель ветра, включающийся во время компиляции нашего контента, в который мы по очереди помещаем все объекты и подвергаем их разным скоростям движения или вращения, размещая каждый объект под разными углами, и каждый раз измеряя силы и крутящие моменты. Так мы получали таблицу поиска, из которой можно было бы брать значения во время выполнения игры.

Известными величинами во время выполнения будут линейная и угловая скорость тела, поэтому $S=5$, то есть всего около 300 КБ на объект. Как будет выглядеть эта таблица? И всё равно это как минимум на порядок величин больше, чем допустимое потребление памяти.

Во-первых, я преобразовал к линейному виду вычисление в сумму вкладов на основании предварительного вычисления сил и крутящих моментов по отдельности для только перемещаемого и только вращаемого тела. Чтобы значительно снизить количество сэмплов, я использовал две техники. Это уменьшило предварительно вычисляемую таблицу всего до 7 КБ на объект. Во-вторых, я аппроксимировал это в аналитическую формулу для экстраполяции значения при произвольных скоростях на основании заранее вычисленной реакциии только при скорости перемещения 1 м/с и скорости вращения 1 рад/с. Причина возникновения проблемы заключается в том, что даже при очень простой модели сопротивления мы сталкиваемся с тем фактом, что приложенные к телу силы, то есть вращение и перемещение — это не просто сумма приложенных сил по отдельности для вращения и перемещения. К сожалению, для этого пришлось выполнить несколько аппроксимаций. Вывод формулировки можно посмотреть в Приложении 1. Даже хотя мне пришлось добавить аппроксимации, удалось сохранить некоторые члены, вызываемые сочетанием вращения при перемещении, схожие с кориолисовой составляющей. Каждый сэмпл соответствует единичной линейной скорости относительно воздуха при 1 м/с или единичной угловой скорости относительно воздуха при 1 рад/с, в определённом направлении. В результате вместо того, чтобы непосредственно хранить силы и крутящие моменты для каждого сэмпла, мы храним вектор членов $\mathcal(\hat{u})$, используемых в формуле линейного вида. В Приложении 1 подробно написано, как это происходит. Компоненты этого вектора используются в формуле, которая также учитывает истинные линейные и угловые скорости объекта относительно воздуха при вычислении сил и крутящих моментов, которые нужно приложить к объекту.

куб, грани которого разделены на сетки размером, допустим 3x3 ячейки, как у кубика Рубика. Для хранения таблицы поиска мы использовали кубическую карту (cube map), т.е. В каждом углу ячейки мы вычисляем вектор, идущий от центра куба к углу, и нормализуем этот вектор, чтобы получить единичное направление. Мы сохраняем значения в углах ячеек, поэтому каждая грань может хранить 4x4 значения. В результате у нас получается 6 таблиц (по одной на грань) из 16 элементов с 19 значениям float каждая, что немного больше 7 КБ, и это очень удобно. Мы используем этот вектор единичного направления как $\mathcal{A}(\hat{u})$ для угла ячейки. Техника кубических карт и билинейной интерполяции очень широко используется в рендеринге, поэтому найти информацию о ней и соответствующий код очень легко. Существуют способы дальнейшего уменьшения количества сэмплов, но при таком способе очень просто интерполировать направление произвольной скорости: так как оно всегда попадает в ячейку, то всегда возможно использовать значения в четырёх углах этой ячейки, чтобы выполнить билинейную интерполяцию для этого конкретного направления.

Рисунок 1. Кубическая карта единичных скоростей относительно воздуха. На этом рисунке я выделил красным отдельную ячейку кубической карты, чтобы проиллюстрировать принцип. Имея линейную скорость $\hat{\omega}$ относительно воздуха, мы снова сэмплируем ту же кубическую карту, чтобы найти синюю ячейку и использовать 4 коэффициента, хранящихся в её углах. Затем мы смешиваем все значения по формуле в Приложении 1.

Наши решения этих проблем можно найти в Приложениях 2 и 3. Хотя в большинстве случаев это работало, мы столкнулись с трудностями изменения центра масс или масштаба объектов во время выполнения.

Параллельно мы начали создавать новые ассеты разрушений для Havok, имеющие сотни обломков различных размеров, иногда по несколько сотен частей для одного ассета. При разработке этой модели я не был уверен относительно количества сэмплов, достаточных для получения хорошего поведения объектов, движущихся и вращающихся в воздухе, поэтому я точно не знал, хватит ли у меня памяти на диске и во время выполнения. Общая модель хорошо передавала некоторые уникальные характеристики сложных объектов, но была перебором для мелких обломков. Меня озарило, что некоторые из этих ассетов потребуют столько же памяти, столько все другие ассеты в сумме, если каждый фрагмент будет иметь собственную кубическую карту. Поэтому я нашёл очень компактную формулировку (делающую щедрые аппроксимации), основанную только на ограничивающих параллелограммах (bounding box) объектов. Кроме того, я не был уверен в затратах во время выполнения и возможность вернуться к более малозатратной модели было хорошим способом снижения риска. Её можно найти в Приложении 4.

Объёмы ветра

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

  • Цилиндрический объём ветра, используемый для вьюги, песчаной бури и тропических штормов
  • Объём ветра торнадо — вертикальный набор цилиндров, центрированный относительно деформирующегося со временем вертикального сплайна
  • Туннели ветра, составленные из нескольких соединённых капсулообразных фигур (капсул с отличающимися на разных концах радиусами), образующих что-то вроде «червоточины»

Это ограничение ветра конечным объёмом несколько раз критиковали, и в последний момент едва от неё не отказались, заменив на «топографический ветер». Присутствующий повсюду ветер должен был соответствовать перемещению облаков в верхних слоях атмосферы и обеспечивать игроку восходящий поток воздуха при встрече с повышающимся наклоном рельефа. Мы реализовали его, но отсутствие чёткого графического отображения ветра заставило нас отказаться от этой функции. У ребят из отдела рендеринга и так уже было слишком много задач по реализации облаков, рек, водопадов, апгрейду до DirectX 12 и многого другого.

С другой стороны, торнадо и туннели ветра оказались сложнее и заслуживают более подробного объяснения. В ретроспективе оказалось, что штормы стали самыми простыми объёмами ветра, как с точки зрения формы (цилиндр), так и распределения ветра (почти однонаправленное для песчаной бури, или вращающееся вокруг центральной оси для вьюги).

Торнадо

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

Рисунок 2. Наше величественное торнадо — шейдеры создавали Габриэль Сассоне. Энгин Силасун, Стивен Юэн, Эдди Гонг и Хэмиш Янг. Все они внесли свой вклад на разных этапах разработки. Время ускорено неравномерно, чтобы показать вам красивую картинку днём и пропустить ночные этапы.

Хэмиш настраивал кривые по своему вкусу, бросая десятки 12-метровых контейнеров вдоль торнадо, чтобы посмотреть на их поведение. Как и со многими другими функциями, и как это часто бывает в нашей индустрии, мы с Хэмишем часто переходили от настройки к коду, пока результат не оказался достаточно хорошим. Спустя какое-то время Хэмиш обнаружил, что его настройка усложнялась, потому что необходимо было сделать так, чтобы ветер повсюду соответствовал простому вращательному движению, но в то же время притягивал объекты в центр. Он использовал для торнадо максимальную зарегистрированную скорость ветра (примерно 230 миль/ч). Это гарантировало, что, например, объекты застрявшие в центре, будут вращаться на месте, как мы и хотели. Угловая скорость уже была частью поля, не только толкающей, но и вращающей объекты в торнадо. После этой последней настройки кривые стали состоять из следующих компонентов: Поэтому Хэмиш попросил начать с ветра, повсюду соответствующего этой угловой скорости в центре, то есть начать с поля скоростей идеального поля вращения, в которое может добавляться настраиваемое поле ветра.

Где $h$ — высота над нижней частью торнадо, а $d$ — расстояние от центра. Скорость ветра торнадо задаётся уравнением:

$\vec{V}(d, h) = [V_t \mathcal{T}(d) \mathcal{H}(h) + \Omega d]\hat{t} + V_r \mathcal{R}(d) \mathcal{H}(h) \hat{r} + V_u \mathcal{V}(d) \Phi(h) \hat{up}$

Когда объекты ускорились до касательной скорости торнадо на заданном расстоянии, касательная составляющая сил сопротивления воздуха по сути равна нулю, потому что и воздух, и объект движутся по касательной в унисон. Оставшаяся часть скорости ветра втягивает объект внутрь, как планета притягивает спутники, чтобы изогнуть его траекторию так, чтобы он остался на орбите; также объекты толкаются вверх, чтобы достичь бОльших высот. Подталкивание объектов вверх — это важнейшая оптимизация производительности, предотвращающая затратные вычисления контактов сотен объектов и земли. Помогает также и то, что с высотой торнадо увеличивает радиус, пространственно распределяя объекты, чтобы снизить вероятность их столкновения и вычисления контакта. Торнадо в реальном мире образуется обменом воздуха, когда слой холодного воздуха находится поверх слоя тёплого. Тёплый воздух поднимается вверх подобно водовороту вокруг центра торнадо, который является спускающимся вниз холодным воздухом. Поэтому в принципе, в центре воронки скорость ветра должна быть сильно направлена вниз. Как видите, у нас это не реализовано, чтобы снизить вероятность отталкивания объектов вниз, к земле и втягивания в торнадо, иначе это увеличило бы затратные вычисления коллизий.

Рисунок 3. Поле ветра торнадо в горизонтальном разрезе (наверху), и в вертикальном резрезе (снизу). Два разреза слева не учитывают член $\Omega d$ вращающегося поля ветра, с которого мы начинали, чтобы лучше понять, нужно ли дополнительное поле для правильной работы торнадо. В версии справа вращающееся поле добавлено. На расстоянии мы видим очень быстрый ветер, в любой точке направленный примерно под 45 градусами от радиуcа. Вертикальный разрез слева показывает, насколько сильно торнадо должно втягивать в центр. Затем есть небольшой переходный интервал, в котором почти нет ветра, а за ним идёт сильное вращающееся поле в центре. Сила поля ветра показана цветом, чем краснее, тем сильнее.

Рисунок 4. Линии путей вокруг поля ветра торнадо — центральный сплайн прямой.

Чтобы деформировать его, мы использовали 2 соединённых концами кубических сплайна, 3 контрольных точек которых находятся на орбите и движутся с разными скоростями вокруг воображаемой вертикальной линии. Прямое торнадо кажется скучным. В Приложении 5 подробно рассказано, как мы создали сплайны. Получившийся профиль торнадо со временем меняется, поэтому оно медленно сдвигается от S-образной формы к C-образной, и обратно. Объекты, кружившиеся по орбите прямого торнадо, теперь разлетались, как только центральный сплайт торнадо удалялся от них, и печально падали на землю. Интересно, что торнадо в режиме с деформацией работало не так хорошо. В конце концов, эта деформация должна в первую очередь возникать из-за движения воздуха, и нам показалось естественным добавить эту составляющую. Чтобы устранить эту проблему, мы добавили в скорость ещё один член, возникающий из-за самой деформации торнадо. Это красиво решило проблему, дав нам линии путей, очень похожие на показанный ниже рисунок.

Рисунок 5. Линии путей вокруг поля ветра торнадо — центральный сплайн со временем изгибается

Рисунок 6. Пример торнадо в игре, оно уничтожает и всасывает в себя всё на своём пути. Это визуальный отладчик Havok (VDB), чрезвычайно полезный инструмент, позволяющий нам наблюдать за симулируемыми элементами.

Туннели ветра

Туннели ветра были разработаны для обеспечения контроля над физическим ветром в отдельных частях мира, например, в каньонах и пещерах, а также столбов дыма перед огромными промышленными вентиляторами и ветровыми пушками. Их можно использовать, чтобы направлять игрока в определённых пространствах или миссиях. В наших первых прототипах использовался традиционный цилиндр с равномерным векторным полем постоянной скорости параллельно оси вращения цилиндра. Размещение всех этих цилиндров в сцене было кропотливой задачей, поэтому вместо этого мы использовали кубический сплайн Безье, вокруг которого растянут круг варьирующегося радиуса. Эта форма аппроксимируется разделением центрального сплайна на капсулообразные фигуры, т.е. на капсулы с разными радиусами по краям. Поле скоростей в них тоже претерпело некоторые изменения. Поначалу ветер в туннеле был расположен по касательной к центральному сплайну. Мы задавали скорости ветра в каждой контрольной точке сплайна, и они интерполировались из одной контрольной точки в другую вдоль отрезка сплайна. Однако вскоре Джошуа Эспиноса (дизайнер, занимавшийся туннелями ветра и перемещениями игрока по ним) выяснил, что ему нужны ещё два компонента. Один компонент — это восходящий поток, дающий игроку дополнительный искусственный подъём, всегда направленный вверх. Другой компонент мы назвали «втягиванием», он толкает игрока к центральному сплайну, но сильнее всего на краях туннеля, к центру снижаясь до нуля. Наконец, граница уменьшения во внешней части туннеля обеспечила плавный переход от внешней к наружной части туннеля. Толщина этой границы задаётся как процент от радиуса; она действует вдоль всего туннеля ветра.

Рисунок 7. Туннель ветра, разделённый на последовательность пересекающихся капсулообразных фигур. Разделение возникает, когда меняется направление сплайна или когда больше невозможно выполнять линейную продольную интерполяцию радиуса (A) или скорости ветра (B). Мы вычислили вторую производную этих переменных вдоль сплайна, чтобы определить, когда нужно выполнять разделение.

Фигура 8. Туннели ветра, отредактированные в Apex Engine. Туннель ветра разбит на капсулы правильным образом, зависящим от сплайна, изменений радиуса и скорости ветра вдоль сплайна. Как сказано в следующем разделе, можно также увидеть пространственное разделение, которое сортирует капсулообразные фигуры в кривые Мортона. Эти вычисления достаточно быстры для обновления в реальном времени; кроме того, с туннелем ветра можно быстро взаимодействовать для его тестирования. Инструмент редактирования был изготовлен переделкой некоторых частей инструмента создания рек с добавлением отладочной отрисовки.

Рисунок 9. Пример длинного туннеля ветра, позволяющего игроку перемещаться вверх по течению реки к горе. Речное течение подталкивает Рика к морю, а ветер может быстро вернуть его внутрь суши поверх той же реки. Отдел, занимающийся механиками игрока, заметил, что повороты Рико боковым ветром были слишком резкими, поэтому основное влияние ветра заключается в ускорении игрока (попутным ветром) или в торможении (встречным ветром) и придании ему дополнительного подъёма (составляющая восходящих потоков).

Рисунок 10. Рико в вингсьюте в том же туннеле ветра. Здесь игрок никак не управлял направлением. Поначалу Рико теряет высоту, пока не достигает внешней части туннеля ветра, которая постепенно замедляет его снижение. Затем центральная часть создаёт достаточный восходящий поток, чтобы преодолеть гравитацию и быстро ускоряет его вверх по каньону. Код вингсьюта из JC3 для правильного взаимодействия с ветром был усовершенствован Джошуа Эспиноса, Хэмишем Янгом и Рикардом Гранфелдом.

Оптимизация запросов ветра

При потенциальном наличии сотен активных тел вычисление локального ветра в их позиции должно быть быстрым процессом. Мы начали оптимизацию запросов с вычисления AABB (axis aligned bounding box) для каждого объёма ветра и сохранения их в плотно упакованном массиве. Это обеспечивает быстрое отбрасывание грубым перебором, потому что можно очень быстро проверить позицию относительно каждого AABB без промахов кэша. В каждом кадре AABB объёмов ветра в массиве обновляется для подготовки к запросу.

Для торнадо нужно провести больше вычислений, потому что наверху оно намного шире, чем внизу, в то время как наибольшая плотность объектов находится внизу. Если позиция находится внутри объёмов шторма, то мы напрямую вычисляем влияние ветра с помощью цилиндрической формы. Они намного ближе передают форму торнадо и применяются для отсечения объектов до вычисления ветра. Решение с AABB не очень хорошо отсекает объекты поблизости от торнадо, поэтому мы использовали поставленные друг на друга цилиндры. Это было возможно, потому что хотя сплайн торнадо — это интерполируемые в 3D точки, а потому он является параметрической 3D-кривой, на практике интерполируемые им контрольные точки равномерно и последовательно распределены вдоль вертикали, что даёт нам достаточно простую кривую. Последняя оптимизация торнадо заключалась в том, чтобы избегать проецирования позиции запроса на центральный сплайн. Но вместо этого мы решили изменить способ сэмплирования сплайна, использовав в качестве параметра сэмплирования самого сплайна высоту над нижней точкой торнадо. В общем случае, проецирование точки на 3D-сплайн или поиск точки на этом сплайне с той же высотой требует поиска вдоль этого сплайна. Но при этом для нахождения точки на сплайне на той же высоте сводится к передаче ему высоты позиции запроса с последующей фиксацией высоты точки в высоте запроса. Это немного изменяет форму сплайна, но сплайн по-прежнему проходит через точки интерполяции. Также мы могли изменить способ построения центральной кривой, использовав вместо параметрической кривой кубическую функцию высоты, но, как и многие другие оптимизации, эта появилась слишком поздно. Для контрольных точек, расположенных неравномерно вдоль вертикальной оси, разница заметна, но когда они расположены равномерно, то она едва видна.

К счастью, нам удалось воспользоваться технологией, параллельно реализованной для рек Энгином Силасуном: разреженной пространственной базой данных. Туннели ветра тоже требовали оптимизации, потому что проецирование каждой точки запроса на центральный сплайн каждого туннеля ветра даже не рассматривалось. Если ячейка не находится в пространственной базе данных, то в точке запроса нет ветра, вызванного туннелями ветра. Мы делим пространство на 32 $O(\log(n))$. Кроме того, эта проверка должна быть довольно быстрой, и поэтому мы урезали туннели ветра до капсулообразных фигур. Если ячейка найдена, то нужно выполнить более затратную проверку. Капсулообразные фигуры немного более затратны, но всё равно быстрее, чем нахождение кратчайшего расстояния до кубического сплайна. В общем случае используются капсулы, потому что можно с малыми затратами вычислять расстояние до их центрального сегмента. Ещё одно свойство пространственно целостной системы туннелей ветра заключается в том, что мы можем запомнить индекс ячейки определённого объекта, находившегося в предыдущем кадре, и использовать его как подсказку для ускорения выполнения запроса в следующем кадре, потому что вероятнее всего он уже находится в этой или соседней ячейке.

Рисунок 11. Капсулообразные фигуры туннелей ветра вводятся в разреженную пространственную базу данных в соответствии с ячейками, которые они пересекают. В результате получается массив из 9 ячеек, в каждой из которых есть только несколько капсулообразных фигур.

В заключение

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

Например, лист под углом 45 градусов будет генерировать бОльшую подъёмную силу, чем в нашей грубой модели. Модель сопротивления воздуха чрезвычайно упрощена и не отражает некоторые важные свойства реального течения жидкости. На самом деле мы проверили вычисления сил и крутящих моментов, генерируемых на всей кубической карте тестового объекта (винтажного автомобиля из JC4), использовав ПО вычислительной гидродинамики с открытым кодом (OpenFOAM). Но в ней хорошо то, что силы и крутящие моменты не вычисляются во время выполнения, а решение с кубической картой позволяет избежать более подробной симуляции. Однако для вычислений ПО потребовалось несколько часов, поэтому очевидно, что такое решение не подходит для использования на всех объектах. Сравнение показало, что мы не слишком отдалились от верных результатов, но «форма» кубических карт была другой, и я ожидаю, что она ещё сильнее будет отличаться для объектов в форме крыла. Но я думаю, что для игр на основе физики было бы очень интересно иметь быстрые, но реалистичные аэродинамические коэффициенты для всех объектов. Наш метод занимал меньше секунды на объект. Я вкратце поигрался с этой идеей, понаблюдав за быстрым падением и вращением семян-вертолётиков клёна, и создал модель в Maya, чтобы посмотреть, что произойдёт. Допустим, Рико мог бы собирать объекты в огромные бумеранги. Но мне кажется, для более быстрого вращения необходимо будет значительно улучшить модель сопротивления воздуха. Результат оказался лучше, чем ожидалось, и объект действительно медленно вращался при падении. Как это часто бывает, нам просто не хватило времени.

Рисунок 12. Слева: все ориентации винтажного масл-кара из Just Cause 4, помещённого в виртуальный туннель ветра для заполнения кубической карты с сеткой 5x5 на каждой грани. Справа: визуализированные в Paraview результаты для двух ориентаций — под углом сверху (наверху) и сзади (внизу). Красным показано высокое давление, синим — низкое, линии обозначают потоки.

Деформацию торнадо можно ещё улучшить. Торнадо и штормы сработали очень эффективно. У нас была ещё одна идея — разработать пушку, создающую мини-торнадо. Если бы мы уделили этому больше времени, то, вероятно, смогли бы сделать водяные смерчи (естественно, с акулами вместо коров), водовороты или огненные торнадо.

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

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

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

Приложение 1: модель сопротивления воздуха

Мы рассматриваем тело $\mathcal{B}$, заданное мешем $T_i, i \in [0, N-1]$.

Рисунок 13. Тело $i \in [0, N-1]$.

Рассмотрим треугольник $\vec{V}_i$:

$\vec{V}_i = \vec{V} + \vec{\Omega}\times\vec{OC_i} = V_i \ \hat{v_i}$

Рисунок 14. Треугольник и скорости в его центре.

Именно здесь нам нужно учитывать поставленные нами требования: Теперь нам нужно записать элементарную силу $\vec{F}_i$, действующую на этот треугольник.

$\vec{F}_i = - A_i \ V_i \ (\vec{V}_i \cdot \hat{n}_i)\ \hat{n}_i$

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

$\vec{F} = \sum{\vec{F}_i} = \sum{- A_i \ V_i \ (\vec{V}_i \cdot \hat{n}_i)\ \hat{n}_i}$

Так как мы стремимся сократить всё до 1 м/с и 1 рад/с, давайте перепишем с $\hat{\omega}$:

$\vec{F} = \sum{- A_i \ \|V\hat{v} + \Omega\hat{\omega}\times\vec{OC_i}\| (V\hat{v} + \Omega\hat{\omega}\times\vec{OC_i}) \cdot \hat{n}_i\ \hat{n}_i}$

А теперь первая большая аппроксимация:

$\|V\hat{v} + \Omega\hat{\omega}\times\vec{OC_i}\| \approx V + \Omega\| \hat{\omega}\times\vec{OC_i} \| $

Это эквивалентно аппроксимации длины гипотенузы треугольника суммой длин двух его сторон. Эта аппроксимация также известна как «расстояние городских квартатов» (Манхэттенское расстояние). Когда я работал над этой задачей, как раз находился на Манхэттене, так что было бы глупо её не использовать. Итак, мы имеем следующее:

$\vec{F} = \sum{- A_i \ (V + \Omega \| \hat{\omega}\times\vec{OC_i} \|) (V\hat{v} + \Omega\hat{\omega}\times\vec{OC_i}) \cdot \hat{n}_i\ \hat{n}_i}$

Разложим:

$\vec{F} = V^2 \sum{ -A_i \hat{v} \cdot \hat{n}_i \hat{n}_i } +\Omega^2 \sum{ -A_i \|\hat{\omega}\times\vec{OC_i}\|(\hat{\omega}\times\vec{OC_i})\cdot \hat{n}_i\hat{n}_i } \\ +V\Omega \sum{ -A_i (\hat{\omega}\times\vec{OC_i}) \cdot \hat{n}_i \hat{n}_i } +\Omega V \sum{ -A_i \|\hat{\omega}\times\vec{OC_i}\|(\hat{v}\cdot\hat{n}_i)\hat{n}_i }$

Поначалу это может казаться пугающим. Но потом вы замечаете, что именно это мы и искали. Заметьте, что в суммах встречается только $\|\hat{\omega}\times\vec{OC_i}\|$ с его средним значением среди всех треугольников:

$\|\hat{\omega}\times\vec{OC_i}\| \approx Avg(\|\hat{\omega}\times\vec{OC_i}\|)_{i\in[0,N-1]}$

Давайте обозначим это за $\hat{\omega}$. Теперь мы можем наконец записать:

$\vec{F} = V(V+\Omega\ \overline{v_r}(\hat{\omega}))\vec{F}_t(\hat{v}) +\Omega^2 \vec{F}_r(\hat{\omega}) +V\Omega \vec{F}_c(\hat{\omega}) $

где:

$\begin{align*} \vec{F}_t(\hat{v}) &= \sum{ -A_i \hat{v} \cdot \hat{n}_i \ \hat{n}_i }\\ \vec{F}_r(\hat{\omega}) &= \sum{ -A_i \|\hat{\omega}\times\vec{OC_i}\|(\hat{\omega}\times\vec{OC_i})\cdot \hat{n}_i \ \hat{n}_i }\\ \vec{F}_c(\hat{\omega}) &= \sum{ -A_i (\hat{\omega}\times\vec{OC_i}) \cdot \hat{n}_i \ \hat{n}_i }\\ \overline{v_r}(\hat{\omega}) &= {1\over N}\sum{ \|\hat{\omega}\times\vec{OC_i}\| }\end{align*}$

Момент этих сил относительно точки $OO$ можно получить схожим образом. И в результате мы получим следующее:

$\left.\vec{M}\right|_O(\hat{v}, \hat{w}) = V(V+\Omega\ \overline{v_r}(\hat{\omega}))\vec{M}_t(\hat{v}) +\Omega^2 \vec{M}_r(\hat{\omega}) +V\Omega \vec{M}_c(\hat{\omega})$

где:

$\begin{align*} \vec{M}_t(\hat{v}) &= \sum{ -A_i \hat{v} \cdot \hat{n}_i \ (\vec{OC_i}\times\hat{n}_i) }\\ \vec{M}_r(\hat{\omega}) &= \sum{ -A_i \|\hat{\omega}\times\vec{OC_i}\|(\hat{\omega}\times\vec{OC_i})\cdot \hat{n}_i \ (\vec{OC_i}\times\hat{n}_i) }\\ \vec{M}_c(\hat{\omega}) &= \sum{ -A_i (\hat{\omega}\times\vec{OC_i}) \cdot \hat{n}_i \ (\vec{OC_i}\times\hat{n}_i) }\end{align*}$

С учётом изложенных выше определений теперь мы можем образовать вектор $\mathcal{A}(\hat{u})$:

$\mathcal{A}(\hat{u}) = \begin{bmatrix} \vec{F}_t(\hat{u}) \\ \vec{F}_r(\hat{u}) \\ \vec{F}_c(\hat{u}) \\ \vec{M}_t(\hat{u}) \\ \vec{M}_r(\hat{u}) \\ \vec{M}_c(\hat{u}) \\ \overline{v_r}(\hat{u}) \end{bmatrix}$

Это вектор 19 значений и мы ссылаемся на таблицу сэмплов $\hat{w}$, но можем найти достаточно близкие значения, а потом или использовать непосредственно их, или применить их для интерполяции выходных данных. И на этом этапе в дело вступает кубическая карта, описанная в статье.

Приложение 2: центр масс

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

$\vec{F} = \sum{\vec{f}_i}$

Моменты $O$ задаются как:

$\left.\vec{m}_i\right|_O = \vec{OP_i} \times \vec{f}_i$

а общий момент этих сил относительно точки $O$ равен:

$\left.\vec{M}\right|_O = \sum{\left.\vec{m}_i\right|_O} = \sum{\vec{OP_i} \times \vec{f}_i}$

Теперь это просто математическое определение, формула, которую нельзя напрямую использовать для изменения движения тела, потому что центр гравитации $G$ в этой формуле по теореме Шаля равен:

$\left.\vec{M}\right|_O = \sum{(\vec{OG} + \vec{GP_i}) \times \vec{f}_i} = \sum{\vec{OG} \times \vec{f}_i} + \sum{\vec{GP_i} \times \vec{f}_i}$

Мы узнаём момент сил относительно $\vec{F}$, поэтому:

$\left.\vec{M}\right|_O = \vec{OG} \times \vec{F} + \left.\vec{M}\right|_G$

И это очень полезно для нас, если мы вычислили общую силу $\vec{F}$ без изменений и приложили к центру масс следующий крутящий момент:

$\left.\vec{M}\right|_G = \left.\vec{M}\right|_O + \vec{GO} \times \vec{F}$

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

Приложение 3: масштабирование

Ещё один изъян проявился, когда в игру ввели надувные шары. Эти шары являются сферическими телами, размер которых может меняться с коэффициентом 5 или 10. Как и в случае с центром масс, было слишком затратно пересчитывать аэродинамические свойства во время выполнения. Можно ли вывести аналитическую формулу?

Рисунок 15 Тело $\vec{O_sC_{si}} = s\ \vec{OC_i}$

Получим: Рассмотрим ещё раз тело $\vec{F}$, но теперь для отмасштабированного объекта.

$\vec{F_s} = V^2 \sum{ -A_i \hat{v} \cdot \hat{n}_i \hat{n}_i } +\Omega^2 \sum{ -A_i \|\hat{\omega}\times\vec{OC_{si}}\|(\hat{\omega}\times\vec{OC_{si}})\cdot \hat{n}_i\hat{n}_i } \\ +V\Omega \sum{ -A_i (\hat{\omega}\times\vec{OC_{si}}) \cdot \hat{n}_i \hat{n}_i } +\Omega V \sum{ -A_i \|\hat{\omega}\times\vec{OC_{si}}\|(\hat{v}\cdot\hat{n}_i)\hat{n}_i }$

Использовав те же аппроксимации, что и в первый раз, и заменив $s\ \vec{OC_i}$, мы получаем:

$\vec{F_s} = V(V s^2+\Omega s^3\ \overline{v_r}(\hat{\omega}))\vec{F}_t(\hat{v}) +\Omega^2 s^4 \vec{F}_r(\hat{\omega}) +V\Omega s^3 \vec{F}_c(\hat{\omega})$

Для центра масс отмасштабированного тела $G_s$.

Приложение 4: упрощённая модель сопротивления для параллелограмма

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

$\vec{F} = - \rho V^2 \left< e_y e_z, e_x e_z, e_x e_y \right> \cdot \hat{v}$

$\vec{M} = - {{\rho}\over{6}} \Omega^2 e_x e_y e_z \hat{w} \cdot \left< e_y^2+e_z^2, e_x^2+e_z^2, e_x^2+e_y^2 \right>$

Приложение 5: сплайн центра торнадо

Торнадо центрировано относительно центральной кривой, форма которой медленно изменяется. Мы реализовали это с помощью двух последовательных кубических сплайнов Безье: $\mathcal{C}_0$ для нижней части и $\mathcal{C}_1$ для верхней. Сплайн $\mathcal{C}_0$ имеет контрольные точки $P_0$, $P_1$, $P_2$ и $P_3$, а $\mathcal{C}_1$ контролируется точками $P_4$, $P_5$, $P_6$ и $P_7$. Позиция самого торнадо находится в точке $P_0$ на земле, а верхняя точка $P_7$ начинается прямо над уровнем слоя облаков, но запаздывает во времени, медленно следуя за положением на земле. Так как кривые соединены одним концом, $P_3$ и $P_4$ совпадают в пространстве. Также мы использовали 3 горизонтальные контрольные орбиты для точек $P_1$, $P_2$ и $P_5$. Наконец, $P_3$ была расположена ровно в средней точек между $P_2$ и $P_5$, а $P_6$ в средней точке между $P_5$ и $P_7$.

Создание сплайна центра торнадо

Эволюция сплайна центра торнадо во времени

Справочные материалы

  1. Just Cause 4
  2. Havok
  3. Foundations of Multidimensional and Metric Data Structures. Hanan Samet
  4. Z-order curve
  5. Cube Mapping
  6. Open FOAM
  7. Volta
  8. Ironklad Studios
Показать больше

Похожие публикации

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»