把 AI 生成的整片场景导入 Unreal Engine 5

你有一整个街区的 GLB 和一个 UE5 工程。导入时要检查什么:单位、Nanite、Lumen、碰撞,以及为什么一个巨大的 Actor 永远不会被流送。

由智能体在 Cuberta 中搭建的市中心:玻璃塔楼环绕高层摩天楼,街道宽阔

一个四十栋建筑的街区 GLB 落进 Content Drawer,变成一百六十个 Static Mesh,而人体模型的头顶高过了二楼窗户。这是单位问题,一分钟能修好。一小时后你才发现,整个街区是作为一个 Actor 进来的,World Partition 默默判定它永远处于加载状态。这一条属于结构问题,在导出端修远比在关卡里修便宜。

Unreal 对待导入几何体的方式和 Unity 不同:Nanite 改变了三角面预算的含义,Lumen 改变了光照通道的代价,World Partition 改变了 Actor 的用途。如果你刚从另一个引擎过来,Unity 那一侧的同一个问题是另一套取舍,几乎没有一条习惯可以直接迁移。

厘米,以及那个「门」测试

一个 Unreal Unit 就是一厘米。角色控制器、物理、导航网格、Distance Field 和流送网格都建立在这个前提上,而且没有任何一个会在前提错误时提醒你。关卡只是「手感不对」。

十秒钟就能做的检查

把模型放到原点,从模板内容里拖一个第三人称角色出来,让它站进门洞。默认角色胶囊体半径 34 uu、半高 88 uu,也就是一根 176 cm 的圆柱;把玩家当成大约 180 cm。一条可信街道上的门洞高 210–230 uu、宽 110–140 uu。门高 2.2 uu,说明你差了一百倍。门是 220 uu 而旁边人行道宽 800 uu,说明单位是对的、比例是错的——这是生成器的问题,不是导入器的问题。

那个 100 倍是从哪来的

FBX 在全局设置里存了单位缩放系数,Unreal 会读它,所以从以厘米为单位的工具导出的 FBX 通常尺寸正确。glTF 干脆没有单位字段:规范直接声明数值以米为单位,所以这个换算只能靠假设而不是读取。量一下门,如果差一百倍,就把这次导入的 Import Uniform Scale 设成 100。修在资产上或导出端,别靠在关卡里缩放 Actor——碰撞、Distance Field 和导航都会继承那份别扭。

FBX 还是 GLB,以及各自是怎么进来的

哪个格式整体更好是另一场讨论。这里要紧的是:它落进 UE5 工程之后各自变成了什么。

FBX

FBX 走 Unreal 用了很多年的 FBX 导入器,产出的是资产:Static Mesh、材质、解包到内容目录里的贴图。Combine Meshes 决定你拿到一个网格还是很多个,对一个街区来说你要的是很多个。当前 Interchange 路径下 FBX 给不了你的是「排布」——Import Into Level 不接受它——所以你拿到的是一个装满网格的文件夹,摆放得自己来。

glTF 与 GLB

glTF 由 Interchange 框架处理。嵌在 .glb 里的贴图和材质会自动进来,而导入的材质是 Unreal 自带 glTF 父材质的实例,不是全新的材质资产——如果你的工程有自己的母材质,这会让你略感烦躁。整片场景优先选 GLB 的理由是 File 里的 Import Into Level,Interchange 对 glTF 支持这一路:它读取节点层级,按文件描述的排布在关卡里生成 Actor。对一个街区来说,排布就是大部分工作量。Import Into Level 支持哪些格式在各引擎版本之间变动过,所以先在你自己的版本里确认。

Cuberta 这样的编辑器——智能体把场景搭成一个个独立物件,并导出带贴图的 GLB 或 FBX——交给你的文件里层级本来就在。一个焊成单一网格的街区,在你开始之前就已经把它丢掉了。

Nanite:它改变了什么,又不能替你免掉什么

什么样的生成网格能从中获益

Nanite 是面向 Static Mesh 的虚拟化几何管线,它在真正稠密的几何上才划算:抽面后的扫描数据、大量拼装的立面、带真实建模细节的屋顶——这些正是手工搭 LOD 链最痛苦的场合。它还压平了 draw call 的算术:不再是「每个实例每个材质槽一次绘制」,Nanite 场景更接近「每个材质一次绘制」。

什么时候根本开不了

  • 骨骼网格与顶点动画。Nanite 面向 Static Mesh;一切蒙皮的、用 Morph Target 的、在顶点着色器里做动画的,都在它之外。
  • 半透明材质。带 translucent 材质的 Nanite 网格根本不会被渲染出来——这就是「Outliner 里有、屏幕上没有」那栋建筑的标准解释。Masked 材质在当前版本里是支持的,前提是打开工程设置并且材质配置正确。
  • 硬件。Nanite 需要 DirectX 12 加 Shader Model 6,或 Vulkan 上的等价物。

它不会替你修拓扑

打开 Nanite 不会焊接任何东西,不会把翻转的法线翻回来,也不会解决两层壳体占据同一空间。它还会用同一份源数据生成一个粗糙的 fallback 网格,而复杂碰撞和 Lightmass 用的正是这个 fallback:糟糕的输入照样产出糟糕的碰撞和糟糕的烘焙,只是更不显眼。在几千面的方盒子建筑上——大多数生成街区就是由它们组成的——Nanite 也并不免费:管线本身有固定开销,而占满屏幕的大三角形恰恰是它的光栅化器最不擅长的情况。打开、测量、并且做好关掉它的准备。这堆几何体到底值不值得这条管线,是另一个优化问题

多少个材质槽算多

一个 Static Mesh 每个材质槽对应一个 section。不开 Nanite 时,这就是「每个实例每个槽一次 draw call」;开了 Nanite,你付的更接近「每个唯一材质一次」,但每个材质仍然要着色,仍然是一份独立的着色器排列。对一栋生成的建筑来说,一到四个槽是健康的:墙、屋顶、玻璃、装饰线脚。超过每栋八个左右,你就是在为没人会看到的变化付钱;四百栋建筑每栋十个槽,早在成为美术问题之前就已经是贴图流送问题了。值得单独想一想的是玻璃:窗户用半透明材质,就意味着那个网格不能是 Nanite。要么把玻璃拆成独立网格、只让它不走 Nanite,要么把窗户做成带强反射的不透明材质。

Lumen,以及什么时候还是要烘焙

新建的 UE5 工程默认用 Lumen 照明,对生成内容来说这个默认值是个礼物:不需要光照贴图 UV,不需要烘焙,中午重新生成一遍街区,十分钟后就能看到它被照亮。

Lumen 有一个要求,生成几何体几乎总在违反它。没有硬件光追时,Lumen 追踪的是 Mesh Distance Field,而 Distance Field 无法表达极薄的特征,也无法表达从背面看的单面几何。薄于约 10 cm 的墙会漏光,而生成的建筑里到处是单面立面和零厚度墙体——于是黄昏的光就从关着的店铺门面里渗了进来。按优先级排的修法:生成时就给墙真实厚度,对有问题的网格打开双面 Distance Field 生成,对仍然不听话的把 Distance Field Resolution Scale 提上去。同一套系统也惩罚合并:多层建筑做成一个网格,会得到一个粗糙的距离场并自我遮挡。

如果确实必须烘焙——通常是因为目标硬件吃不下 Lumen——那就老实地为它留出时间。每个网格都需要不重叠的光照贴图 UV,生成的网格通常没有,而 Unreal 在脏拓扑上自动生成的结果,正是那些 UV 重叠警告的来源。开着 Nanite 时,Lightmass 是按 fallback 网格工作的,所以烘出来的轮廓比视口里看到的更粗。

碰撞:复杂碰撞是个陷阱

导入的 Static Mesh 带着复杂碰撞进来——就是它自己的三角面,用于射线检测——而且除非源文件里带了 UCX_ 形体,否则完全没有简单碰撞。这个街区对射线是实心的,对别的一切都不是,而最显而易见的那个修法恰恰是错的:

如果你使用 UseComplexAsSimple,这个物体就无法参与模拟,但它可以与其他被模拟的(简单)物体发生碰撞。

此外,对一个街区量级的三角面做逐多边形查询代价很高,而开着 Nanite 时你查询的是 fallback 网格。该用什么代替:

  • 建筑:一个盒子,或者少数几个凸包。没有谁的角色控制器需要那道檐口。
  • 道路与人行道:一个平面,或者下面的地形,而不是路面网格的三角面。
  • 道具:在 Static Mesh 编辑器里做自动凸分解,控制在几个凸包以内。
  • 任何要参与物理模拟的东西:永远用简单碰撞。
  • 纯装饰几何:干脆不要碰撞——在生成街区里这常常是一半的 Actor。
  • 地形和纯静态场景件:这里用复杂碰撞是真的合适。它本来就是干这个的。

如果你的源工具能在渲染网格旁边一起导出 UCX_ 碰撞体,就用它。这比一个资产一个资产地生成凸包快得多。

World Partition,以及分块导出的理由

World Partition 把世界保存在一个持久关卡里,把它切分成运行时网格单元,再按玩家所在位置流送这些单元。流送的单位是 Actor,关于生成街区该怎么切分,其余一切都从这里推导出来。

作为一个 Actor 导入的街区没法被流送。大于网格单元尺寸的 Actor 会被提升到网格层级的更高层,而横跨单元边界的 Actor 无论附近有没有人都会保持加载。把它导入成许多个 Actor——一栋建筑一个,或者一小簇一个——系统不用你操心就会开始干活。

由此得出那个实用结论:按瓦片导出,而不是导出成一个文件。把街区切成 100–200 m 的方块,能换来能顺利跑完的导入、天然合适的 Actor 粒度,以及重新生成一个街块而不必重导另外三十个的能力。如果你的工具允许限定它工作的范围——在 Cuberta 里可以标出哪里让智能体建、哪里不建——你就能一块一块地生成和导出:同一次切分,只是提前了一步。

Actor 都进来并且标记为 spatially loaded 之后,先跑一次 Build 里的 Build HLODs,再去评判这地方远看是什么样。HLOD 为流送范围之外的单元生成简化代理,天际线因此会留在那里,而不是弹成一片虚无。

一份简短的排查清单

现象常见原因
所有东西差 100 倍导出端的单位假设;先设 Import Uniform Scale,再去修导出器
Outliner 里有,屏幕上没有Nanite 网格上用了半透明材质,或者法线反了
角色穿墙而过没有简单碰撞;三角网格只回应射线检测
光渗进封闭房间墙不足 10 cm 厚,或者是单面的
构建光照时报 UV 重叠在脏拓扑上自动生成的光照贴图 UV
什么都不会被卸载街区是一个 Actor,或者 Actor 没有标记为 spatially loaded
全是灰的贴图没有嵌进文件,或者材质实例丢了父材质

真正决定结果的东西

Nanite 意味着你很少需要为三角面争论,Lumen 意味着你很少需要烘焙。两者加起来,去掉了过去导入大型环境时绝大部分痛苦。UE5 不会宽容的是结构:文件是用什么单位写的、街区由多少个 Actor 组成、以及有没有人告诉过引擎哪里是实心的。

这三件事都在你按下 Import 之前就已经决定了,其中两件由导出这个文件的东西决定。这也就是那个论据:值得在一个以米思考、把建筑保持为独立物件的工具里生成场景——而不是生成一个漂亮的单一网格,然后花一周把它拆开。