TOTAL RELOAD : процедурная генерация проводов. (часть 1)
Готовы рассказать о процедурной генерации проводов и о том как она устроена в TOTAL RELOAD. Всего проводам будет посвящено 2 статьи (2 части). Выше приведен скриншот из игры на котором представлены финальные провода.
ВАЖНО: материалы, которые будут представлены ниже, следует рассматривать в качестве материалов из технических этапов разработки, которые не передают геймплей, атмосферу, историю игры, а только дополняют текст статьи.
Предупреждение: эта статья не является инструкцией, также, скорее всего, она не будет полезна профессиональным программистам, которые имеют опыт работы с процедурной генерацией мешей. Статья содержит картинки, видео и обобщенные объяснения разработанной нами системы генерации проводов и обобщенный ответ на вопрос «что же такое меш (mesh)?» Итак, поехали!
Часть 1. Процедурная генерация проводов
Механика нашей игры связана с проводами, от них зависит восприятие игры, то как игроку будет комфортно играть. В процессе разработки игры, было реализовано и опробовано несколько различных вариантов системы проводов. По мере развития проекта мы постоянно их модернизировали.
Версия провода № 1
В самом начале разработки игры было принято решение не пытаться вытянуть качество графики до уровня ААА – проекта. Причины такого решения просты:
- команда состояла (и состоит) из 2-х человек, которым задач и так хватало
- не было уверенности, что все окупится
Итак, в первое время мы разработали «2D» систему проводов, которые были плоскими. Я думал, плоская – это хорошо в плане того, что меньше полигонов и объем проводам можно картами нормалей придать (если потребуется). Ниже представлен первый вариант системы проводов:
Как работала система проводов:
- имеется набор вложенных gameObjects – узлы проводов
- родительский объект создает процедурный mesh с UV. Mesh и UV создаются в зависимости от позиции узлов проводов.
Система позволяла выполнять поставленные задачи:
- отображала соединения объектов
- относительно гибко настраивалась
- генерируемый провод соответствовал первому минималистичному стилю
Меш провода генерировался процедурно по точкам. Здесь видно конечный результат (вариант провода №1):
Провод менял свою геометрию тогда, когда одна из точек меняла свою позицию. Провод мог гнуться на углы +/-90 градусов относительно родительского объекта. Еще одно видео:
Так планировалось прокладывать провод в уровне:
Технически, провод имеет начало и конец, он автоматически должен соединять разные элементы уровня. Это было реализовано процедурно. Имитация соединения элементов по какому-то событию показана на видео:
И вроде как все были довольны этой системой проводов, но…
Но тут, по мере анализа уровней, быстро пришло понимание: «это не то, чего мы хотели добиться». Конечно, смотрится неплохо, лучше чем ничего и даже лучше чем некоторые дизайнерские решения. Но минималистичный стиль присутствует повсеместно и в очень многих инди-проектах. В общем, в очередной раз понесли мы свои результаты к эффективному менеджеру Сове. Сова к этому времени была порядочно раздражена нашими глупыми решениями, но приняла нас. Вот что мы принесли ей напоказ:
У Совы, насколько мы знаем, брови изначально (наверно с рождения) повернуты под 90 градусов. А после всего того, что мы показали, они у нее просто вывернулись на все 360.
Что не устроило Сову:
- Обыденность и минималистичность: где-то она уже видела этот провод;
- Скрытность: он плоский, сбоку его вообще не видно;
- Повороты кратны 90 градусам: уровень будет иметь углы только кратные 90 градусам, это снижает возможности разработчика уровней до… лучше не думать, даже некоторые первые в мире игры имели стены, которые располагались под произвольными углами.
По проводу все осталось в общем по-старому, только в ТЗ добавилось 4 требования:
- провод должен гнуться не только на 90 градусов;
- провод должен быть объемным, так как плоский визуально сливается с полом;
- текстура должна тайлиться, а не растягиваться;
- уникальность проводу нужно придать моделью + материалом.
Пара слов о генерации меша
Хотелось бы чтобы статья содержала больше полезной информации чем просто результаты нашей работы.
Немного глубже затронем тему процедурной генерации мешей.
Что такое меш (mesh)?
Меш – это, условно, модель, которая крепится к компоненту «MeshFilter», который, в свою очередь, принадлежит «GameObject». MeshFilter берет меш из ассетов и передает его в MeshRenderer, последний участвует в рендере меша на экране.
Способы создания моделей для игр:
- в редакторах моделей (например: Blender, Maya, 3ds Max и другие);
- процедурно
- редактирование существующего меша (в общем-то это процедурная генерация меша)
Процедурное создание полезно в ряде случаев. В основном все случаи сводятся к тому, что мы не знаем какую модель нужно создать и модель создается в редакторе или в процессе игры. Это может модель помещения, модель каких-то объектов, которые нельзя построить заранее. Что касается нашего случая, то как вы уже наверно догадались, это модели проводов.
О том как создавать меши уже написано столько всего, что мне остается дать одну из ссылок на материалы и обьяснить, в общем виде, как все работает. Ссылка на то как сгенерировать меш в Unity3D: martin-ritter.com
При создании меша, во-первых, относительно локального (0,0,0) создается геометрия. Геометрия – это набор вершин (точек), которые соединены между собой. Потом идет массив «triangles». Этот массив содержит порядок точек. Тут стоит совсем немного пояснить:
- видеокарте нельзя просто дать точки на отрисовку треугольника без указания порядка. Почему? В основном потому, что есть оптимизация. Треугольник имеет две стороны и если бы видеокарта всегда бы рендерила его две стороны, то она бы неоправданно расходовала ресурс. Порядок передачи точек связан с тем куда нужно направить нормаль полигона.
- видеокарта может рисовать только несколько примитивов: точку, прямую и треугольник. Все остальное создается из примитивов.
Итак, этого в общем-то достаточно для рендера геометрии, но недостаточно для наложения текстур на геометрию. Для наложения текстур нужно создать массив UV – координат. Эти координаты показывают в какую область текстуры проецируется каждая вершина геометрии/модели. Обычно UV принимают значения из диапазона [0,1]. Где (0,0) – нижний левый угол текстуры, (1,1) – правый верхний. В некоторых случаях UV могут принимать значения больше 1 и меньше 0. Это зависит от задачи, которую поставили перед «shader-artist» и его конкретной реализации шейдера.
Кроме vertex и uv, меш может содержать и другие данные:- uv2, uv3…, uv9 (не знаю чем определяется предел uv[i], наверно платформой)
- tangents
- normal
- color
И да, чуть не забыл, нужно создать AABB. AABB – это axis-aligned bounding box. То есть это «коробка», которая позволяет движку быстро исключать игровые объекты из процесса отрисовки на основе попадания их AABB в область видимости камеры.
Зачем? Это сделано для того, чтобы быстро отбрасывать объекты, которые за спиной игрока, чтобы они даже не пытались подаваться на графический конвейер. Оптимизация! :)
Версия провода 2
В основу этой версии легли сплайновые кривые, а вернее, деформация модели вдоль сплайновой кривой. Что такое сплайн и как он работает? Это достаточно базовая математика, в сети есть даже готовые примеры проектов со сплайнами, детальный разбор того как все работает (не буду повторять то, что замечательно расписано здесь: catlikecoding.com), не будем на них останавливаться.
Вот как выглядит настройка провода:
Особоевнимание уделено шейдеру. Он поддерживает тесселяцию, что позволяет экономить на полигональности модели. Здесь сверху провод с тесселяцией, обратите внимание на то, что он гладкий. Внизу провод без тесселяции, угловатый:
Все провода имеют один общий материал, особенности всех проводов закодированы в вершинах мешей.
Здесь версия проводов ближе к финальной (на концах установлены вилки, заданы текстуры, доработаны модели):
Ссылки на нас:
TORSHOCK.COM / VK / DTF / Twitter / FB