Импорт AI-сгенерированной локации в Unreal Engine 5
У вас есть GLB целого квартала и проект на UE5. Что проверить при импорте: масштаб, Nanite, Lumen, коллизии и почему один большой актор никогда не стримится.
GLB квартала на сорок зданий приезжает в Content Drawer сотней шестьюдесятью статик-мешами, и голова манекена проходит выше окон второго этажа. Это единицы, чинится за минуту. Через час выясняется, что весь квартал приехал одним актором и World Partition тихо решил, что он загружен всегда. Вот это уже структурная проблема, и на экспорте она чинится намного дешевле, чем в уровне.
Unreal обращается с импортированной геометрией не так, как Unity: Nanite меняет смысл бюджета треугольников, Lumen — цену прохода освещения, World Partition — само назначение актора. Если вы только что с другого движка, та же задача на стороне Unity устроена иначе, и почти ни одна привычка не переносится.
Сантиметры и проверка дверью
Один Unreal Unit — это один сантиметр. Контроллер персонажа, физика, навмеш, distance fields и сетка стриминга исходят из этого, и ни один из них не сообщит, что предположение неверно. Уровень просто ощущается неправильно.
Проверка на десять секунд
Поставьте меш в начало координат, вытащите персонажа из шаблонного контента и поставьте его в дверной проём. Дефолтная капсула — радиус 34 uu, половина высоты 88 uu, то есть цилиндр 176 см; считайте игрока за 180 см. Проём на правдоподобной улице — 210–230 uu в высоту и 110–140 uu в ширину. Дверь в 2,2 uu означает промах в сто раз. Дверь в 220 uu рядом с тротуаром в 800 uu означает, что единицы верные, а пропорции нет, — и это проблема генератора, а не импортёра.
Откуда берётся множитель 100
FBX хранит коэффициент единиц в глобальных настройках, и Unreal его читает, — поэтому FBX из сантиметрового инструмента обычно приезжает нужного размера. У glTF поля единиц нет вообще: спецификация просто объявляет метры, поэтому конверсию приходится предполагать, а не вычитывать. Померьте дверь и, если промах в сто раз, поставьте Import Uniform Scale равным 100. Правьте масштаб на ассете или на экспорте, но не скейлом актора в уровне, где эту странность унаследуют коллизии, distance fields и навигация.
FBX или GLB и как каждый приезжает
Что лучше вообще — отдельный разговор. Здесь важно другое: во что каждый формат превращается внутри проекта UE5.
FBX
FBX идёт через давний FBX-импортёр Unreal и порождает ассеты: статик-меши, материалы, распакованные в контент текстуры. Опция Combine Meshes решает, получите вы один меш или много, и для квартала нужно много. Чего FBX через текущий путь Interchange не даёт — это расстановки: Import Into Level его не принимает. То есть у вас папка мешей, и расставляете вы их сами.
glTF и GLB
glTF обрабатывает Interchange. Текстуры и материалы, вшитые в .glb, приезжают сами, а импортированные материалы становятся инстансами родительских glTF-материалов Unreal, а не новыми материалами, — слегка раздражает, если в проекте свои мастер-материалы. Причина предпочесть GLB для целой локации — File, затем Import Into Level, который Interchange поддерживает именно для glTF: он читает иерархию узлов и строит акторы в той расстановке, которую описывал файл. Для квартала расстановка и есть большая часть работы. Список форматов, которые принимает Import Into Level, менялся от версии к версии, так что проверьте в своей.
Редактор вроде Cuberta, где агент строит локацию отдельными объектами и экспортирует GLB или FBX с текстурами, отдаёт файл, в котором эта иерархия уже есть. Квартал, приехавший одним сваренным мешем, потерял её ещё до вашего первого клика.
Nanite: что он меняет и чего не оправдывает
Когда сгенерированный меш выигрывает
Nanite — конвейер виртуализированной геометрии для статик-мешей, и он окупается на действительно плотной геометрии: децимированные сканы, тяжело китбашенные фасады, крыши с настоящей смоделированной детализацией — там, где вручную собирать цепочки LOD мучительно. Он же схлопывает арифметику дро-коллов: вместо вызова на слот материала на инстанс сцена с Nanite стоит примерно один вызов на материал.
Когда включить не получится
- Скелетные меши и вершинная анимация. Nanite — про статик-меши; всё, что скиннится, гоняет морф-таргеты или анимируется в вершинном шейдере, остаётся за бортом.
- Прозрачные материалы. Меш с translucent-материалом не рисуется вообще — стандартное объяснение здания, которое есть в аутлайнере и которого нет на экране. Masked-материалы в актуальных версиях поддерживаются: при включённой настройке проекта и корректно собранном материале.
- Железо. Nanite требует DirectX 12 с Shader Model 6 или эквивалент на Vulkan.
Топологию он не чинит
Включение Nanite ничего не сваривает, не разворачивает нормаль обратно и не разводит две оболочки в одном объёме. Заодно он строит из того же исходника грубый fallback-меш, и именно fallback используют комплексная коллизия и Lightmass: плохой вход даёт плохую коллизию и плохой бейк, просто менее заметно. На коробочных зданиях в пару тысяч треугольников, из которых состоит большинство сгенерированных кварталов, Nanite тоже не бесплатен: у конвейера есть фиксированная цена, а крупные треугольники во весь экран — худший случай для его растеризатора. Включите, померьте, будьте готовы выключить. Заслуживает ли геометрия этого конвейера вообще — вопрос оптимизации.
Сколько слотов материалов — уже много
У статик-меша одна секция на слот материала. Без Nanite это дро-колл на слот на инстанс; с Nanite вы платите ближе к одному разу на уникальный материал, но каждый всё равно шейдится и остаётся отдельной пермутацией шейдера. Для сгенерированного здания здоровая норма — от одного до четырёх слотов: стена, крыша, остекление, отделка. После примерно восьми вы платите за вариативность, которую никто не увидит, а четыреста зданий по десять слотов — проблема стриминга текстур задолго до того, как станет вопросом красоты. Отдельно стоит подумать про стекло: окна на translucent-материале означают, что этот меш не может быть Nanite. Либо вынесите остекление в отдельный меш и оставьте без Nanite только его, либо сделайте окна непрозрачными с сильным отражением.
Lumen и когда всё-таки запекать
Новые проекты UE5 по умолчанию светятся Lumen, и для генеративного контента этот дефолт — подарок: не нужны лайтмап-развёртки, не нужен бейк, квартал можно перегенерировать в полдень и увидеть освещённым в десять минут первого.
У Lumen есть требование, которое генеративная геометрия нарушает постоянно. Без аппаратного трассирования он трассирует mesh distance fields, а distance fields не умеют представлять очень тонкие детали и односторонние поверхности, если смотреть на них сзади. Стены тоньше примерно 10 см текут светом, а сгенерированные здания полны односторонних фасадов и стен нулевой толщины — отсюда закат, просачивающийся сквозь закрытую витрину. По порядку предпочтения: давать стенам реальную толщину при генерации, включать двустороннюю генерацию distance field на проблемных мешах, поднимать Distance Field Resolution Scale тем, кто продолжает капризничать. Та же система наказывает за слияние: многоэтажное здание одним мешем получает грубое поле расстояний и само себя затеняет.
Если запекать всё же приходится — обычно из-за железа, которому Lumen не по карману, — закладывайте на это время честно. Каждому мешу нужна непересекающаяся лайтмап-развёртка, у сгенерированных мешей её обычно нет, а автогенерация Unreal по грязной топологии — это ровно те самые предупреждения о наложении UV. При включённом Nanite Lightmass работает по fallback-мешу, поэтому запечённый силуэт грубее того, что во вьюпорте.
Коллизии: комплексная — ловушка
Импортированный статик-меш приезжает с комплексной коллизией — собственными треугольниками, по которым идут трейсы, — и совсем без простой, если в файле не было фигур UCX_. Квартал твёрдый для луча и ни для чего больше, и очевидное решение здесь неверное:
Если вы используете UseComplexAsSimple, симулировать объект нельзя — но им можно сталкиваться с другими симулируемыми (простыми) объектами.
Сверх того, пополигонные запросы по кварталу треугольников дороги, а с включённым Nanite вы опрашиваете fallback-меш. Чем пользоваться вместо этого:
- Здания: бокс или несколько выпуклых оболочек. Ничьему контроллеру персонажа не нужен карниз.
- Дороги и тротуары: плоскость или ландшафт под ними, а не треугольники дорожного меша.
- Пропсы: auto convex decomposition в редакторе статик-меша, в пределах нескольких оболочек.
- Всё, что симулирует физику: только простая коллизия.
- Декоративная геометрия: коллизии нет вообще — в сгенерированном квартале это часто половина акторов.
- Рельеф и неподвижная обстановка: здесь комплексная коллизия действительно уместна. Для этого она и есть.
Если исходный инструмент умеет экспортировать фигуры UCX_ рядом с рендер-мешами — пользуйтесь. Это быстрее, чем собирать оболочки по одному ассету.
World Partition и довод в пользу тайлов
World Partition держит мир в одном постоянном уровне, режет его на ячейки рантайм-сетки и стримит эти ячейки по положению игрока. Единица стриминга — актор, и всё остальное про деление сгенерированного квартала следует отсюда.
Квартал, импортированный одним актором, стримиться не может. Акторы крупнее размера ячейки поднимаются на верхние уровни иерархии сетки, а акторы поперёк границ ячеек остаются загруженными независимо от того, рядом ли кто-нибудь. Импортируйте его множеством акторов — по одному на здание или на небольшую группу — и система сработает сама.
Отсюда довод: экспортировать тайлами, а не одним файлом. Квартал, нарезанный квадратами по 100–200 м, даёт импорты, которые доходят до конца, естественную гранулярность акторов и возможность перегенерировать один блок, не переимпортируя остальные тридцать. Если инструмент позволяет ограничивать область работы — в Cuberta можно отметить, где агенту строить, а где нет, — генерировать и экспортировать можно поблочно: то же деление, только на шаг раньше.
Когда акторы уже в уровне и помечены как spatially loaded, запустите Build, затем Build HLODs, прежде чем судить о том, как место читается издалека. HLOD собирает упрощённые прокси для ячеек за пределами дистанции стриминга, и линия горизонта остаётся на месте, а не схлопывается в пустоту.
Короткий список проблем
| Симптом | Обычная причина |
|---|---|
| Всё в 100 раз не того размера | Предположение о единицах на экспорте: поставьте Import Uniform Scale, затем чините экспортёр |
| Меш есть в аутлайнере, но не на экране | Translucent-материал на Nanite-меше или вывернутые нормали |
| Персонаж проходит сквозь стену | Нет простой коллизии; тримеш отвечает только на трейсы |
| Свет течёт в закрытые помещения | Стены тоньше 10 см или односторонние |
| Предупреждения о наложении UV при билде света | Автогенерация лайтмап-развёрток по грязной топологии |
| Ничего не выгружается | Квартал одним актором либо акторы не spatially loaded |
| Всё серое | Текстуры не вшиты в файл или инстансы материалов потеряли родителя |
Что на самом деле решает исход
Nanite означает, что спорить о треугольниках приходится редко, Lumen — что редко приходится запекать. Вдвоём они убирают почти всё, что раньше делало импорт большого окружения мучительным. Чего UE5 не прощает — это структуру: единицы, в которых написан файл, количество акторов, из которых состоит квартал, и то, сообщил ли кто-нибудь движку, что здесь твёрдое.
Все три решаются до того, как вы нажали Import, и два из трёх решает то, что этот файл экспортировало. Отсюда довод в пользу генерации локации в чём-то, что думает в метрах и держит здания отдельными объектами, — а не красивого единого меша, который вы потом неделю разбираете.