Для моей постапокалиптической игры Frameshift мне нужно было заполнить очень большой открытый мир разнообразными городами. В нашей команде всего 3 человека, поэтому я, разумеется, воспользовался процедурной мощью Houdini!
Раньше, когда не знал о Houdini, я писал на C# собственные инструменты процедурной генерации мешей для Unity. Добавление новых функций было очень долгим процессом, и когда я однажды услышал, что Houdini предназначен для подобных задач, то сразу перешёл на него. Я поэкспериментировал с разными способами генерации городов и улиц, и в этой статье я вкратце расскажу о методике, которая в конечном итоге была использовала для игры.
Сначала я создал инструмент для генерации сети улиц, которые можно было бы использовать в городе. Моё решение основано на изложенных здесь концепциях, а также на использованной по ссылке статье. Там много говорится о тензорных полях и собственных векторах, и всё написано очень технически, но вкратце это можно объяснить так: для моего случая (генерации дорожной сети на рельефе) мне нужно было сгенерировать контурные линии рельефа, а также линии, перпендикулярные им.
Я начал с рельефа, и благодаря небольшой помощи из примера Houdini Gubbins я сгенерировал векторы в каждой точке рельефа, направленные в сторону контура, и линии градиента, использующие VEX:
Жёлтыми векторами показаны контурные линии.
Затем нам нужно создать сетку городских улиц и смешать её векторы с векторами, повторяющими природные изгибы рельефа. Так мы создадим площадь, напоминающую сетку городских улиц, хорошо сочетающуюся с рельефом. Здесь мы помещаем сетку на рельеф, затем выполняем polyframing, чтобы нормали выстроились вдоль линий сетки, добавляем falloff для плавного смешивания и наконец задаём новый вектор контура, являющийся смешением двух векторов:
На изображении выше можно увидеть, что внутри границ прямоугольника контурные линии больше напоминают сетку. Также я выполняю похожий процесс, чтобы добавить влияние уже существующих в игре дорог, созданных вручную. Я вывел в Unity HDA ползунок, чтобы влияние можно было менять, создавая более естественно расположенные или более «квадратные» улицы в пределах границ города.
Далее я преобразую векторные поля в VDB, чтобы из них можно было выполнять сэмплирование для создания готовых дорог. С помощью SOP отслеживания объёмов можно получить красивую визуализацию:
Это уже начинает походить на сетку улиц! Они слишком близки друг к другу, но в целом формы уже есть. Затем мы выполняем множество операций сэмплирования из VDB скоростей и множество vex для создания готовых линий дорог. Код vex по сути расставляет дороги на нужное расстояние и завершает их, если они слишком близки к другим дорогам или слишком длинны:
Затем мы выполняем постобработку, чтобы всё сгладить, удалить небольшие части, удлинить тупики для создания реалистичных перекрёстков, и накладываем дороги на рельеф. Окончательный результат выглядит так:
Всё равно осталось несколько нереалистичных частей, но небольшая подчистка вручную в Unity вполне приемлема, учитывая, сколько времени мы на этом сэкономили.
Итак, мы процедурно сгенерировали городскую уличную сеть, но в результате получилась только куча кривых в Houdini. Теперь я расскажу, как сгенерировать геометрию дорог для каждой из этих кривых, чтобы создать меши, которые можно использовать в Unity. Вот наши предыдущие результаты, перенесённые в Unity:
Для начала нужно рассказать, как выполняется передача данных между Houdini и Unity. В случае дорог я записываю в файл JSON последовательность точек, ширину дороги и пресет поверхности дороги. В Unity я создал инструменты редактора для ручного редактирования дорог при помощи кривых Безье:
В поисковиках можно найти множество туториалов о создании редактора кривых Безье в Unity. Для переноса данных обратно в Houdini я использую нод Python, чтобы считать файл JSON:
Здесь вы видите результат создания городской уличной сети, полученный выше, но на этот раз он считан из файла JSON, потому что теперь он получен из Unity с внесением ручных правок.
Также я импортирую из Unity рельеф, считывая его сырые данные. Вероятно, сегодня это можно сделать нативно, при помощи HDA, потому что в Houdini Engine улучшилась поддержка рельефов Unity, но когда я выполнял эту работу, она ещё была не такой хорошей.
Затем мы создаём перекрёстки. Основная работа здесь выполняется Intersection Stitch SOP, но я также использовал VEX для небольшого удлинения дорог на случай, если они пересекутся не полностью, и удалял короткие концы, чтобы T-образные перекрёстки создавались правильно.
Далее я создаю группу перекрёстков при помощи ослабления (falloff) от точки пересечения, где расстояние ослабления равно самой широкой на перекрёстке дороге. Затем я расширяю эту группу рёбер в случаях, когда угол между двумя дорогами меньше 45 градусов, чтобы между двумя концами перекрёстка было достаточно пространства для создания меша нужной ширины:
Вычисления в ноде VEX находят гипотенузу прямоугольного треугольника (можно считать, что каждая отдельная часть перекрёстка состоит из двух прямоугольных треугольников):
Так как мы знаем, какой должна быть ширина дорог группы перекрёстка (на рисунке выше обозначена как A) и знаем угол a, то можем вычислить C, чтобы найти расстояние, на которое нам необходимо расширить группу перекрёстка, чтобы разделить их на нужную ширину.
Затем мы создаём группы мостов, выделяя секции дорог, находящиеся выше определённого расстояния над землёй. Это выполняется при помощи Ray SOP со включенным «point intersection distance» и отключенным «transform points». Далее, чтобы создать готовые кривые для замыкающей геометрии мы вырезаем при помощи Polycut группы перекрёстков и мостов, чтобы остались только обычные дороги.
И теперь мы наконец-то начинаем создавать реальные меши. Подсеть, создающая меши перекрёстков — это модифицированная версия туториала Solving Intersections. Подсеть, создающая геометрию дороги, также замыкает линию и создаёт UV. Для этого есть ещё один замечательный туториал, созданный HoudiniSimon. В нём рассказывается, как сгенерировать UV для изгиба замкнутой кривой, но первую часть туториала можно применить и к разомкнутым кривым.
Затем я просто создал несколько групп для применения материалов, чтобы использовать атрибут roadPreset, взятый из моих кривых в Unity.
Далее мы создаём геометрию мостов. Показанная ниже сеть итеративно проходить по всем ранее созданным группам мостов и в зависимости высоты над землёй, длины и извилистости вставляет или насыпной, или арочный мост. Создание арочного моста я рассмотрю в отдельном туториале, а насыпной мост использует отличный Gamedev Sweep Geometry SOP со включенными начальной и конечной группами для замыкания заранее созданной геометрии моста вдоль кривой моста.
Результат:
Эти меши мостов состоят из смоделированных вручную начальной/конечной/средней секций, деформируемых и дублируемых вдоль кривой при помощи Gamedev Sweep Geometry SOP. Вот как выглядит созданная вручную геометрия и группы:
Выделена центральная группа, а концы находятся в группах «bridgeStart» и «bridgeEnd», которые используются нодом замыкания геометрии.
Завершаем мы мост, немного экструдировав его для создания дорожной насыпи, на которую будет наложена текстура гравия; также задаются несколько булевых переменных для пересечений определённых типов дорог. Например, если грунтовая дорога пересекается с шоссе, то она усекается.
В конце мы сохраняем всю геометрию для применения в Unity и в дополнительных инструментах Houdini. Мы разделяем меши дорог на меньшие фрагменты и экспортируем их как FBX, а также сохраняем данные в файлы .bgeo, чтобы их можно было использовать позже.
В Unity:
Надеюсь, вам понравилось это краткое описание нашего техпроцесса создания геометрии дорог в Houdini. В следующем посте я расскажу о создании дорожной разметки и выравнивании рельефа, чтобы он больше соответствовал дорогам.