Unreal Engine
February 10, 2023

Создание процедурной травы с помощью Megascans, SpeedTree и UE4

Художник 3D-среды Карстен Мальмквист рассказывает, как генерировать процедурную траву в режиме реального времени, как использовать отсканированные атласы от Megascans для создания реалистичных пучков травы в SpeedTree и как настраивать шейдер травы.

Введение

Меня зовут Карстен Мальмквист, я художник по окружению в студии Ernst&Borg в Мальме, Швеция. Я окончил школу разработки игр The Game Assembly в Мальме и стажировался в PortaPlay и Star Vault, работая над Mortal Online 2.

Проект Realtime Procedural Grass

Я возился с этим проектом в течение нескольких выходных, то тут, то там. Он начинался как попытка сделать материал для ландшафта, но затем перерос в нечто большее. На данный момент это первая «веха» в большом проекте, где я хочу процедурно сгенерировать целый биом. Для меня это почти как рисование на пленэре, но в 3D!

Рабочий процесс от Megascans до SpeedTree

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

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

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

Чтобы нарисовать пучки травы в SpeedTree, я для начала создаю меши карточек (card meshes - прим. пер.), размещая вершины поверх атласа, и убеждаюсь, что назначил различным ЛОДам соответствующее количество полигонов. Я не слишком беспокоюсь о точном количестве полигонов на данном этапе, поскольку знаю, что мне придется вернуться к этому шагу, когда я перенесу пучки в Unreal Engine и увижу, как они работают.

После настройки ЛОДов карточек я могу приступить к созданию пучка. Для этого я сначала использую ноду Zone, чтобы определить, где может расти трава, а затем подключаю ноду Leaf Mesh, содержащую меши карточек, которые я создал ранее. Обычно ноды SpeedTree работают так: дочерние элементы создаются внутри родительских, что означает: карточки из Leaf Mesh создаются внутри зон (созданных нодой Zone).

Для того, чтобы настроить пучок, в целом, я модифицирую ноду Zone, чтобы изменить места, где могут появляться карточки, а затем изменяю ноду Leaf Mesh, чтобы изменить количество карточек, появляющихся в данном месте, и их ориентацию.

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

Выбрав ноду Leaf Mesh, я также настраиваю количество карточек травы во вкладке generation, а затем перехожу во вкладку local orientation, чтобы изменить локальную ориентацию отдельных карточек. Я бы рекомендовал попробовать различные настройки, чтобы понять, что они делают, поскольку, на первый взгляд, это не всегда понятно.

При редактировании пучка травы есть несколько моментов, которые следует учесть:

  1. размер целого пучка;
  2. сколько карточек находится в пучке.

От размера пучка зависит количество экземпляров, которые нужно создать в движке, чтобы покрыть землю. Для хорошей производительности пучок должен быть достаточно большим, чтобы снизить количество вызовов отрисовки (draw calls – прим. пер.), но не слишком большим, иначе пучки будут выпирать из земли в местах с неровным рельефом.

Количество карточек может сильно повлиять на производительность: чем их больше, тем больше требуется перерисовок, что очень дорого. Для этого проекта я обнаружил, что для производительности лучше добавить несколько дополнительных полигонов и, тем самым, убрать полностью прозрачные части атласа травы. Если вы создаете свою собственную растительность в SpeedTree, попробуйте разные количества полигонов, чтобы найти оптимальный вариант.

Вот как теперь выглядят мои пучки травы в SpeedTree:

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

Настройка шейдера травы

Перенесение растительности в Unreal Engine сразу из SpeedTree обычно выглядит не очень хорошо: для того, чтобы трава выглядела хорошо, нужен хороший шейдер. Вообще, в SpeedTree есть возможность экспортировать материал в Unreal, но выяснилось, что там не так много опций, как мне бы хотелось. Поэтому вместо того, чтобы использовать импортированный шейдер, я сделал свой собственный, состоящий из таких функций:

1. «Угасание», или размывание очертаний пучков травы в зависимости от расстояния до камеры. Для этого я использую ноду PerInstanceFadeAmount, которая указывает шейдеру степень «угасания» данного стебелька травы. Делаю я это с помощью карты травы, работая в редакторе материала ландшафта. Карты травы - это способ, при котором процедурная растительность (Landscape Grass Type ассеты) создается на ландшафте его же материалом. Открыв Landscape Grass Type и установив у Start Cull Distance меньшее значение, чем End Cull Distance, нода PerInstanceFadeAmount начнет возвращать значение «угасания» каждого стебелька в материале травы, как только он окажется дальше, чем Start Cull Distance. Имея эту информацию, я могу теперь установить размывание всей травы так, чтобы она плавно исчезала на расстоянии.

2. Увеличение интенсивности карты нормалей. Это позволяет распределить освещение на карточках травы, создавая иллюзию, что отдельные стебельки травы направлены в разные стороны, а не стоят ровными рядами. Для этого эффекта я использовал ноду FlattenNormal. Результат будет зависеть от используемой карты нормалей. В идеале мы хотим, чтобы пряди травы были направлены в разные стороны, а отдельные пряди травы изгибались, скручивались и поворачивались, как это происходит в природе.

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

4. Вариации цвета также очень важны. Они придают траве естественный вид. В шейдере я использую мелко- и крупномасштабную вариацию. Мелкомасштабная вариация состоит из ноды SpeedTreeColorVariation, которая дает случайный цвет каждому стебельку травы. Крупномасштабная вариация цвета происходит от текстуры-маски, которая имеет тайлинг в координатной плоскости XY. Я использую ее для того, чтобы плавно интерполировать между двумя оттенками.

5. Вариация высоты - одна из моих любимых функций шейдера, благодаря ей трава выглядит очень пушистой. Я добиваюсь этого, смещая вершины вдоль их нормалей. Поскольку я не хочу перемещать корни, я добавил цвет вершин на вершины, расположенные ближе всего к земле. Так не заметно, что сами корни остались на месте. Чтобы получить нужную вариацию, я использую тайлинг-маску в координатной плоскости XY и определяю, какие пучки нужно сместить.

Вот как визуально выглядит шейдер:

Настройка материала ландшафта

Материал ландшафта - это часть, которая объединяет все в этом проекте, это то, что позволяет мне, по сути, создавать процедурную растительность на высоком художественном уровне. Сам материал довольно прост. У него есть два слоя Grass и Grass High, которыми я могу рисовать на поверхности ландшафта. Эти слои создают различные меши травы с помощью ноды вывода травы ландшафта. Слой Grass создает меши короткой травы, а слой Grass High - меши более высокой травы. Кроме того, я рисую пучки сорняков и цветов на слое Grass, используя маску, наложенную в координатной плоскости XY на весь ландшафт. И, наконец, чтобы уменьшить количество перерисовок, я слежу за тем, чтобы сорняки, цветы и короткие травы не появлялись на слое Grass High , просто вычитая этот слой из соответствующих масок сорняков и цветов.

Оптимизация

Я считаю, что перерисовка является одним из самых важных факторов при оптимизации любого типа растительности, включая траву. Поскольку моя цель была получить красивый крупный план, я решил сделать пучки травы более плотными, то есть с использованием максимально возможного количества карточек, чем в случае, когда бы я, к примеру, пытался достичь 60 FPS. На моем ПК с GTX 1060 в разрешении 1080p этот проект работает на скорости около 40-45 FPS. Чтобы повысить производительность, я бы уменьшил использование карточек в пучках травы (что, к счастью, легко исправить, поскольку я использую процедурный рабочий процесс в SpeedTree), а также развел бы пучки подальше друг от друга в Unreal Engine.

Заключение

Оглядываясь назад, могу сказать, что этот проект был очень увлекательным, и я многому научился. Самой большой проблемой было, пожалуй, заставить пучок, который хорошо выглядит в SpeedTree, хорошо смотреться в Unreal Engine. И здесь решение о том, какую атласную текстуру использовать, а также настройка хорошего шейдера стали, на мой взгляд, двумя ключевыми моментами.

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

Спасибо, что прочли. Надеюсь, вам понравилось. Если у вас есть какие-либо вопросы, не стесняйтесь задавать их мне!

Оригинал: https://80.lv/articles/making-procedural-grass-with-megascans-speedtree-ue4/