Как 3D-художникам работать с Nanite
Доклад Аррана Лэнгмида, технического художника Epic Games.
![](/upload/iblock/774/zorordralxg4yf8mueew5oif1zuw1d7x.jpg)
![](/upload/iblock/774/zorordralxg4yf8mueew5oif1zuw1d7x.jpg)
Иллюстрация: Unreal Engine / Annie для Skillbox Media
Технология виртуальной геометрии Nanite — одна из главных ключевых особенностей Unreal Engine 5, которая позволяет рендерить сцены с миллионами полигонов. Тем не менее многие разработчики всё ещё критично относятся к ней и не спешат использовать в своих проектах. На GDC 2024 старший технический художник Epic Games Арран Лэнгмид разобрал несколько актуальных проблем, связанных с Nanite, и продемонстрировал варианты их решения.
С обновления 5.4 в Unreal Engine появилась русская локализация, поэтому для удобства в докладе представлены русскоязычные названия функций с их аналогом на английском языке в скобках. Если какую-то функцию в движке ещё не перевели на русский язык, оригинальное название сохраняется и дополнительно выделяется жирным шрифтом.
Кратко о спикере, технологии и цели доклада
![](/upload/setka_images/15273906082024_bb2921aa7578ad7da7c5e04d7a7a64aa557740eb.jpg)
Арран Лэнгмид
Более десяти лет преподавал основы игрового арта, а также дисциплины, связанные с 3D-графикой, в Солентском университете в Великобритании. Помимо этого, он занимался разработкой игр и успел выпустить мультиплатформенную гонку «Bears Can’t Drift!?». В настоящий момент уже более шести лет работает в Epic Games техническим художником и консультирует других разработчиков по вопросам, связанным с Unreal Engine.
Уникальность технологии Nanite состоит в том, что она преобразовывает полигональную структуру объекта в собственную виртуальную сетку, разбивая её на огромное количество треугольников. При этом осуществляется рендеринг только тех участков меша, которые видны с конкретного ракурса. Вкупе с экономичным форматом данных системы Nanite обеспечивает корректное отображение большого множества объектов в кадре. Уровни детализации (LOD) рассчитываются автоматически.
И всё же бездумно подключать эту функцию в надежде, что она сможет отрендерить десятки миллионов полигонов, не стоит. Nanite демонстрирует свою эффективность далеко не в каждой сцене.
![](/upload/setka_images/15185306082024_accf102caaa970ce65d217b9ae9a8e9a57caa67c.jpg)
Например, на скриншоте выше представлена визуализация леса с большим камнем на переднем плане. Чтобы увидеть внутренние процессы виртуальной геометрии, можно открыть вкладку С освещением (Lit) — Визуализация Nanite (Nanite Visualisation). В ней находятся режимы визуализации, отображающие работу Nanite в конкретной сцене. И если выбрать режим Замена (Overdraw), который отвечает за отображение избыточных треугольников, перекрывающих друг друга, можно увидеть следующую картину.
![](/upload/setka_images/15185606082024_08fda0244b5397e030ee401fd2bea5b24f78a72b.jpg)
Фиолетовым отмечена область без перекрытия, в данном случае камень и ландшафт. Ассеты деревьев содержат очень много треугольников из-за комплексной структуры ветвей. К тому же они расположены достаточно близко друг к другу, в результате чего происходит наслоение геометрии. Иными словами, на одно и то же количество пикселей Nanite приходится отрисовывать больше треугольников, что в итоге замедляет рендеринг. Именно поэтому практически вся область с лесом отображается в ярко-жёлтом цвете — и с такой сценой Nanite покажет себя малоэффективно.
В презентации Арран предлагает несколько техник, которые помогут избежать проблем с оптимизацией при использовании Nanite. Но чтобы выявить причину, почему виртуальная геометрия столь избирательна к типам ассетов, для начала стоит рассмотреть примеры, где технология показывает наилучшие результаты.
Какие ассеты подходят для Nanite
Смежная геометрия
Nanite лучше всего раскрывает свой потенциал на ассетах со смежной геометрией. Под этим подразумевается структура с непрерывной полигональной сеткой. Например, как у камня, представленного на скриншоте ниже.
![](/upload/setka_images/15184806082024_6896a8696b8038f4fc8989ab005e4fccc3b90047.jpg)
Кадр: Unreal Engine / YouTube
Арран уточняет, что для корректной работы Nanite весь меш необязательно должен быть цельным, но чем больше геометрии образует единую полигональную сетку, тем лучше.
Если в уже ранее упомянутом разделе Визуализация Nanite (Nanite Visualization) выбрать режим отображения Скоплений (Clusters), можно увидеть кластерную структуру ассета в реальном времени.
На кадрах видно, что полигональная сетка разбита на тысячи мелких кластеров — каждый из них представляет собой группу треугольников. При приближении или отдалении камеры от объекта меняется и размер кластеров. Этот процесс схож с работой модификаторов по упрощению геометрии, которые встречаются в программах для 3D-моделирования. И если, к примеру, приблизиться к краю объекта вплотную, детализация остаётся на высоком уровне. Потоковая передача данных происходит очень быстро, так как рендерится только небольшой, видимый фрагмент ассета.
Поэтому Nanite хорошо работает со смежной геометрией с точки зрения групп сглаживания, UV и цветов вершин.
Геометрия, создающая окклюзию
Это явление возникает, когда ассет на переднем плане блокирует рендеринг объектов, расположенных позади. В примере ниже включён режим Замены (Overdraw), и когда камера приближается вплотную к каменной глыбе, движок рендерит только её видимую поверхность, игнорируя стены и арку позади.
![](/upload/setka_images/15185706082024_9f4177150537e15f4549b4114125e7b7fd1ba2c4.gif)
Пример также демонстрирует эффективность Nanite в распознавании и визуализации значимых объектов в зависимости от ракурса.
Геометрия без наслоений
Под этим подразумеваются объекты со структурой, которая не соприкасается с другими полигональными сетками. В сцене ниже в режиме Замены (Overdraw) можно заметить большое оранжевое пятно и несколько мелких пятен, которые то и дело появляются на ассете булыжника. Это явление происходит из-за того, что в качестве примера Арран специально разместил позади большого камня ещё один поменьше и продублировал его 30 раз, прислонив к первичному мешу.
![](/upload/setka_images/15185706082024_08b05e7a793ee96c87ad6538ed513e45f4ce5704.gif)
В результате в геометрии образовались слои, и Nanite уже сложно распознать, какой именно слой необходимо отрендерить, а какой скрыть.
При соблюдении всех трёх условий, перечисленных выше, Nanite может показывать весьма достойные результаты в области оптимизации.
Новые возможности тесселяции Nanite в обновлении 5.4
Арран отмечает, что от пользователей часто поступают запросы касательно оптимизации ассетов огромного размера, так как они начинают занимать слишком много места на жёстком диске.
Эту проблему можно решить, воспользовавшись новой экспериментальной функцией в контексте тесселяции Nanite, которая появилась в обновлении 5.4. Её возможности демонстрировали на презентации State of Unreal 2024 на примере разработки детального окружения для игры Marvel 1943: Rise of Hydra.
Рассмотрим, как это нововведение поможет сэкономить ресурсы на примере сцены ниже, где в центре внимания — два больших камня, расположенных с краю.
![](/upload/setka_images/15184806082024_b30ff17d14b759c017197570526ff51b33cc83c5.jpg)
![](/upload/setka_images/15185406082024_fc9956ee2f4201e204a5532c68850c6715ed24e0.jpg)
Кадр: Unreal Engine / YouTube
Режим визуализации Треугольники (Triangles) на втором кадре демонстрирует примерно одинаковую плотность геометрии Nanite у обоих ассетов. Но на самом деле геометрия камня справа основана на низкополигональной сетке объекта, который находится позади него. И объём нового ассета на диске занимает столько же места, сколько низкополигональный меш.
![](/upload/setka_images/15184706082024_6caf85fa09e0642959e62c753d9a2f18236eb1da.jpg)
Добиться такого результата достаточно просто. Если выделить камень справа и открыть Material Editor, в разделе Граф Материалов (Material Graph) можно увидеть, что в главном ноде, который отвечает за вывод, появился новый слот Толщина поверхности (Displacement). К нему можно подключить карту высот (Height Map) или любую другую карту, в которая содержит информацию о рельефе поверхности.
Затем в Сведениях (Details) материала в строке поиска можно набрать Nanite и дополнительно установить величину смещения рельефа — Magnitude. А чуть ниже в пункте Center — указать значение смещения рельефа вверх или вниз. Далее необходимо поставить галочку напротив Enable Tessellation, что активирует тесселяцию Nanite для данного ассета.
![](/upload/setka_images/15184806082024_cece785eb92cd643f5e788e5f37e3d933a76f56c.jpg)
Кадр: Unreal Engine / YouTube
Таким образом новый рельеф будет занимать столько же места на жёстком диске, сколько карта высот. Недостаток тесселяции в том, что её применение подразумевает динамический процесс, то есть виртуальная геометрия может меняться в зависимости от карты высот. И при активном слоте Толщина поверхности (Displacement) Nanite создаёт дополнительные треугольники, чтобы поверхность соответствовала рисунку карты высот. Чем больше плотность пикселей — тем больше генерируется треугольников. В свою очередь, такой подход хуже сказывается на производительности, если сравнивать с виртуальной геометрией Nanite, меняющейся в зависимости от обзора. Рассмотрим это явление на практике.
Когда Арран вновь переходит в режим визуализации Треугольники (Triangles) и отдаляет камеру, можно заметить, что у камня слева структура треугольников изменяется в зависимости от расстояния, а у камня справа она остаётся прежней. Как уже упоминалось выше, алгоритм Nanite автоматически подстраивается под расстояние камеры по отношению к объекту, и чем дальше расположена камера, тем меньше требуется треугольников для визуализации объекта. Но структура камня справа с подключённой тесселяцией не может адаптироваться под уровни детализации.
С другой стороны, полученный путём тесселяции рельеф можно изменять в реальном времени, смешивая с другими объёмными слоями кистью: откройте вкладку режим «Выбор» (Selection Mode) — Режим «Ландшафт» (Landscape Mode) и уже в нём переключитесь на режим Окрашивания (Paint).
Главное преимущество такого подхода — это создание цельной объёмной поверхности, которая исключает нежелательное в контексте Nanite наслоение нескольких типов ассетов друг на друга. Иными словами, не нужно размещать огромное количество камней и веток на ландшафте, их можно просто добавить с помощью кисти, как в примере выше.
Впрочем, при необходимости уровень тесселяции можно уменьшить для всей сцены. Для этого нужно набрать в консольной строке команду r.Nanite.DicingRate и через пробел указать значение разрешения Nanite. Чем оно выше, тем меньше треугольников в сцене.
![](/upload/setka_images/15185406082024_2229e417950c39bd1ac90259b6c781a232a40430.jpg)
Кадр: Unreal Engine / YouTube
Например, если ввести значение r.Nanite.DicingRate 8, разрешение Nanite у объектов с включённой тесселяцией уменьшится. Таким образом можно изменять разрешение в зависимости от текущих задач.
![](/upload/setka_images/15185406082024_71b97f3681cfd481f98f8279e17d064ae63ea66a.jpg)
Кадр: Unreal Engine / YouTube
Оптимизация деревьев в Nanite
Зная о рекомендуемых типах геометрии для использования Nanite, можно сделать вывод, что ассеты деревьев не просто не вписываются в эти требования, а устроены с точностью до наоборот.
- Меш дерева состоит из отдельных элементов (ствол, ветки, плоскости листьев).
- Специфическая структура не может перекрыть собой другие объекты в сцене (сквозь ветки можно увидеть другие объекты, к тому же деревья в сцене, как правило, подвижны).
- Ветки расположены очень близко друг к другу, в результате происходит наслоение геометрии.
Все эти факторы осложняют рендеринг, так как Nanite сложнее распознать, какую геометрию нужно отобразить и какие объекты необходимо отрендерить в первую очередь.
Тем не менее Epic Games постоянно ищет наиболее удачные способы оптимизации флоры в Unreal Engine 5. И в рамках своего доклада Арран предложил метод, который разработчики могут опробовать уже сейчас.
До появления Nanite движок поддерживал ассеты деревьев, ветки или листья которых состояли в основном из плоскостей с текстурами. С одной стороны подобные модели удачно смотрятся в сценах общего плана, но при акценте на деталях пересечения плоскостей начинают сильно бросаться в глаза.
![](/upload/setka_images/15185306082024_b96f49b701f581862428f9c58e1a46c731d0aa89.jpg)
Кадр: Unreal Engine / YouTube
Как правило, модель дерева состоит из нескольких компонентов: ствол, ветви и большое количество сгенерированных листьев-плоскостей, которые размещены по всей кроне в разных точках. Сосна из примера выше, созданная Арраном, представляет собой ствол, на котором размещено множество копий одной ветки с иглами, заранее подготовленной художником в Blender. Закончив сборку дерева, Арран запёк всю конструкцию в один ассет.
![](/upload/setka_images/15184806082024_d75dd4921f9f5ca9dc828e1efafbd5a21dfa9c5d.jpg)
Кадр: Unreal Engine / YouTube
Если открыть ассет этой сосны в отдельном окне, статистика в левом верхнем углу вьюпорта покажет, что меш содержит чуть больше 4,5 миллиона треугольников Nanite (пункт Треугольники Nanite (Nanite Triangles) выделен синим на скриншоте ниже). На самом деле это относительно небольшое значение, если меш соответствует требованиям Nanite, о которых упоминалось ранее. Но в данном случае речь идёт о дереве, поэтому процессы преобразования данных и оптимизации значительно усложняются.
![](/upload/setka_images/15184906082024_b76bc71fa80c20942ed01e19c21ead7b69ad6b89.jpg)
На скриншоте также можно увидеть, что ассет дерева занимает 271 МБ, причём 150 МБ отведено под геометрию Nanite. Эта информация прописана в пункте Предварительный размер сжатого диска (Estimated Comressed Disk Size) — на скриншоте подчёркнута красной линией.
Для сравнения Арран продемонстрировал статистику ассета камня, структура которого лучше адаптирована под Nanite. Пункт Треугольники Nanite (Nanite Triangles) показывает, что ассет содержит ровно 2 миллиона треугольников Nanite. Но при этом в пункте Предварительный размер сжатого диска (Estimated Comressed Disk Size) указано, что геометрия Nanite занимает чуть больше 16 МБ.
![](/upload/setka_images/15185106082024_1c076733da30d01108464d85a8ffad1517acec59.jpg)
Эти примеры ещё раз подтверждают, что технологии сложно оптимизировать меши, структура которых не соответствует трём пунктам, упомянутых в материале ранее.
Тем не менее ограничение в контексте деревьев можно обойти, если правильно разбить структуру дерева на составляющие, задействовав при этом фреймворк с процедурной генерацией контента Procedural Content Generation (или просто PCG).
Плагин PCG необходимо подключить в движке в настройках, после чего он будет доступен всегда. Открываем вкладку Правка (Edit) — Плагины (Plugins), в строке поиска набираем Procedural и отмечаем галочками плагины Procedural Content Generation Framework и Procedural Content Generation Framework Geometry Script Interop. После этого в меню создания блюпринтов появится процедурная генерация контента Граф PCG (PCGGraph) и Экземпляр Графа PCG (PCGGraph Instance).
Более подробную информацию о фреймворке процедурной генерации в Unreal Engine 5 и его применении можно узнать из плейлиста видеоуроков от программиста инструментов в Epic Games Адриена Логута.
Используя блюпринт PCG, Арран создал процедурную генерацию той самой ветви и применил полученную сетку на меш ствола с крупными ветвями. В итоге у него получилось полноценное дерево с динамической структурой. Направление ветвей с иголками можно задавать в реальном времени.
Ниже представлена структура графа текущей процедурной генерации ветвей, где за основу взята ветка в качестве Семплера сетки (Mesh Sampler) и создан нод Копировать точки (Copy Points). Точки наследуют свойства и атрибуты актора и меша. Также в графе настроены уровни детализации Distance to Density.
К слову, экспериментируя со связками нодов Distance to Density, Фильтр атрибута (Attribute Filter) и Генератор статичной сетки (Static Mesh Spawner) можно получить крону, в которой несколько ветвей будут без листьев, или вовсе изменить структуру кроны, тем самым добавив ассету больше вариативности.
Преимущество этого подхода в том, что ассет состоит из отдельных компонентов, и каждый из них весит очень мало. Например, общий объём компонентов для текущего дерева из примера, по словам Аррана, составляет 1,6 МБ.
Фреймворк PCG позволяет преобразовывать группы мешей в процедурный контент. Арран заранее подготовил уровень, где разместил готовое дерево. В Каталоге ресурсов (Content Browser) он кликнул ПКМ по файлу с уровнем, перешёл во вкладку Прописанные действия ресурса (Scripted Asset Actions) и выбрал PCG — Level to PCG Settings.
![](/upload/setka_images/15185506082024_1cc7c7aaff1472b38801633d33e2f9e6221a290f.jpg)
В результате дерево из уровня преобразовалось в данные с сохранением позиции, самого меша и его материалов. Все эти данные сохраняются в блюпринт, который можно добавить в новый блюпринт PCG, а затем с помощью построения графа — сгенерировать множество деревьев по всему ландшафту.
![](/upload/setka_images/15185506082024_62bf1942effdabf0107c530d35221fdf53489254.jpg)
В видео ниже можно увидеть как выглядит блюпринт PCG для текущего лесного массива.
Нод TreeInstanceDemo — настройки PCG с деревом, которое создал Арран. Связка Получить данные ландшафта (Get Landscapes Data) — Семплер поверхности (Surface Sampler) — Точки преобразования (Transform Points) отвечает за образец ландшафта для создания нескольких точек генерации в мире. Затем художник объединил нод TreeInstanceDemo и эту связку в ноде Копировать точки (Copy Points), чтобы создать копии точек по всему миру. Далее Арран связал нод Копировать точки (Copy Points) с Фильтром атрибута (Attribute Filter), чтобы отделить контент, который не требуется в данной сцене.
С помощью этой техники можно получить плотный лесной массив деревьев с высокой детализацией, задействуя минимальные затраты ресурсов. Недостаток в том, что при таком подходе можно достигнуть лимита экземпляров, то есть инстансов — зависимых копий меша (в данном случае дерева).
![](/upload/setka_images/15185506082024_ea2b2f998665356b094cbc9e4d99ecdc646b93d5.jpg)
На кадре выше заметно, что на большом расстоянии листва исчезает. Конкретно в этом примере Арран вручную уменьшил расстояние отображения веток с целью продемонстрировать ещё одну технику — использование плоскостей-билбордов.
Чтобы снизить нагрузку на обработку мешей дальнего плана, можно заменить трёхмерный ассет простой плоскостью-билбордом, добавив его в иерархию уровней детализации.
![](/upload/setka_images/15185506082024_b200d8bc76303a5287d3954213dd40d331052ae4.jpg)
Кадр: Unreal Engine / YouTube
Для такой оптимизации необходимо применить функцию World Partition и разграничить обработку данных, подключив подгрузку билборд-ассетов. Открываем раздел Параметры мира (World Settings), ставим галочку напротив пункта Включить подгрузку (Enable Streaming), если она не проставлена. Во вкладке Runtime Settings — Runtime Partitions находим Default HLOD Layer. Далее в разделе HLOD в пункте Layer Type выбираем Instancing (чтобы движок продолжал генерировать экземпляры). И ставим галочку напротив Запретить Nanite (Disallow Nanite).
Теперь при запуске симуляции (кнопка Play с зелёной стрелкой) на дальнем плане вместо голых деревьев появятся билборд-ассеты, которые издалека смотрятся довольно органично. Подобная комбинация упростит оптимизацию, так как движку не нужно рендерить меши деревьев на дальнем плане и тратить на них лишние ресурсы.
Столь необычный подход позволяет оптимизировать рендеринг одного из самых ресурсозатратных типов ассетов, сохраняя при этом высокое качество картинки. Некоторые пользователи относятся к способу довольно критически, и их реакцию можно понять: комбинация билборд-ассетов с технологией Nanite, которая должна в теории исключить уровни детализации, кажется странным. Но в настоящий момент техника, предложенная Арраном, считается самой эффективной в контексте оптимизации детализированного лесного массива в движке.
И не стоит забывать, что Epic Games строит большие планы на развитие Nanite, а значит, в будущих обновлениях стоит ожидать другие решения как в отношении оптимизации растений, так и для применения самой технологии в целом.