ИИ-генерация уровней для Godot: от GLB до настоящей сцены
Как занести сгенерированную локацию в Godot через GLB и довести её до уровня: коллизии, окклюдеры, свет и повторный импорт без потери работы.
Положите .glb в папку проекта Godot — и это уже сцена. Ни плагина, ни конвертера, ни диалога с вопросом, чему равна единица измерения. glTF — формат, который документация самого Godot помечает как рекомендованный, а импортёр встроен в движок. Это кратчайший путь от сгенерированной локации к тому, по чему можно ходить, — и здесь простая часть заканчивается. Всё, чего файл не несёт, строить всё равно вам: коллизии, окклюдеры, решение по свету и способ переимпортировать локацию, не выбросив работу прошлой недели.
Почему glTF — кратчайший путь в Godot
Страница поддерживаемых форматов в документации Godot откровенна насчёт иерархии: glTF 2.0 указан как рекомендованный — и текстовый .gltf, и бинарный .glb. FBX работает: с 4.3 он идёт через импортёр ufbx, а не через старый конвертер FBX2glTF. Файлы .blend импортируются напрямую, но этот путь под капотом вызывает собственный glTF-экспортёр Blender.
Ничего не приходится переинтерпретировать. Godot использует правую систему координат с осью Y вверх и -Z как направлением взгляда камеры — ровно та договорённость, что закреплена в спецификации glTF 2.0, — и обе стороны работают в метрах. Дверь 2,1 м в исходнике остаётся дверью 2,1 м во вьюпорте, и никто не трогает поле масштаба. Подробный разбор форматов — в статье GLB против FBX для игровых движков.
Одно следствие задаёт весь дальнейший процесс: Godot не может сохранять поверх исходного 3D-файла. Рядом создаётся .import, а сконвертированные ресурсы лежат в скрытой папке .godot внутри проекта, так что исходный .glb остаётся источником истины, а импортированная сцена каждый раз пересобирается из него. Поддержка первоклассна и в собранных проектах, так что тот же файл можно загрузить в рантайме через GLTFDocument.
Cuberta экспортирует GLB с текстурами, так что район, который агент разложил по земле — дороги, перекрёстки, тротуары, кварталы зданий, уличная мебель, свет, — приезжает одним файлом: копируете его в res://.
Настройки импорта, которые важны для района
Выделите .glb в доке FileSystem — и док Import заполнится опциями. Большинство значений по умолчанию годятся для персонажа. Очень немногие рассчитаны на целый город.
| Опция | Для района | Почему |
|---|---|---|
| Meshes > Generate LODs | Оставить включённым | Дальние здания теряют треугольники сами; в 4.6 LOD стали лучше держать форму мешей из отдельных частей. |
| Meshes > Create Shadow Meshes | Включить | Сваривает вершины для теневого прохода: платите раз на импорте, экономите каждый кадр. |
| Meshes > Light Baking | Static Lightmaps — только если будете запекать | Опция генерирует UV2 на импорте — самая долгая часть большого файла. |
| Meshes > Lightmap Texel Size | 0,5 и выше | Значение 0,2 рассчитано на комнату, а не на район. |
| Meshes > Ensure Tangents | Выключить, если нет карт нормалей | Меньше файл, быстрее импорт. |
| glTF > Embedded Texture Handling | Extract Textures | Настоящие файлы изображений с собственным VRAM-сжатием и мипмапами. |
| Animation > Import | Выключить для статики | Импортировать нечего. |
Кнопка Advanced… под доком — или двойной клик по файлу — открывает диалог Advanced Import Settings. Именно там делается работа по узлам: слева дерево всех узлов glTF, посередине превью, справа опции узла, включая Skip Import для служебной геометрии, которая всегда просачивается в экспорт.
Как сделать сцену редактируемой: наследование и инстансы
Импортированную сцену нельзя править напрямую, и это намеренно: дерево пересобирается при каждом переимпорте, так что правки внутри него были бы стёрты. Выберите Scene > Open Scene… на .glb — Godot предложит унаследованную сцену; правый клик и New Inherited Scene ведут туда же.
Задокументированных ограничений там ровно два: узлы базовой сцены нельзя удалить (добавлять новые где угодно можно) и подресурсы нельзя редактировать на месте. Для материалов лечится пунктом Actions… > Extract Materials: он пишет .tres и переключает сцену на ссылки. Альтернатива — инстанцировать локацию внутрь уровня .tscn и держать правки там.
- Принадлежит локации — прокси-коллизии, окклюдеры, узел LightmapGI, области навигации. Унаследованная сцена, по одной на .glb.
- Принадлежит игре — точка старта, триггеры, спавны, дверь, которая открывается. Сцена уровня, которая инстанцирует локацию.
- Принадлежит материалу — кастомный шейдер на дороге. Извлечённые .tres, которые переживают переимпорт по устройству системы.
Коллизии, которые район может себе позволить
Godot генерирует коллизии двумя путями. Поузлово в диалоге Advanced Import Settings опция Generate > Physics создаёт родительский PhysicsBody3D, а формы коллизий становятся соседями MeshInstance3D; Body Type выбирает StaticBody3D, RigidBody3D или Area3D, Shape Type — саму форму. Документация высказывается прямо: Trimesh даёт точную потреугольную коллизию, но работает только со статичным телом, и «для статичной геометрии уровня используйте Trimesh». У Decompose Convex есть Precision: выше значение — детальнее коллизия и дороже CPU.
Второй путь — суффиксы в именах узлов исходного файла. -col добавляет дочернюю статическую коллизию по треугольникам, -colonly убирает видимый меш и оставляет StaticBody3D, -convcol и -convcolonly делают выпуклые варианты. Разделителем может быть -, $ или _, регистр не важен — это полезно знать и в обратную сторону, потому что сгенерированный объект по имени shop_col вас удивит. Поставьте nodes/use_node_type_suffixes в false, и механизм замолчит.
- Земля, дороги, тротуары, бордюры, лестницы — Trimesh. По этому ходит игрок, и точность формы здесь и есть смысл.
- Коробки зданий — не Trimesh. Фасад на 30 000 треугольников даёт коллайдер на 30 000 треугольников, в который вы будете только упираться. Хватит пары боксов руками.
- Уличная мебель, заборы, фонари — выпуклая коллизия либо вообще ничего там, куда игрок не дойдёт.
- Растительность — обычно ничего. Коллайдер на каждой карточке листвы — счёт, который никто не соглашался оплачивать.
Рекомендация Godot короче: когда возможно, используйте несколько примитивных форм вместо треугольного меша или выпуклых оболочек. С 4.6 физический движок по умолчанию — Jolt: быстрее прежнего, но не настолько, чтобы город из trimesh-зданий стал хорошей идеей.
Свет: запекать или отдать SDFGI
| Параметр | LightmapGI | SDFGI |
|---|---|---|
| Запекание | Минуты на GPU, повторно после любого изменения геометрии | Нет; каскады строятся вокруг камеры |
| Нужен UV2 | Да — Light Baking в Static Lightmaps | Нет |
| Динамика | Получает непрямой свет через пробы | Получает GI, но не вносит вклад |
| Отражения | Нет; нужен ReflectionProbe или Sky | Свои, только на непрозрачных материалах |
| Цена в рантайме | Почти нулевая; идёт на встроенной графике | Самый дорогой вариант GI в Godot |
| Ломается на | Скорости итераций | Быстром движении камеры — видны сдвиги каскадов |
В документации есть фраза про лайтмапы, которая выглядит как закрытие вопроса:
Не подходит для процедурно генерируемых уровней.
Вердикт про уровни, которые генерируются в рантайме, и для них он верен. Локация, сгенерированная один раз, экспортированная и замороженная, — такая же статичная геометрия, как любое построенное здание, и запекание работает прекрасно. Загвоздка в том, сколько тянет на себе слово «замороженная»: пока вы двигаете гавань и расширяете главную улицу, каждый переимпорт обнуляет запекание, а запекание района измеряется минутами.
Поэтому разделение не техническое, а временнóе. Пока планировка движется, работайте на SDFGI: включается в Environment, запекания не требует вообще и просит только, чтобы у мешей был режим глобального освещения Static — этим управляет опция Light Baking в доке импорта. Когда планировка устоялась, переключайтесь на Static Lightmaps и запекайте один раз. Развёртка UV2 кэшируется между переимпортами, а файлы .unwrap_cache должны лежать в системе контроля версий: они держат UV2 одинаковыми на разных машинах и версиях движка.
Окклюзия, потому что город — это в основном стены
Документация Godot по оптимизации берёт городок как разобранный пример: идя по улице, вы видите несколько зданий, небо и пару птиц, а наивный рендерер отправляет на отрисовку соседнюю улицу, людей на ней и здания за ними. Depth prepass избавляет от полного шейдинга, но не от отправки.
Включите Rendering > Occlusion Culling > Use Occlusion Culling в настройках проекта — понадобится переключатель Advanced, перезапуск не нужен. Добавьте OccluderInstance3D, выделите его и нажмите Bake Occluders в 3D-вьюпорте. Польза от запекания решается тремя вещами:
- Запекаются только узлы MeshInstance3D. MultiMeshInstance3D, частицы и CSG игнорируются, так что растительность, перенесённая в MultiMesh, перестала быть окклюдером.
- Прозрачные материалы исключены. Площадь из стеклянных башен не перекрывает ничего. Документация отмечает, что метод эффективнее всего в интерьерах с множеством небольших комнат и нормальными непрозрачными стенами.
- Пользуйтесь маской и упрощением. Bake > Cull Mask убирает динамику из запекания; Bake > Simplification меняет точность на время CPU — если объекты пропадают там, где должны быть видны, значение задрано.
Окклюдеры можно получить и на импорте: Generate > Occluder предлагает Mesh + Occluder или Occluder Only с настройкой Simplification Distance, а суффиксы -occ и -occonly делают то же из исходного файла. Чтобы увидеть, что реально отсекается, включите Perspective > Display Advanced > Occlusion Culling Buffer.
Количество узлов, инстансинг и где начинает болеть
Сгенерированный район приезжает как один MeshInstance3D на каждый экспортированный объект. Несколько сотен узлов — не проблема; проблема в drawcall'ах. Forward+ инстансит автоматически, когда узлы MeshInstance3D делят один меш и один материал, без настройки, — но только для непрозрачных материалов и alpha-test, никогда для альфа-блендинга и вообще никак в рендерерах Mobile и Compatibility.
Это ловушка, которую стоит проверить первой. Экспортёр, выдающий каждому объекту собственный материал, молча выключает автоматический инстансинг, и сорок одинаковых фонарей становятся сорока drawcall'ами. Смотрите на количество материалов раньше, чем на количество треугольников: Extract Materials и сведение дубликатов на один ресурс часто дают для кадра больше, чем любая работа с мешами, — а это отдельная тема.
MultiMesh — ответ для того, что приходит тысячами: трава, брусчатка, секции забора, деревья. Документация ставит границу так: тысячи постоянно обрабатываемых экземпляров — идите напрямую через серверы, сотни тысяч и миллионы — остаётся только MultiMesh. Плата в том, что отдельные экземпляры не отсекаются по фрустуму: MultiMesh рисуется целиком или не рисуется вовсе, поэтому режьте его по кварталам.
По дальности Mesh LOD с опции импорта работает сам, а Visibility Ranges — ручное дополнение. Пример в документации — как раз городской квартал: дайте низкодетальному мешу BatchOfHouses значение Visibility Range Begin и назначьте его Visibility Parent для четырёх детальных домов. Смотрите при этом на Objects Drawn и Draw Calls во вкладке Monitors отладчика: интуиция насчёт того, что сработало, ошибается примерно в половине случаев.
Переимпорт без потери недели
Вот сценарий, который решает, рабочий ли это процесс. Вы меняете городок в генераторе, экспортируете, перезаписываете location.glb, Godot переимпортирует. Что происходит с двумя днями работы на стороне Godot?
Часть переживает это по устройству системы. Извлечённые материалы при переимпорте не перезаписываются — с оговоркой, что переименование материала в исходнике рвёт связь. Кэш развёртки UV2 сохраняется, анимации, сохранённые в файл, сохраняют добавленные дорожки. Тихо не переживает всё, что адресуется через NodePath внутрь импортированного дерева: если экспорт переименовал Building_014 или переставил соседей, переопределения в унаследованной сцене приземляются в никуда. Есть и открытый на 4.7 баг: сохранение унаследованной сцены после переимпорта GLB пересоздаёт уникальные идентификаторы узлов, и diff'ы .tscn зарастают шумом.
- Держите свою работу соседями, а не детьми. Прокси-коллизии, окклюдеры, узел LightmapGI и точки спавна кладите под собственный узел, а не в дети импортированных узлов, чьи имена вы не контролируете.
- Используйте import script. В доке импорта есть поле Import Script > Path, указывающее на EditorScenePostImport с функцией
_post_import(scene). Назначение коллизий по шаблону имени, установка режимов GI, подмена материалов — всё, что выражено кодом, само отрабатывает при каждом переимпорте. - Экспортируйте кусками. Один .glb на район либо один на тип здания плюс файл планировки. Тогда правка гавани переимпортирует гавань и не трогает старый город. В Cuberta это сводится к тому, чтобы отметить, где агенту строить, а где нет.
Преимущество Godot здесь реальное, но узкое. Он снимает трение формата — ни конвертации, ни переговоров о единицах, ни переворота осей, — и это стоит больше, чем звучит, когда одну локацию вы переимпортируете двадцать раз. Чего он не снимает — левел-дизайн. Импортированное дерево — это геометрия с именами; коллизии, окклюзия, свет и решение о том, что здесь один объект, а что тысяча, по-прежнему за вами. Автоматизировать имеет смысл ровно одно: чтобы всё это происходило одинаково каждый раз, когда вы нажимаете Reimport.