В своей лекции он уделил внимание основному принципу построения многих внутриигровых эффектов, или принципу гранулярности. О том, как студия реализовала масштабную систему реалистичной разрушаемости, с какими ограничениями собственных ресурсов и производительности платформ столкнулась, какие оптимизации произвела и какие уроки из всего этого вынесла — далее в материале.
Итак, сначала о вызовах, с которыми столкнулась студия.
Действие игры происходит внутри здания правительственного агентства в бруталистском стиле, имеющего некоторые сверхъестественные черты — например, движущиеся стены.
Устройство штаба должно было выглядеть правдоподобно, ведь это правительственное агентство, где работают тысячи сотрудников службы, выполняющие рутинные поручения. Столы, телефоны, кружки, МФУ — все это обычные для офисного работника атрибуты, которые ожидаешь увидеть на рабочем месте, и своим присутствием они помогают качественно рассказать историю этого самого места. Брутализм подразумевает тонны бетона, но и не только: здесь у нас и дерево, и стекло, создающие наиболее подходящий облик для здания спецслужбы.
Когда речь заходит о разрушении, в первую очередь стоит задуматься о тактильности. Перед командой разработчиков стояла задача организовать богатое интерактивное окружение, сразу создающее ощущение возможности взаимодействия практически с чем угодно в его пределах.
Очевидно, студия при работе столкнулась и с определенными ограничениями. Взаимодействия с предметами должны были выглядеть и ощущаться реалистично. Игрок должен обладать определенной свободой действия касательно разрушений, но не безграничной, поскольку возможности игры упираются в конечные производительность платформ, память и требования к искусственному интеллекту. При этом команда, на которую была возложена задача реализовать разрушаемое окружение, оказалась совсем небольшой, что тоже необходимо было учитывать в работе.
Итак, в основе внутриигровой разрушаемости лежит принцип гранулярности. Он же заложен и в основу многих спецэффектов в кинематографе. Смысл его заключается в том, что природа не определяется количественно. Это непрерывное полотно, создаваемое из широкого спектра объектов — от больших до маленьких, от масштабных твердых тел до пыли и дыма. Если что-то их этого не отражать на экране, цельной картины не сложится.
В игровом движке этот принцип можно реализовать в виде трех различных уровней детализации. Объекты на них представлены в виде твердых тел (Rigid Bodies), их деталей, частей пропсов, самих пропсов и окружения. Последнее в данном случае представляет собой своеобразную статическую сетку, с которой могут столкнуться интерактивные объекты. Частицы сетки, иерархия твердых тел и декали материалов придают объектам большую детализацию на определенных слоях. Так, от цельных объектов мы переходим к их фрагментам и затем к обломкам. Последний слой — непосредственно частицы. Спрайты частиц, частицы от тлеющих углей, песок и прочее — все это играет большую роль при заполнении подобных градиентов.
На скриншоте выше показано статичное окружение. Выглядит довольно пусто, хотя некоторая детализация здесь есть: например, на заднем плане можно заметить перила для лестницы.
Удивительно, как меняется восприятие, когда мы начинаем заполнять пространство объектами, с которыми можно непосредственно взаимодействовать.
Что касается рабочего процесса в Remedy, на самом деле он довольно тривиален. Художники по окружению предоставляют модули геометрии уровня и пропсы для сборки, после чего VFX-отдел производит настройку ригов и кинематографической анимации разрушения. Наконец, после этого результат отправляется в собственный движок Remedy — Northlight.
Было необходимо определиться с подходом, как все будет работать, и команда остановилась на процедурном.
Что это значит?
Процедурный подход — это основанная на правилах обработка и интерпретация данных.
Информация об игровом мире представлена моделями, содержащими метаданные о материалах. Так, можно задать, например, что сиденья скамьи сделаны из ткани, основание — из бетона, растения — собственно, растения. Определив материалы, можно сформулировать конечный набор правил для каждого из них, определяющий реакции на все действия, которые могут производиться в игре. Например, чтобы при стрельбе от растений отлетали кусочки листьев, бетон разбивался на осколки, а металлическая труба деформировалась, и из нее прорывалась вода. Затем все данные перенаправляются в движок, и он уже на каждое взаимодействие реагирует надлежащим образом.
Так почему именно процедурное разрушение?
Потому что был необходим быстрый и последовательный оборот действий, предсказуемое поведение в рамках четко определенных условий. В игре задействованы сотни ассетов. На изображении выше вы можете увидеть всевозможные блоки, из которых состоят комнаты, стены, колонны, лестницы, перила и многое другое. Под ним — различные пропсы: столы, стулья, вазы, растения, компьютеры, телефоны. Для реализации разрушения такого разнообразия объектов выделена команда лишь из 1-3 человек. Поэтому было необходимо предопределить паттерны, по которым работает мир: если на объект воздействуют определенным образом, нужно, чтобы он ломался именно так, как это прописано для выбранного способа разрушения данного материала.
Итак, необходимо было задать определенное поведение в зависимости от материала. Чтобы, когда вы стреляете бы по дереву, оно бы разлеталось на щепки. Или, если вы стреляете по стеклу, оно бы разбивалось на осколки. При этом частицы и декали тоже должны вести себя определенным образом в соответствии с тем, из чего сделан предмет.
Для каждого материала существует своя геометрия разрушаемости, задаваемая различными уровнями. На примере мы видим кусок перил, в основе которого — бетон, затем — металлическую подпорку и, наконец, дерево. Слева направо показаны этапы, как они ломаются:
- Уровень А показывает разлом бетона. Декалей здесь нет, поскольку трещин еще немного. Видно, что подпорка немного погнута.
- Уровень B. Металла уже нет, но остаются еще больше поломанные бетон и дерево.
- Уровень C опционален: когда он есть, представляет собой еще больше разломов.
Теперь представим, что мы ударяем по определенному углу объекта — тогда он не должен разбиться полностью, только его часть.
Итак, в Control существуют твердые тела, представляющие собой цельный объект. Но еще есть детали, соединенные связями. Это те же твердые тела, которые может разделить так называемое составное столкновение.
Детали создаются во время инициализации, имеют общий коллайдер и движутся как одно целое до тех пор, пока не сломаются. Они соединены друг с другом теми поверхностями, которыми соприкасаются между собой.
Поговорим о соединениях. Они создаются в геометрической иерархии на основе метаданных. Твердые тела связываются друг с другом неким шарниром — например, в случае с дверью или выдвижным ящиком. Их можно динамически разрушить, опять-таки, за счет силы импульса.
Для соединений существует особая физика разрушения. Они не ломаются вместе с объектом — то есть, если проделать дыру в двери, дверь все еще останется целым объектом, удерживающимся на счет внутренних соединений. Таким образом, если разбить родительский блок RB1, дверь не сорвется с петель: к проему все еще будет прикреплен ее кусок, не затронутый ударом. И дверь с дырой посередине все еще сможет закрываться и открываться, как и положено. Таким образом разработчики хотели избежать ситуаций, когда объекты ломаются полностью вне зависимости от того, куда и какой силы пришелся удар, как это бывает в некоторых играх.
В процессе моделирования в собственном движке Northlight выполняется логика разрушения и определяется, какие события и частицы на нее реагируют. Затем физический движок NVIDIA моделирует твердые тела и старается вписать их в существующие для игры ограничения.
Само разрушение реализуется следующим образом. У нас есть некоторая входная геометрия. Иногда необходимо предварительно подготовить модель, задать геометрию склеивания и определить, в каких случаях какие детали могут сломаться. Затем модели отправляются в Houdini и обрабатываются там. Разрушение в Houdini — довольно масштабная установка формата HDA, выполняющая реакции на основе материалов и записывающая данные в память. Иногда приходилось вручную исправлять и устанавливать некоторые метаданные физики, чтобы убедиться, что настройки верны, особенно когда дело касалось соединений. Затем все данные передаются в движок, где используются для создания игрового мира.
Инструмент разрушения в Houdini выглядит примерно так. Допустим, у нас есть входные данные в виде бетонного блока. Необходимо определить, какие области могут сломаться, и задать материал. В данном случае блок будет выполнять разрушение по правилам, заданным для бетона, управлять им и создавать различные иерархии с точки зрения геометрии рендеринга и столкновений. Затем необходимо убедиться, что моделирование производится в рамках имеющегося бюджета и заданного стиля. После этого можно экспортировать модель в движок.
В движке это выглядит так. У вас есть некая иерархия, несущая информацию о слоях A, B, C и т. д. Сюда входит название материала, статичный ли объект или нет, данные о соединениях, их типах и прочем. Иерархия представлена уровнями, а физические свойства различаются в зависимости от наименования материала. При правильном задании имени физика обрабатывается движком. О проблеме имен мы еще поговорим позже.
Выше показан сценарий симуляции твердого тела. Джесси стреляет в окружающие ее предметы, и они взрываются, тем самым реализуя физику разрушения.
Поскольку разрушаемое окружение — вещь ресурсоемкая, а консоли и ПК имеют свои пределы производительности, перед командой стояла задача оптимизации системы, чтобы она не слишком нагружала устройства.
Поскольку нужно было вписываться в определенный бюджет по производительности, был установлен лимит в 200 активных твердых тел на экране — таким образом, предметы за его пределами исчезали вовсе.
В случае событий, задействующих множество быстро движущихся объектов, имеет место задержка столкновений для того, чтобы система успела произвести все вычисления.
Также был реализован режим сна у неиспользуемых предметов. Допустим, если бетонный блок упадет на пол, никто не ожидает, что он начнет прыгать, подобно мячику, — так что довольно быстро он может «заснуть». Это относится ко многим предметам в игре. По той же причине их можно складывать друг на друга, и они точно так же будут лежать неподвижно.
Кроме того, пробелы между предметами были заполнены частицами. Таким образом, при разрушении объекта вокруг него образуется пыль или щепки.
В игре все системно и событийно. Существуют следующие события, связанные с частицами:
- удар пули, имеющий различный исход в зависимости от материала;
- разрыв связи между двумя деталями; в данном случае происходит слом, высвобождающий частицы;
- полное разрушение объекта, приводящее к его распаду на частицы.
Выше показан процесс редактирования частиц. Прямо в игре вы можете разместить некую систему частиц и затем изменить ее. В данном случае просто меняется частота формирования искр. Что интересно, изменить ее можно буквально в реальном времени и тут же получить мгновенный отклик, а затем проиграть снова и посмотреть, как срабатывает эффект. Реализованный таким образом быстрый цикл итераций позволяет отшлифовать подобные вещи, пока они не начнут отображаться правильно.
Еще одна особенность частиц — стандартное моделирование. Время от времени команде приходилось использовать поля расстояний со знаком (signed distance fields, SDF). Благодаря этому удалось добиться, чтобы объекты не проваливались сквозь пол, что выглядело бы крайне странно.
На примере выше разрушаемый предмет представляет собой симбиоз частиц и твердого тела. Именно это мы и видим. От взрыва в воздухе появляется пыль за счет дополнительных слоев частиц, заполняющих недостающие пробелы в градиенте гранулярности.
И последнее — декали материалов, коих в игре много и порождаются они динамически. По сути, это просто текстуры, которые накладываются поверх объектов для создания видимости разрушения.
Если что-то ломается, на предмете появится декаль с изображением трещин. Обычно они создаются в Houdini или его аналогах. В Control выбор нужной декали происходит динамически на основе материала. Это помогло также задействовать довольно большую часть статической геометрии. Как было показано в начале, вокруг нас всегда много статических объектов, которые тоже могут подвергаться некоему воздействию, которое необходимо учитывать.
Вот как это выглядит. Если вы разобьете пол, сам полигон останется тем же, но с появлением декалей его внешний вид может сильно измениться. Стоит заметить, что в использовании они довольно экономны и эффектны.
Итак, нас есть частицы, твердые тела и декали. В этом примере пришлось пойти на кое-какие ухищрения, потому что простой инструмент взрыва не породил бы столько декалей. Теперь Джесси «бросает» некий объект, способный оставить вмятину в полу. Пол при этом остается статичным полигоном, но благодаря декалям на нем остаются следы от удара.
Еще затронем тему кастомных пропсов. Существует множество внутриигровых предметов, которые можно разбросать — огнетушители, компьютеры, лампы и тому подобное, — которые нельзя полностью процедурно сгенерировать. Эффекты для каждого из них художникам по окружению все же приходилось задавать вручную. Впрочем, от их наличия внутриигровой мир выглядит только богаче и разнообразнее.
Итак, какие уроки извлекла студия, работая над Control?
Здесь стоит затронуть следующие вещи:
- качество геометрии входных данных;
- согласование наименований материалов;
- инструменты монолитного разрушения;
- мониторинг производительности.
Первое — это качество геометрии. Несогласованная входящая геометрия может возникать при неправильном масштабировании и ориентации, но также и из-за неправильного назначения материалов. Иногда качество сетки может оказаться слишком низким, и это тоже будет пагубно сказываться на результате. Еще случается, что, когда вы ломаете объект, то понимаете, что внутри него ничего нет, и это неправильно. Чтобы избежать таких проблем, необходимо улучшить входящие данные, стандартизировать весь пайплайн геометрии так, чтобы при экспорте система предупреждала, если что-то не соответствует критериям и требует исправления. Это помогло бы избежать постоянной петли обратной связи между различными отделами в поисках того, когда именно возникли проблемы.
Кроме того, неплохо бы иметь встроенные инструменты, чтобы можно было смоделировать объект и сразу увидеть, как он будет выглядеть при разрушении. Очевидно, это порождает проблему создания большего количества инструментов с более удобными интерфейсами, но стоит того, чтобы этим озаботиться.
Мы привыкли давать названия различным вещам. Но проблема в том, что эти названия могут быть некорректными. Например, в Control существует 17 различных вариантов обозначения материала «бетон», и в этом нельзя никого винить, ведь всегда имеет место человеческий фактор. Совет Рихтера — вообще отказаться от стандарта имен. Лучше просто завести единый API метаданных. Таким образом, независимо от того, какой инструмент художники используют для создания пропсов, можно экспортировать данные в движок прямиком оттуда без каких-либо промежуточных этапов.
Следующий урок по большей части специфический для Houdini. Суть в том, что зачастую, начиная работу над чем-то, вы это еще множество раз его переделываете в процессе, совершаете какие-то надстройки, и необходимо убедиться, что даже спустя два года работы вы сможете открыть исходный файл даже несмотря на то, что рабочий инструмент мог уже поменяться 20 раз. Это означает, что вам понадобится некая стандартизация работы с HDA. Именно над этим сейчас работают в Remedy: над тем, чтобы все было правильно распределено, и вы никогда не потеряли какую-либо версию инструмента и всегда имели возможность повторить то, что делали в прошлом.
Здесь важно заметить, что, создавая автоматизированные инструменты, вы по факту используете то же программное обеспечение, как если бы вы все делали вручную. И пока у них один и тот же бэкэнд, все должно быть полностью согласовано.
Производительность и тестирование — одни из самых существенных аспектов разработки.
Изначально в Remedy тесты не были автоматизированными. После добавления на уровень новых объектов его необходимо было пройти вручную, чтобы проверить, что все работает правильно. Но потом что-то менялось в движке, менялся бэкэнд, что-то оптимизировалось, и после этого требовалось повторное тестирование. Это довольно опасно, ведь что-то проверить вы непременно забудете. Словом, не лучший способ, приводящий к потенциальному накоплению багов.
Второй аспект — тестирование производительности. Долгое время в Remedy не измеряли какие-либо существенные показатели вроде частоты кадров или времени вычисления. Таким образом, проблемы с производительностью довольно часто обнаруживались слишком поздно.
Что можно здесь предпринять — это, в первую очередь, улучшить показатели производительности. Необходимо определить, увеличение каких параметров будет сказываться на игре лучше или хуже, чтобы опираться на это при оптимизации и определении бюджета, за который нельзя выходить.
Помимо этого, вам поможет автоматизированное тестирование, в рамках которого также можно внести разнообразие в выходные данные для того, чтобы они нагляднее демонстрировали влияние изменений в движке.
Также можно определить и принять меры против снижения производительности. Например, сделать так, чтобы во время крупномасштабных событий некоторые объекты миновали промежуточные уровни разрушения — скажем, от твердых тел переходя сразу к частицам.
Еще одна мера — это зонирование площади в зависимости от ожидаемой нагрузки. В основе этой идеи лежит то, что мы можем сами определять, к какой области применить определенные контрмеры для того, чтобы не применять их ко всем ассетам на уровне, когда в этом нет необходимости. Например, если в скором времени к Джесси подоспеют враги с гранатами, очевидно, в локации будет слишком много разрушений, и на время их нападения можно ускорить процесс генерации разрушений.
По итогу хочется отметить, что команда Remedy проделала монументальную работу, из которой можно почерпнуть множество идей в части реализации и оптимизации системы процедурного разрушения окружения.