← 返回筛选结果

2026 · 个人项目

Niagara GPU 虫群集成系统

一套基于 Niagara 构建的可复用 GPU Agent 框架,用于大规模虫群动画、粒子级玩法交互与实时物理反馈。

年份
2026
类型
个人项目
职责
技术美术 / Niagara 系统开发

项目概览

项目目标与实现思路

该项目最初用于支持《龙骑士》原型,随后从单一虫群特效扩展为一套可复用的实时 Agent 系统。系统将 VAT 动画、Neighbor Grid3D 邻居查询、粒子级伤害、Render Target 区域交互以及 GPU 到 Blueprint 的表现切换整合在同一套流程中,在保持大规模群体运行效率的同时,提供清晰的受击与死亡反馈。

系统拆解

虫群系统如何工作

整个系统由四部分连接起来:粒子动画、空间查询、伤害交互,以及需要物理反馈时的 GPU 到 CPU 切换。

学习参考系统中的多项基础方法参考了 Ghislain Girardot 的 Niagara 教程。我在此基础上完成了动画数据协议、Gameplay 逻辑、系统集成与性能分析。 Ghislain Girardot ↗

01

数据驱动的 VAT 动画

通过 AnimToTexture 烘焙 Idle、Walk、Hit 和 Death。每个 GPU Agent 独立选择状态、计算播放帧,再通过 Dynamic Parameters 把当前帧传给材质。

  1. AnimToTexture
  2. 状态数据
  3. 选择 Index
  4. 计算帧
  5. VAT 材质
虫群中独立运行的 VAT 动画状态
用 Vector4 协议保存四段动画的起止帧、播放速度与状态类型
用 Vector4 协议保存四段动画的起止帧、播放速度与状态类型
先处理移动、受击与死亡条件,再写入动画 Index
先处理移动、受击与死亡条件,再写入动画 Index
逐粒子计算播放时间、循环、随机偏移与最终帧
逐粒子计算播放时间、循环、随机偏移与最终帧
02

Neighbor Grid3D 群体模拟

Agent 先把位置写入 3D Grid,再查询附近 Cell,用于 Separation 和局部影响。这样只需要处理邻近数据,不必让每个粒子与整个虫群逐一比较。

  1. 初始化 Grid
  2. FillGrid
  3. QueryGrid
  4. 邻居数据
  5. Separation
碰撞与避让调试:绿色显示当前局部选择到的 Agent
碰撞与避让调试:绿色显示当前局部选择到的 Agent
Debug Grid 显示各个 Cell 中记录的 Agent 数量
Debug Grid 显示各个 Cell 中记录的 Agent 数量
03

GPU Gameplay 交互

同一套生命值和状态流程可以接收三类输入:射线点射、范围爆炸,以及写入 Render Target 的持续伤害。

  1. Gameplay 输入
  2. 定位 Agent
  3. 计算伤害
  4. 更新状态
范围爆炸伤害与 GPU 到 CPU 物理反馈
范围爆炸伤害与 GPU 到 CPU 物理反馈
Render Target 驱动的持续伤害
Render Target 驱动的持续伤害
Line Trace 定位与单体伤害
Line Trace 定位与单体伤害
并行筛选射线候选目标,并通过共享 Buffer 保留最近的有效命中
并行筛选射线候选目标,并通过共享 Buffer 保留最近的有效命中
根据爆炸半径与事件 ID 判断目标,避免同一事件跨帧重复处理
根据爆炸半径与事件 ID 判断目标,避免同一事件跨帧重复处理
把世界坐标转换为 Render Target UV,再读取持续伤害区域
把世界坐标转换为 Render Target UV,再读取持续伤害区域
04

GPU 到 CPU Agent 切换

大规模群体由 GPU 粒子负责;需要细致死亡反馈时,Niagara 只导出必要数据,由 Blueprint 在同一位置生成 CPU Agent,并接管 Ragdoll 与方向性冲量。

  1. 死亡事件
  2. 导出粒子数据
  3. Callback
  4. 生成 BP Agent
  5. Ragdoll + 冲量
GPU Agent 切换为 Blueprint Ragdoll
移除 GPU 粒子前,导出位置、伤害类型与受击信息
移除 GPU 粒子前,导出位置、伤害类型与受击信息
Blueprint Callback 接收导出数据,并为每个结果生成 CPU Agent
Blueprint Callback 接收导出数据,并为每个结果生成 CPU Agent
根据伤害类型选择物理反馈,并计算冲量方向
根据伤害类型选择物理反馈,并计算冲量方向

Performance / Profiling study

性能分析

先在一万个粒子下替换模型和材质,再增加粒子数量,最后查看 Niagara 内部各阶段的耗时。

Unreal 编辑器 · 2560 × 1440 · 100% 渲染比例
01

10,000 粒子:主要开销在渲染

BasePass 为 5.88 ms,Velocity 为 4.98 ms,而 Niagara 模拟为 0.30 ms。换成 Cube 后,GPU 耗时降低约 10 ms;只关闭 VAT,变化则小得多。所以我会先处理渲染成本。不过,这组测试还不能把几何、材质和像素覆盖的开销单独分开。

渲染配置对比10,000 Agents · 单位 ms · 越低越好
配置GPU 耗时BasePassVelocity
LOD0 + VAT原始模型 · 7,160 三角形21.13–21.335.884.98
VAT 关闭当前材质开关带来的变化较小20.49–20.675.374.60
Cube 替换诊断实验 · 几何与外观同时变化11.28–11.340.660.17
LOD21,792 三角形 · 存在 VAT 变形14.13–14.342.281.33
Nanite Support探索实验 · 粒子实际渲染路径尚未确认18.79–18.924.723.35

GPU 范围为配对截图读数,Pass 使用截图中的 Busy Avg。这些是编辑器对比结果,非多轮基准测试;不同 GPU 队列的 Pass 耗时不能直接相加。

LOD0 · BasePass 5.88 ms / Velocity 4.98 ms
LOD0 · BasePass 5.88 ms / Velocity 4.98 ms
LOD2 · BasePass 2.28 ms / Velocity 1.33 ms
LOD2 · BasePass 2.28 ms / Velocity 1.33 ms

LOD 的收益潜力与生产限制

LOD2 三角形少了 75%,GPU 耗时约低了 33%,但 VAT 动画出现拉伸。要保证动画正常,可能需要针对低 LOD 模型重新烘焙 VAT,让动画数据与该 LOD 匹配。完成变形和 LOD 切换检查后,再验证同等画质下的性能收益。

查看模型限制
LOD2 模型检查 — 1,792 三角形 / 1,594 顶点,存在明显 VAT 变形
LOD2 模型检查 — 1,792 三角形 / 1,594 顶点,存在明显 VAT 变形
02

粒子增加后,开销怎么变

从 1,000 增加到 20,000 粒子,GPU 耗时从 12.23 升到 30.95 ms。BasePass 从 1.69 升到 10.63 ms,Velocity 从 0.52 升到 9.81 ms;Niagara 模拟则在 0.21–0.57 ms 之间。这组场景里,渲染开销的增长明显快于已测到的模拟开销。

原始 LOD0 · 代表性截图读数 · 单位 ms
AgentsGPUBasePassVelocityNiagara 模拟
1,00012.231.690.520.21
5,00016.293.562.540.25
10,00021.335.884.980.30
15,00025.858.247.420.33
20,00030.9510.639.810.57
03

模拟内部耗时

10,000 粒子 · LOD0 · Niagara 堆栈读数(Avg,ms)
10,000 粒子 · LOD0 · Niagara 堆栈读数(Avg,ms) ↗

QueryGrid 为 0.071 ms,约是 Particle Update(0.036 ms)的两倍,是这次截图中耗时最高的模拟阶段。下一轮优化会围绕网格密度和查询量展开,同时检查耗时变化与虫群行为是否保持正常。

阶段Avg(ms)
QueryGrid0.071
Particle Update0.036
StoreNearestAimingTarget0.011
GetTraceSelection0.011
FillGrid0.007

独立堆栈截图 · 阶段平均耗时,非单个 Module 耗时。CPU 的 System/Emitter 与 GPU 粒子阶段分别计时,这些数值不等于整帧 Niagara 总耗时。

接下来改什么

我会先降低模型开销,做好兼容 VAT 的 LOD,再测试距离剔除和阴影。模拟内部则先看 QueryGrid。现在 LOD2 虽然更快,但要先修好变形,才能算可用的优化结果。

工具

技术栈