Как преобразовать карту высот с верхней картинки в лес с нижней?
Игры с открытым миром стабильно набирают популярность и находятся на первых местах в списках бестселлеров. Каждая новая игра поднимает планку размеров и сложности мира. Просто глядя на трейлеры последних игр с открытым миром, можно понять, что их цель — создание ощущения огромного масштаба.
Постройка таких миров ставит перед разработчиками большой вопрос — как эффективно заполнить подобные просторные миры? Никто не хочет расставлять каждое дерево вручную, особенно если команда разработки мала. В конце концов, разработка игр всегда связана с умными компромиссами.
Если посмотреть на типичную игру с открытым миром, то можно увидеть в действии принцип Парето — 20% контента составляют основной путь игрока, а 80% — это фон. Основной путь игрока должен отличаться высоким качеством и художественным содержанием, потому что игроки проведут на нём бо́льшую часть времени. Фоны, в том числе обширные леса или пустыни вокруг главных городов, не требуют такого внимания к деталям. Эти 80% представляют собой отличную цель для умных инструментов размещения содержимого, которые слегка жертвуют качеством и художественным оформлением в пользу скорости и простоты создания контента.
После выпуска нашей последней игры «Shadow Warrior 2» у нас появилась возможность немного поэкспериментировать с новыми идеями, пока наша команда дизайнеров была занята препродакшеном новой игры. Мы решили потратить это время на создание прототипа улучшенного инструмента размещения, активно работая с художниками уровней. Мы очень благодарны нашему работодателю Flying Wild Hog, позволившему написать об этом так рано, и всем тем, кто участвовал в создании этого прототипа.
Мы знали, как можно сгенерировать простую карту высот в World Machine. Вопрос заключался в том, как быстро преобразовать эту карту в красивую сцену, чтобы при этом команда художников уровней не умерла от переутомления.
Анализ решений
Существует несколько подходов к решению этой задачи, в том числе процедурное расположение, расположение на основе физики и расположение на основе цветных карт.
Процедурное расположение генерирует контент на основании свода заданных правил и случайного начального числа. Такие способы можно разделить на те, которые симулируют физический процесс (телеологические) и те, которые просто пытаются имитировать конечный результат (онтогенетические). Примером телеологических способов может быть генерирование леса на основании накопления воды и распределения солнечного света в "Ведьмаке 3". Ещё один пример — инструмент создания процедурной растительности в UE4, который симулирует рост следующих друг за другом поколений растительности. Примерами онтогенетических способов можно считать процедурную генерацию на основе Houdini, в котором технические художники пишут правила самостоятельно, например, как в "Ghost Recon Wildlands".
Физические решение — это интересный способ расположения объектов. Они основаны на симуляции физики, при которой можно сбросить несколько объектов с высоты и подождать, пока они рассеются по уровню. Такое реализовано, например, в Object Placement Tool для Unity.
Расположение на основе цветной карты основано на раскрашенных вручную цветовых картах, которые в дальнейшем преобразуются в ресурсы в соответствии с заданным набором правил. Свежим примером такого подхода являются инструменты из "Horizon Zero Dawn", ставшей для нас мощным источником вдохновения.
Начальная точка
Мы — довольно небольшая студия с ограниченными ресурсами, поэтому всегда ищем способы ускорить нашу работу — в том числе с помощью улучшенных инструментов расположения сущностей.
Наш первый инструмент размещения был основан на физике и создавался для нашей первой игры Hard Reset (2011 год). В игре присутствовали тёмные киберпанковские города, поэтому мы создали инструмент для быстрого расположения различных типов «мусора». Мы могли просто подвесить объекты в воздухе и включить физическую симуляцию. После того, как всё падало на землю и переставало двигаться, то, если нам нравились конечные результаты, достаточно было просто сохранить их. Пользование этим инструментом было истинным удовольствием, но в результате его использование оказалось довольно ограниченным. Результатами сложно было управлять, а повторение симуляции часто оказывалось медленнее расположения вручную, поэтому в конце концов мы решили отбросить эту идею.
Мы присматривались с процедурным решениям, но нам так и не удалось их применить, в основном из-за команды художников уровней, которая не слишком хорошо освоила Houdini и похожие пакеты.
Во второй игре, Shadow Warrior (2013 год), у нас были области на открытом воздухе с разными типами растительности, поэтому мы создали инструмент расположения на основе рисования. Процесс работы над уровнями был основан на создании базовых сеток (мешей) в 3ds Max. Художники уровней раскрашивали вершины этих сеток уровней, и во время импорта уровня эта раскраска вершин преобразовывалась во множество точек создания объектов.
Раскрашенная сетка уровня из Shadow Warrior — в цвете вершин хранится плотность размещения травы и зарослей
Внутри нашего редактора игры художники уровней могли выбрать любую область и настроить тип сущностей, создаваемых там с указанной плотностью и свойствами (например, привязка к сетке или вариации цветов). Затем, в процессе выполнения игры мы создавали эти сущности в соответствии с заданными художниками правилами и параметрами выполнения игры (например, настройками LOD). Этот инструмент был хорошо воспринят командой художников уровней и они часто просили, чтобы мы ещё больше расширили его возможности.
Требования
Мы начали с того, что записали ожидаемые от новой системы характеристики:
- Быстрое прототипирование. Мы хотели быстро прототипировать миры на основании высокоуровневых входных данных, предоставляемых художниками уровней, чтобы они быстро могли задавать внешний вид мира в общих чертах. Художник уровней как минимум должен иметь возможность указать, где находится лес, пустыня и т.д. Например, нарисовать 2D-карту мира, а затем преобразовать её во внутриигровой мир. Очень важна возможность быстрого запуска прототип мира внутри игры, чтобы могла приступить к работе вся команда разработчиков.
- Простые и безопасные итерации. Нам необходим способ для внесения безопасных изменений в последнюю минуту, при которых не потребуется перестраивать весь мир и не нужно будет блокировать область (преобразовывать данные инструмента расположения во вручную расставляемые сущности). Блокировка области позволяет вносить произвольные изменения в расположение сущностей, но уничтожает весь смысл инструмента расположения, потому что после блокировки не будет возможности настраивать правила расположения в процессе работы без уничтожения сделанных вручную изменений. Т.е уменьшение параметра, например, плотности деревьев, должно уничтожать только несколько экземпляров деревьев, а не перестраивать весь лес с нуля.
- Расширяемость. Для небольших команд разработчиков важна способность постепенного добавления новых функций. Мы не можем сначала планировать в первый год разработки, затем во второй год создавать ресурсы, располагать их в третий год, а потом выпускать игру. Нам нужна возможность работы с ресурсами в течение всего производства и беспроблемный способ их добавления в уже имеющийся мир. Например, нам нужен простой способ замены одного типа деревьев на два типа деревьев без изменения их расположения.
- Идеальная интеграция с располагаемым вручную контентом. Очевидно, нам нужен некий способ размещения военной базы внутри сгенерированного леса или ручное прокладывание идущей через этот лес дороги без забот о том, что сгенерированные деревья торчат из расставленных зданий или проложенной дороги.
Мы были готовы поступиться уровнем качества и ручным контролем ради возможности более эффективного расположения контента.
Рисователь биомов
Наблюдая за тем, как наши художники уровней пользовались предыдущим инструментом рисования, мы заметили, что они делают двойную работу. Например, сначала они располагают экземпляры травы, а позже рисуют под этой травой почву с соответствующей текстурой травы. Мы решили генерировать и текстурирование земли, и расположение сущностей с помощью одной системы. Это не только ускорило работу, но и позволило создать целостный мир, в котором все ресурсы расположены на соответствующих им текстурах земли.
Мы хотели иметь возможность повторно использовать цветовые карты биомов для ускорения прототипирования. Чтобы решить эту задачу, мы основали нашу систему на двух цветовых картах: типа биома (например, лес, пустыня, вода и т.д.) и весов (пышности растительности), а также добавили некоторые правила, управляющие раскрашиванием карты весов: низкие значения должны были означать почти пустой рельеф, а высокие — пышную растительность или множество препятствий.
В предыдущем инструменте раскрашивания после завершения нового набора префабов нам часто приходилось возвращаться к старым областям и перекрашивать их. Для упрощения итераций мы решили построить систему с более сложными правилами, а именно со списком правил создания объектов, который будет оцениваться в порядке важности — от самых важных до самых неважных. Это позволило нам без головной боли добавлять новые префабы в уже существующие области.
Кроме того, чтобы иметь возможность итерирования, нам нужно было снизить до минимума влияние изменения правил. Чтобы решить эту задачу, мы основываем всё на заранее вычисленных точках создания и заранее вычисленных случайных числах. Например, если нужно исправить точки создания деревьев, то при настройке плотности их расположения появляются новые экземпляры, но бо́льшая часть леса остаётся неизменной.
Наконец, после первых тестов мы решили, что всё-таки нам понадобится какая-то процедурная генерация, чтобы разрушить некоторые повторяющиеся паттерны. Мы решили задачу, расположив особые объекты (например, упавшее дерево в лесу) с очень низкой плотностью (с низкой вероятностью создания).
Правила биомов
Теперь, когда у нас есть карта типа биома и карта весов, нам нужны какие-то правила, описывающие способ преобразования этих карт в сущности и текстуры рельефа.
Правила наложения текстур достаточно просты:
- Интервал весов биома с задаваемым спадом
- Интервал весов рельефа с задаваемым спадом
- Интервал холмистости рельефа с задаваемым спадом
- Плотность
Каждому правилу назначается конкретная текстура рельефа, а эти правила применяются снизу вверх. Во-первых, мы заполняем весь биом базовой текстурой. Затем оцениваем последующие правила и располагаем назначенную текстур при выполнении условий, то есть заменяем предыдущую текстуру в текущей точке.
Правила расположения сущностей немного более сложные:
- Все изложенные выше правила текстур
- Расположение относительно к земле или к мировой оси «вверх» — например, деревья привязаны к мировой оси «вверх», потому что они обычно растут вверх, но камни располагаются относительно рельефа
- Случайное угловое смещение от оси привязки — позволяет разрушить однообразность, например, растущего бамбука
- Случайный поворот вокруг оси привязки
- Интервал случайных масштабов
- Смещение вдоль оси привязки
- Влияние (радиус коллизий сущности)
Как и в случае правил текстур, каждому правилу сущностей назначен конкретный префаб. Правила сущностей применяются сверху вниз. Сначала мы создаём большие сущности, такие как камни или деревья, затем, если это возможно, создаём кусты, траву и т.д. Дополнительно каждая сущность также проверяет коллизии между самой собой и уже расположенными элементами.
С помощью таких правил можно построить пример биома, например, вот лес:
Пример назначения весов для лесного биома
Среди других возможных интересных правил есть проверка расстояния до другой сущности. Например, создание мелких деревьев вокруг больших. Мы решили пока не реализовывать их, чтобы минимизировать процедурную генерацию.
LOD биома
И вот здесь система проявляет себя полностью. Благодаря тому, что все сущности формируются из цветных карт, она значительно улучшает LOD и потоковую загрузку. Мы создаём сущности в процессе выполнения игры, поэтому с точки зрения потоковой системы нам достаточно получить всего два байта на квадратный метр вместо загрузки всех данных расположения сущностей.
Для различных настроек графики на PC мы просто управляем плотностью мелких объектов типа мусора или травы. Созданием LOD мира управляют сложные правила создания сущностей. Мы создаём всё, что находится рядом с игроком. После определённого расстояния создаём только более крупные объекты. Ещё дальше создаём только самые крупные объекты. Наконец, дальше определённого расстояния от камеры мы вообще не создаём объекты. Это не только помогает в рендеринге, но и помогает во всех сторонних вычислениях центрального процессора, потому что нам не приходится симулировать или отслеживать изменения сущностей на расстоянии.
Интеграция биомов
Мы хотели интегрировать наше решение с расставляемыми вручную сущностями и другими инструментами. В случае сплайновых инструментов, например, инструментов создания рек или дорог, мы можем аналитически вычислить расстояние от этого сплайна. На основании этого расстояния можно автоматически удалять все сущности рисователя биомов с проложенных дорог и рек. Более того, мы снижаем вес биомов вокруг этого сплайна. Таким образом, если мы расположим дорогу внутри леса, то пышность растительности рядом с дорогой будет снижена.
Пример того, как инструмент создания рек автоматически работает с биомами
Похожим образом мы работаем и с расставляемыми вручную сущностями. В наши префабы можно вставить специальные блокировщики биомов. Блокировщики биомов — это простые формы (т.е. сферы или выпуклые фигуры), удаляющие сущности биомов и уменьшающие вес биомов вокруг себя с неким заданным значением спада. Это не только помогает избегать появленяи деревьев внутри вручную располагаемых домов, но и позволяет свободно перемещать здания без необходимости ручной перекраски цветных карт, потому что всё окружающее будет приспосабливаться к новой позиции здания, не разрушая нарисованных данных биомов.
Рабочий процесс
Наш рабочий процесс начинается с World Machine, в которой мы генерируем начальную карту высот. На следующем этапе мы выполняем итерации на приблизительных цветных картах биомов в Substance Designer. Мы создали поддержку автоматического повторного импорта карт биомов, поэтому когда художник графики сохраняет их в Substance Designer, то новая карта биомов импортируется, и изменения сразу же становятся видимыми внутри редактора игры.
Это позволяет быстро создать мир, наполненный ресурсами, текстурами рельефа и т.д. Очевидно, что он не будет соответствовать качеству готовой игры, но в целом, на этом этапе игра уже может работать, а команда разработчиков геймплея уже начинает работу над скоростью игрока, транспорта и боями.
Наконец, когда нас устроит грубая модель мира, мы начинаем вручную располагать ресурсы и вносить мелкие изменения в цветные карты биомов с помощью кистей из редактора игры.
Реализация
Алгоритм расположения сущностей сводится к циклическому обходу заранее вычисленных точек создания, получению данных мира в каждой точке (например, высоты рельефа, его наклона и т.д.), вычислению плотности из правил создания и сравнению плотности с заранее вычисленной минимальной плотностью в точке создания, чтобы определить, нужно ли создавать сущность в текущей точке. Под сущностями подразумеваются экземпляры префабов, то есть мы можем создавать, например, деревья с триггерами, звуками, спецэффектами (например, светлячками) и декалями рельефа.
Предварительное вычисление хорошего множества точек создания оказалось на удивление сложной задачей. Мы хотели заранее вычислять паттерн, обладающий следующими свойствами:
- Как можно более плотное расположение
- Между точками есть заданное минимальное расстояние
- Близлежащие сущности не выровнены относительно одной прямой, потому что это бы разрушило иллюзию естественного расположения (подробнее об этом можно прочитать в превосходной серии постов про расположение травы в игре Witness)
- Вышеперечисленные свойства должны сохраняться при снижении плотности (благодаря снижению числа заданных точек создания в зависимости от вычисленной плотности)
- Паттерн должен иметь бесшовное наложение. чтобы покрывать большой мир
Мы пробовали генерировать множество точек, похожих на пятно Пуассона с дополнительным требованием, чтобы близлежащие точки не были выровнены относительно одной прямой. В результате мы пришли к регулярной сетке, искажённой несколькими функциями sin и cos. Также мы назначили простым алгоритмом дизеринга каждой точке вес, чтобы сохранить вышеприведённые свойства при удалении некоторых точек из-за снижения плотности создания сущностей.
При создании сущностей на рельефе важно не использовать исходную карту высот рельефа, а вместо неё пользоваться той, в которой есть вставленные вручную сетки рельефа. К счастью, у нас были эти данные, потому что мы создавали с помощью трассировки лучей эту комбинированную карту высот для отрисовки теней больших высот рельефа.
Для обработки коллизий между сущностями у нас есть двухмерная битовая карта коллизий, перед расположением сущностей мы растеризируем на неё форму сущности.
Похоже, что расположение сущностей удобно выполнять в шейдере графического процессора, но на самом деле, когда мы начали реализовывать более сложные правила, типа коллизий между сущностями с разными размерами, код стал очень запутанным. В результате мы решили просто создавать сущности с помощью функции центрального процессора. Эта функция получает новый тайл 64 м x 64 м, создаёт сущности и завершает работу, а затем мы запускаем ещё одну функцию с другим тайлом.
С другой стороны, на видеопроцессоре отлично работает создание текстур рельефа, потому что каждый тексел можно вычислять параллельно без всяких зависимостей. Мы просто запускаем один шейдер на каждый уровень clipmap рельефа для создания для него карты текстур. Единственным недостатком является то, что для обработки реакций коллизий (пуль, следов ног и т.д.) нам нужно, чтобы эти данные были и в основной памяти со стороны ЦП. Для этого нам понадобилось копировать вышеупомянутые карты текстур из памяти видеопроцессора в основную память.
Подводим итог
Кто знает, что принесёт нам будущее, но в интервью с визионерами игровой индустрии часто всплывает упоминание Metaverse (например, в этом интервью с Тимом Суини). Я понятия не имею, как будет выглядеть эта Metaverse, но для неё точно понадобятся более интеллектуальные инструменты, способные строить и располагать огромные количества контента. Я верю, что однажды такие инструменты станут привычным стандартом для художников уровней.
Автор: PatientZero