UE5.8 MetaHuman Collections 与 NPC Crowd:从剧场里坐满的观众说起

📑 目录

UE5.8 MetaHuman Collections 与 NPC Crowd:从剧场里坐满的观众说起

引子:剧场里的观众

阿琳所在的项目正在做一款以现代都市为背景的叙事游戏。第三幕的高潮发生在一座能容纳九百人的大剧院里:主角混在观众席中,舞台灯光亮起,反派从二楼包厢缓缓走下。镜头需要扫过全场,每张脸都不能是复制粘贴的同一套五官。

第一版方案很直接。美术组手动摆放了九百个 MetaHuman Actor,每个人都用独立 Skeletal Mesh、独立材质实例、独立动画蓝图。场景在编辑器里还能勉强打开,一进 PIE 模式,帧数直接掉到 12。内存占用冲破 8 GB,动画更新把主线程占满,GPU 也叫苦不迭。策划在旁白里还没念完第一句台词,玩家已经因为卡顿摔了手柄。

阿琳把人群删掉到两百人,帧数回到 45。可导演要求镜头里必须看到满座。两百个座位空着,气势差了一大截。她需要一种方法:镜头拉近时,前排观众得像真实 MetaHuman 一样有表情、有衣褶、有头发细节;镜头拉远时,后排观众可以简化成几根骨骼加一张贴图,但整体上仍然像一群人,而不是一群克隆体。

这就是 UE5.8 中 MetaHuman Collections 与 NPC Crowd 技术要解决的核心问题。

flowchart TD
    subgraph source ["MetaHuman 源资产"]
        dna["DNA 文件"]
        rig["Rig Logic"]
        bs["Blend Shape 库"]
    end
    subgraph collection ["MetaHuman Collection"]
        preset["体型 / 年龄 / 肤色预设"]
        wardrobe["服装集合"]
        hair["发型集合"]
        decal["面部细节贴花"]
    end
    subgraph crowd ["Crowd 运行时"]
        spawn["生成器"]
        lod["LOD 管理器"]
        mass["Mass AI 代理"]
        anim["动画共享池"]
    end
    dna --> preset
    rig --> preset
    bs --> preset
    preset --> wardrobe
    preset --> hair
    preset --> decal
    collection --> spawn
    spawn --> lod
    lod --> mass
    mass --> anim

一、MetaHuman Collections:把角色做成调色盘

MetaHuman Collections 不是把九个现成角色摆进场景,而是一套组合系统。它把一个人的外观拆成几个可替换的模块:头部、发型、眉毛、眼睛、皮肤细节、服装、鞋子、配饰。每个模块内部再提供若干变体。系统像调色盘一样,从每个盘子里取一格,拼出一个看起来独一无二的人。

传统做法中,若项目需要一百个不同路人,美术要做一百个独立角色。改动一件衣服就得改一百个文件。Collections 的做法是:把头部基础 mesh、服装 mesh、发型 mesh 全部做成独立资产,再用数据表描述哪些组合是合法的。美术只维护模块本身,不维护组合结果。运行时根据随机种子和权重,从 Collection 里采样出一群人。

UE5.8 在 Content Browser 里新增了 MetaHuman Collection 资产类型。打开后能看到几个关键面板:

  • Identity:定义年龄、性别、肤色、体型等基础维度;
  • Wardrobe:挂载服装资产,可设置季节、职业、阶层等标签;
  • Hair & Groom:发型与毛发样式,支持 LOD 切换时的 cards 替代;
  • Decals & Micro-details:雀斑、皱纹、疤痕、妆面等面部细节;
  • Rules:用简单逻辑限制组合,例如某套西装只配短发,某款眼镜只配特定脸型。

规则系统的意义常被低估。如果没有规则,随机组合可能产生西装配泳裤、老人配少女发型的荒诞结果。规则让随机变得可信。阿琳给剧场观众设了三条规则:正装观众占 70%,休闲装占 25%,工作人员制服占 5%;正装观众的年龄分布偏向 25 到 55 岁;儿童只出现在家庭组合中。观众席的多样性一下子变得有逻辑,而不是纯随机堆砌。

二、高保真 NPC Crowd:生成、渲染与行为管线

Crowd 管线的起点不是场景里的 Actor,而是一份 Crowd Definition 资产。它描述三个问题:在哪里生成、生成多少人、用什么 Collection。位置信息可以来自导航网格上的随机点、PCG 生成的分布、手工放置的 Spawn Marker,也可以从 GIS 数据导入。

在剧院场景里,阿琳用了一组 Spawn Volume 覆盖观众席。每个座位上放一个 Marker,Marker 带一个朝向属性,保证观众都面朝舞台。生成时,Crowd Manager 读取 Marker 列表,根据密度参数决定哪些座位坐人。空出 10% 的座位,既省性能,也让场面更像真实剧场,不会满得发假。

生成之后,每个 NPC 并不是传统意义上的 Character Actor。UE5.8 的 Crowd System 在底层大量使用 Instanced Skeletal MeshInstanced Static Mesh。同一套服装、同一款发型会被合并成实例,交给 GPU 一次绘制。这样一千人和一百人的绘制调用差距,主要体现在实例数量上,而不是每个 NPC 都走一遍完整的渲染管线。

下面是 Crowd Definition 的一个简化配置片段,展示如何在 C++ 中注册生成规则:

UFUNCTION(BlueprintCallable)
void RegisterTheaterCrowd()
{
    FCrowdSpawnConfig Config;
    Config.Collection = TheatreAudienceCollection;
    Config.SpawnMarkers = SeatMarkerArray;
    Config.Density = 0.92f;
    Config.LODSettings.NearDistance = 800.0f;
    Config.LODSettings.MidDistance = 2500.0f;
    Config.LODSettings.FarDistance = 6000.0f;
    Config.bUseMassAI = true;
    Config.MaxConcurrentAnimations = 64;

    CrowdManager->RegisterCrowdConfig(FName("TheaterAudience"), Config);
    CrowdManager->SpawnCrowd(FName("TheaterAudience"));
}

代码很短,但覆盖了生成源、密度、LOD 距离、Mass AI 开关和动画并发数几个关键参数。实际项目中,这些参数会在不同机位反复调试,而不是一次写死。

flowchart TD
    A["Crowd Definition 资产"] --> B["读取 Spawn Marker"]
    B --> C["按密度采样座位"]
    C --> D["从 MetaHuman Collection 组合外观"]
    D --> E{距离相机多远}
    E -->|"小于 8m"| F["高保真 MetaHuman Skeletal Mesh"]
    E -->|"8m 到 25m"| G["中等 LOD 实例化骨骼网格"]
    E -->|"25m 到 60m"| H["低模 Cards + 顶点动画"]
    E -->|"大于 60m"| I["Imp Sprite / 不发人物"]
    F --> J["动画蓝图 + Facial Rig"]
    G --> K["共享动画图集"]
    H --> L["顶点动画纹理"]
    I --> M["不更新动画"]

三、实例化渲染与 LOD:距离决定细节

LOD 是 crowd 性能的生命线。没有 LOD,一千个 MetaHuman 同时更新面部表情和布料模拟,再好的显卡也顶不住。UE5.8 的 crowd LOD 分为四个层级,分界距离会根据屏幕占比动态调整,而不是固定的世界距离。

判断一个 NPC 该用哪一级 LOD,核心指标是 screen-space size,也就是这个人在屏幕上占多少像素。公式可以简化成:

S=Hobjectfdtan(θ/2)S = \frac{H_{object} \cdot f}{d \cdot \tan(\theta / 2)}

其中 SS 为屏幕空间高度占比,HobjectH_{object} 是角色在世界空间中的高度,dd 是到相机的距离,ff 是投影矩阵相关常数,θ\theta 为垂直视场角。当 SS 小于某个阈值,就切换到更粗的 LOD。

在剧院里,前排观众离镜头可能只有三米,SS 很大,必须启用完整的 MetaHuman 面部 rig, wrinkles、眼睛注视、微表情都要更新。后排观众可能只有几十个像素,启用完整 rig 是浪费。LOD1 会关闭 facial animation,LOD2 会用 impostor cards,LOD3 可能直接不渲染人物,只保留座位上一个模糊色块。

实例化渲染和 LOD 是配合工作的。假设系统把同一套西装、同一款短发的 NPC 归类到同一 bucket,这个 bucket 在远处可以一次性绘制几十个实例;靠近相机时,实例会被拆散,每个人独立处理骨骼动画和 facial rig。拆散本身有开销,所以 UE5.8 会在 CPU 端维护一个可见性列表,只把真正靠近相机的实例拆分出来,其余保持实例化。

这里容易踩的一个坑是 LOD 跳变。如果两个相邻 NPC 一个刚好在阈值内、一个刚好在阈值外,观众会明显看到一个人突然变糊。UE5.8 引入了基于噪声的滞后阈值:每个 NPC 的切换距离根据 ID 做一点随机偏移,让跳变在时间和空间上分散,人眼就不容易察觉。

四、与 Mass AI 的集成:让千人各自有主意

如果 crowd 只是播放循环待机动画,场面很快会显得死板。观众会同时转头、同时鼓掌、同时站起来。UE5.8 把 crowd 的动画和行为委托给 Mass AI 系统。Mass 原本是为了大世界海量代理设计的,特点是数据导向、任务并行、避免大量 UObject 开销。

在 Mass 的体系里,一个 NPC 不是 ACharacter,而是一组 Fragment。位置、速度、朝向、当前动画状态、目标兴趣点,都是紧凑的结构体,储存在连续的内存块里。Processor 按类型批量处理这些 Fragment,比如所有带有 FMassLookAtFragment 的实体一起计算注视目标。这样 CPU 缓存命中率高,也容易多线程并行。

剧院观众的行为可以拆成几个 State:Idle 看舞台、鼓掌、侧头交谈、起立、离场。每个 State 对应一段动画 clip 或 blend tree。Mass StateTree 负责在这些 State 之间切换。切换条件可以是导演事件,也可以是随机计时器,还可以受邻近观众影响。比如前排有人站起来鼓掌,周围观众在一定概率下会跟随。

sequenceDiagram
    participant Director as "导演事件"
    participant ST as "Mass StateTree"
    participant Frag as "观众 Fragment"
    participant Anim as "共享动画池"
    participant GPU as "GPU 实例渲染"

    Director->>ST: 触发「主角登场」事件
    ST->>Frag: 更新 InterestTarget
    Frag->>ST: 上报当前 State 与计时器
    ST->>Frag: 写入新 State:StandAndApplaud
    Frag->>Anim: 请求 applause 动画
    Anim->>Frag: 返回图集索引与帧偏移
    Frag->>GPU: 提交实例化绘制数据
    GPU-->>ST: 每帧回读可见性反馈

这个序列图里有一个容易被忽略的细节:动画不是每个 NPC 自己播放的,而是从一个共享动画池里取。池子里只存几十段常用动画,每段动画以纹理图集或顶点动画纹理的形式存在。NPC 只记录自己在图集中的偏移和播放进度,从而省下大量内存。

五、动画共享与纹理图集:省内存的诀窍

crowd 最大的内存杀手往往不是 mesh,而是动画。一个 MetaHuman 的 facial rig 有上万个 blend shape,身体动画也是每秒三十帧的骨骼变换。如果一千个人各自播放独立动画,内存会爆炸。

UE5.8 的做法是 动画图集化。把常用的待机动画、走路循环、鼓掌、交谈姿势预烘焙成纹理。纹理的每个像素储存某一帧某一骨骼的变换信息。运行时,GPU 根据实例 ID 和当前时间,在纹理里采样出这一帧的骨骼姿势,直接驱动顶点。这种方式叫 GPU Skinning via Texture,也常被称作 Vertex Animation Texture,简称 VAT。

VAT 的优点是省内存和省 CPU。CPU 不需要每帧更新骨骼,只需要算一个时间偏移;GPU 并行采样纹理,效率极高。缺点是只能播放预烘焙动画,无法做复杂 IK 或动态混合。对于远处 crowd,这个缺点完全可以接受。近处高保真 NPC 仍然走传统骨骼动画,远处用 VAT,中间过渡层用简化的骨骼动画。

另一个相关技术是 Pose Sharing。多个 NPC 可以共享同一个动画实例,只是起始时间错开。观众鼓掌时,系统只维护一段掌声动画,一千个人分别从这段动画的不同时间点开始播放。由于鼓掌动作本身有随机性,错开起始时间后,整体看起来不会完全同步。

纹理图集的大小需要精打细算。假设一段 60 帧的动画,每帧 64 根骨骼,每根骨骼用 4 个 float16 储存位置和旋转,总大小是:

Bclip=60×64×4×2=30720 bytesB_{clip} = 60 \times 64 \times 4 \times 2 = 30720 \text{ bytes}

一段动画只占 30 KB。如果把一百段动画打进一张图集,也不过 3 MB 左右。对比传统骨骼动画动辄几十 MB 的内存占用,优势非常明显。

graph LR
    A["骨骼动画源文件"] --> B["离线烘焙到纹理"]
    B --> C["动画图集"]
    C --> D["共享动画池"]
    D --> E["近处: 传统 GPU Skinning"]
    D --> F["中处: 简化骨骼 + 混合"]
    D --> G["远处: VAT 顶点动画纹理"]
    E --> H["高保真 MetaHuman"]
    F --> I["中距离 LOD"]
    G --> J["远距离 Impostor"]

六、性能预算:帧数、内存、CPU 三面墙

做 crowd 不是在追求真实,而是在预算内追求可信。UE5.8 项目通常会为 crowd 定下三项硬指标:帧时间、显存占用、主线程 CPU 时间。

帧时间方面, crowd 在主机上的目标通常是每帧不超过 2 到 3 毫秒。这个预算要分给动画更新、物理模拟、AI 决策、渲染提交。超出预算时,优先降低远处 LOD 密度,而不是砍近处质量。观众永远先看镜头对准的地方,背景糊一点影响不大。

显存方面,MetaHuman 的高清纹理是吞内存大户。UE5.8 支持纹理流送和虚拟纹理,但 crowd 场景里纹理数量多、尺寸大,还是需要严格控制。常见做法是把服装贴图合并成 atlases,把面部细节做成可开关的 decal。只有在近处才加载 4K 皮肤纹理,中远距离用 1K 或 512 即可。

CPU 方面,Mass AI 已经把大部分逻辑并行化,但 Crowd Manager 本身还在主线程。生成、销毁、LOD 切换、可见性剔除都需要主线程参与。当相机快速移动时,大量 NPC 同时进入视锥,会造成帧时间尖峰。UE5.8 为此做了异步生成和对象池:NPC 不是现生成现销毁,而是预先创建好,藏在远处,需要时激活并赋予新外观。

一个粗略的性能估算模型是:

Tframe=Tbase+nneartnear+nmidtmid+nfartfarT_{frame} = T_{base} + n_{near} \cdot t_{near} + n_{mid} \cdot t_{mid} + n_{far} \cdot t_{far}

其中 nn 是各 LOD 层级的可见 NPC 数量,tt 是每个 NPC 在该层级的平均耗时。调优 crowd 就是不断调整 LOD 距离、密度、动画并发数,让这个公式的结果稳定在目标帧时间以内。

七、平台差异:PC、主机、移动端的分水岭

同样的 crowd 配置,在不同平台上的表现可能天差地别。PC 有高端显卡和大内存,可以承受更多高保真 NPC 和更高分辨率的纹理。主机平台,尤其是 PS5 和 Xbox Series X,CPU 和 GPU 算力相对固定,预算更容易量化,但也更难通过降画质来掩盖问题。移动端基本无法运行完整 MetaHuman crowd,只能用简化的 low-poly 人物或 billboard。

PC 的优势在于可变分辨率、可扩展画质设置和更大的显存。项目可以为 PC 提供 Ultra、High、Medium 三档 crowd 预设,玩家自己选。主机上通常只给一个固定配置,因为硬件是统一的。UE5.8 在主机上推荐的做法是:把 crowd 的 LOD 分界距离定得更激进,远处人物更早切换成 impostor,近处也限制高保真 NPC 的最大数量。

PS5 的 SSD 和 I/O 架构让纹理流送很快,但 crowd 需要的纹理数量庞大,仍然要避免同屏加载过多 unique textures。Xbox Series X 的算力稍强,但内存带宽和 PS5 各有侧重,需要分别 profile。Switch 或移动端则要把 MetaHuman 完全排除在 crowd 之外,改用风格化低模或 2D 人群。

一个跨平台项目的常见策略是:用同一套 MetaHuman Collection 作为数据源,在不同平台生成不同精度的 crowd 资产。PC 用完整 Collection,主机用简化版 Collection,移动端用超低模 Collection。数据源统一,产出规格不同,便于维护。

八、UE5.8 的 crowd 改进

UE5.8 在 crowd 领域有几项值得注意的更新。

第一项是 MetaHuman Collections 正式集成到引擎默认工作流。之前这套系统更多依赖 MetaHuman Plugin 和 Quixel Bridge,现在 Content Browser 里直接新建 Collection 资产,和新建 Blueprint 一样简单。Preset 之间可以继承,子 Collection 覆盖父 Collection 的某些模块,方便项目分层管理。

第二项是 Crowd LOD 的自动分层。引擎可以根据 Collection 里的资产复杂度,自动建议 LOD 分组。美术不再需要手动把每个服装分配到哪个 LOD,系统会分析顶点数、材质复杂度、骨骼数量,给出推荐。最终决策权仍在开发者手里,但起点高了很多。

第三项是 Mass AI 与 Crowd 的深度绑定。在 UE5.7 及更早版本,Mass 和 Crowd 更像两套系统,接起来需要不少胶水代码。UE5.8 提供了统一的 Crowd Mass Processor,把生成、LOD、动画、行为串成一条线。新项目可以直接从模板开始,不用自己搭管线。

第四项是 异步 crowd 生成与流送。大世界场景里,玩家高速移动时, crowd 需要在后台不断生成和销毁。UE5.8 把生成任务拆成多个小任务,分布到多帧执行,避免单帧卡顿。配合 World Partition 的流送,远处城市的 crowd 可以按区域加载,而不是一次性全部创建。

第五项是 Groom 的 crowd 优化。头发一直是 MetaHuman 的渲染大头。UE5.8 为 crowd 中的 groom 提供了 cards-based fallback,中远距离直接用发片替代真实发丝,内存和绘制调用都大幅下降。只在特写镜头才启用完整 groom。

结语

阿琳最后把剧院场景拆成了三层:镜头前三排是 36 个完整 MetaHuman,表情和手部动作都单独控制;中间区域是 240 个中距离实例化 NPC,共用动画图集;后排和包厢是 620 个 impostor 加简化动画。总人数接近九百,主机上稳定 30 帧,PC 上稳定 60 帧。观众不再像木头,他们会鼓掌、侧头、小声交谈,偶尔也会有人起身离场。

MetaHuman Collections 与 NPC Crowd 不是让美术一键生成 magical 人群的黑箱,而是一套需要在距离、细节、行为、性能之间反复权衡的工具。理解它的管线,才能在镜头扫过全场时,既保证每一张脸都不穿帮,又不会因为九百人同时更新而把机器压垮。