[转载/熟肉] 鸣潮的性能与卡顿:引擎架构、硬件误区与长线运营的真实代价

24834 字
124 分钟
[转载/熟肉] 鸣潮的性能与卡顿:引擎架构、硬件误区与长线运营的真实代价
AIGC 通告

本文使用 AI 辅助原文翻译工作,如有翻译不当之处请在评论区/私信至电子邮箱 justpureh2o@outlook.com 指出。

Note

转载信息:

本文转载并翻译自 ToonXD 的个人博客,原文标题为《Wuthering Waves Performance & Stutter: Engine Architecture, Hardware Misconceptions, and the Real Cost of Live-Service》,原文链接:https://pqmlmaoxd.github.io/gamedev/analysis/2026/04/12/wuthering-waves-performance-issues.html,原文发表于 2026 年 4 月 12 日。

本文为中文翻译,仅供学习交流,版权归原作者 ToonXD 所有;文中数据、观点与结论均来自原作者,不代表本站立场。为便于中文阅读,译文在不改变原意的前提下对个别表述做了技术性润色。

译注

鸣潮已确认在 3.6 版本中将脚本编程语言由 TypeScript 切换为 C#,并于 2026 年 8 月 21 日开放了限量测试。由于原文撰写日期较早,该部分信息可能失准。迁移相关官方公告见下图:

前言#

作为上一篇存储膨胀分析的后续(https://pqmlmaoxd.github.io/gamedev/analysis/2026/03/15/wuthering-waves-memory-bloat.html),今天我想深入聊一个更有争议的话题:鸣潮(WuWa)为什么是现在这样的性能表现,以及社区的抱怨到底公不公平。

先说点背景:我在这件事上花了不少时间,因为我确实想知道这种不满是否合理——或者说,是不是大家在把一些远比“实现质量”更深层的问题归咎于开发者。

本文不是在为库洛游戏辩护,也不是一份借口清单。我希望它是一次诚实的尝试:解释为什么性能问题远比“开发者没优化”或者“技术菜”要复杂得多。

⚠️ 警告: 本文比之前那篇存储膨胀分析要技术化、学术化得多。它会涉及 CPU 架构、引擎线程模型、垃圾回收机制以及面向数据编程——如果你觉得读起来吃力,可以直接跳到文末的 TL;DR。


一个人人都吵不出共识的“问题”#

我在 Reddit、Discord 和 YouTube 上反复看到同样的抱怨:

  • “星炬学院在我这台中高端 PC 上卡得厉害——到底怎么回事。”
  • “官方根本不在乎优化,只顾着堆特效……”
  • “看看终末地、原神、大镖客 2——人家都很流畅。鸣潮开发两年了还做不到?”

这些讨论里没有人区分出来的一点是:“这游戏性能很差”其实同时描述了至少两种完全不同的现象——它们的根因不同、发生在地图上的位置不同、修复的时间表也不同。

模式 A——身处城内时的卡顿。 微卡顿、帧生成节奏不稳、帧数始终锁不住。它发生在星炬学院、Septimont,以及任何人口密集区域——无论你是在战斗还是只是在走路。这一模式下 GPU 负载往往并不高,主导限制来自 CPU 侧的协调开销,而不是 GPU 的原始吞吐。

模式 B——离开城市时的硬冻结。 这个更让人难忘:你跨过边界,游戏瞬间掉到 0 FPS,持续 500 毫秒到几秒,然后完全恢复。从开服就玩鸣潮的玩家会记得早期版本里这个问题有多严重。经过多个版本修补,它已经明显改善——v3.1 的流式加载管线优化带来了肉眼可见的差异——但并没有完全消失。

这两种模式看起来相关,是因为它们都发生在密集区域附近。但它们不是一回事:

模式发生时机正在发生什么现状
A——城内卡顿身处密集区域期间活跃逻辑 Tick 数量过多导致 GameThread 过载——NPC AI、脚本、物理、交互Handler引擎层面的天花板,尚未解决
B——离城冻结离开密集区域时资源流送刷新 + V8 GC 压力 + 着色器/材质预热或按需编译显著改善,仍有残留

还有第三种现象,与上面两者并列但彼此独立:

同时存在的现象时机根因
战斗掉帧(移动端)高特效的密集战斗GameThread 粒子系统饱和 → GPU 渲染端低效级联

本文会把这些都讲一遍。更重要的是,它会尝试解释为什么它们是不同的问题——因为这部分背景正是大多数社区分析完全忽略的,而它决定了“优化”这个词在这里到底意味着什么。


1. 引擎的根本约束#

1.1 UE4 里一帧到底是怎么造出来的#

在虚幻引擎 4(UE4)中,每一帧都按顺序流经三个阶段:

[GameThread] ──► [RenderThread] ──► [GPU]

GameThread(游戏线程) 是所有让游戏之所以是“游戏”的事情发生的地方:Actor 更新、物理模拟、AI 决策、动画状态机、粒子系统 Tick 更新、碰撞检测、脚本逻辑——全部,每一帧,按顺序执行。GameThread 是中心的协调点,所有游戏逻辑都必须先经过它,工作才能被下发到下游。

这里值得澄清一点:UE4 确实有一套 TaskGraph 系统,允许把一部分异步工作分派给工作线程组——鸣潮的客户端日志可以证实这一点,运行时会显示 NP(普通优先级)、HP(高优先级)、BP(后台优先级)三个任务线程组处于活动状态。这意味着鸣潮并不是完全单线程模型。但是,TaskGraph 的并行并不能消除 GameThread 这个瓶颈——它只是减少了一部分工作。GameThread 仍然是必须经过的协调与同步点。那些在设计上就非线程安全的游戏 API、AI 状态、脚本逻辑、Actor Tick 协调,依然必须走这里。当这种协调开销饱和时,无论有多少工作线程,下游线程都只能等。

GameThread 完成后,会把一串渲染命令交给 RenderThread(渲染线程)。RenderThread 把这些命令整理成 GPU 能执行的形式。关键就在这里:两个线程在每帧结束时同步。在双方都处理完当前帧之前,谁也不能开始下一帧。

Epic 自己的文档是这么写的:

“在虚幻引擎 4(UE4)中,整个渲染器运行在自己的线程里,比游戏线程落后一到两帧。” “游戏线程把命令插入渲染命令队列……RHI 函数只能在渲染线程中调用。”

来源:https://dev.epicgames.com/documentation/unreal-engine/threaded-rendering-in-unreal-engine

简单说:GameThread 慢,RenderThread 就得等,GPU 就会空转,于是你看到帧时间尖峰。你的 GPU 有多快并不重要——它必须等 GameThread 把活喂过来。

历史背景——为什么现在比以前更明显

UE4 的协调模型已经跨了好几代引擎,这不是新出现的限制。变的是引擎如今所处的环境。

2010 年代初 UE4 面世时,它面向的是完全不同的硬件与内容预期:CPU 普遍只有 2–4 核,机械硬盘仍占主流并制约着流送,目标帧率常是 30–60 FPS 且帧时间预算更宽松,游戏世界的复杂度也相对更低。

此后硬件与游戏设计都在演进,方向都是增加单帧工作量,却没有相应提升引擎并行处理这些工作的能力:CPU 核心数大幅增长,但单线程性能并未同比提升;SSD 消除了大量 I/O 约束,使更激进的流送和更大的世界成为可能;现代作品追求更高的视觉保真度、更密集的环境与更复杂的模拟系统。

结果是:过去还能在舒适裕量内运转的那些架构约束,如今被更频繁地触及——乃至突破。这就是为什么曾经偶发、局部出现的瓶颈,在现代作品里变成了持续存在且高度可见的问题。问题并不只是引擎“老”,而是现代工作负载正在系统性地把引擎的协调模型推到设计极限的边缘。

—— @argon1ut

时间线问题——为什么至今还没修

大多数性能讨论都缺一块背景:这个行业并不运行在同一条时间线上。有三条速度各不相同的时间线:

  • 引擎演进(快)——虚幻引擎持续引入新系统、改进的线程模型、实验性的面向数据框架。
  • 游戏开发周期(中)——大型游戏要 5–6 年才能做完,架构决策早在发售前就已锁定。
  • 生态采纳(慢)——工具、工作流与开发者经验是逐步演进的,往往要跨越多个项目。

这三条时间线并不对齐。一款 2025 年发售的游戏,很可能是按 2019–2020 年的假设设计的——那时许多现代引擎改进尚不存在或尚未达到生产可用。即使新系统已经就绪,也无法在不给在研项目带来巨大风险的前提下回填。

这造成一种结构性滞后:等引擎层面的方案出现时,本该受益的游戏已经推进得太远,无法采用。GameThread 模型、AActor 体系与面向对象设计仍然留在这里,不是因为它们最优,而是因为它们是依赖稳定性与向后兼容的庞大既有生态的地基。

结果是一段过渡期:引擎在进步,硬件在进步——但已发售的游戏仍建立在更旧的假设之上。这个行业正在用上一代的设计,对接它们替代品的第一代。

现代游戏正用今天的内容规模,暴露昨天引擎架构的问题,而明天的解决方案还没有达到生产可用。

—— @argon1ut

可以把它想象成一家餐厅厨房:每一张订单——无论多简单——都必须由主厨亲自过目签字才能出餐。厨房里站着 16 个副厨随时待命。但每一张单子依然在等主厨的签名。副厨再多也没用。主厨快一点有点用。瓶颈是流程,不是人手。

在 UE4 里,GameThread 就是那位主厨。每一帧、每一段游戏逻辑,都要按顺序经过它签字,才能往前推进。

UE4 的诊断命令 stat unit 会把 Game、Draw、GPU 时间分别列出来。当 Frame time ≈ Game time 时,瓶颈就是 GameThread。而鸣潮在密集区域里,稳定地落在这个位置。

1.2 为什么 6 核或 8 核 CPU 也救不了#

这是本文最反直觉的一点,所以我想说清楚:

更多的 CPU 核心对 GameThread 瓶颈没有帮助。

一位社区成员分享了他在密集城区游玩时的 Process Lasso 截图——这个模式一眼就能认出来:

[图片:社区成员的 Process Lasso 截图——i7-11700F,鸣潮运行在城区。Client-Win64-Shipping.exe 显示创建了 319 个线程,但整体 CPU 使用率读数只有 31%,与此同时游戏报告的响应压力是 100%。角落里的 CPU 柱状图呈现严重偏斜的分布——一小簇核心在苦干,绝大多数接近空转。]

来源:https://www.reddit.com/r/WutheringWaves/comments/1p2pwg0/cpu_issue_apparently_the_new_area_is_full/

这台机器有充足的计算能力。但决定你会不会卡的那些工作——NPC 行为树、交互 Handler、AI Tick 协调、脚本逻辑——并不能自由地分摊到所有核心上。其中很大一部分必须先经过 GameThread 这个协调点,才能被分派给工作线程,而这个点上的同步开销正是把某个线程顶到 100% 的原因。线程有 319 个;可关键路径能并行多少,受引擎线程约束限制。

我自己的 CapFrameX 测试印证了 Process Lasso 截图显示的模式:在 i7-12700KF + RTX 5070 上的两次城区会话中,CPU 单线程峰值达到 97–100%,而整体 CPU 负载平均只有 40–46%。聚合数字看起来没问题,但每线程的情况完全是另一回事。

vkguide 的多线程文章描述的正是 UE4 的这个毛病:

“你经常会看到虚幻引擎游戏在超过 4 核之后很难继续扩展……一款大量使用蓝图和 AI 计算的 UE4 游戏,会让 Game Thread 在一个核心上忙得不可开交,而机器上其他核心闲置。”

来源:https://vkguide.dev/docs/extra-chapter/multithreading/

Epic 自己的开发者文档也这么说:

“从设计上,许多引擎 API 与玩法操作并非线程安全,因此必须在 GameThread 上执行……Game Thread——中心执行线程:每帧协调所有游戏逻辑。” “但多线程并不总是银弹。有些任务本质上是串行的,强行拆分可能只带来复杂度而没有性能收益。”

来源:https://dev.epicgames.com/community/learning/tutorials/BdmJ/unreal-engine-multithreading-techniques

我在同一台硬件上采集了两次基准会话——i7-12700KF + RTX 5070,设置完全一致:1080p、画质设置、光追、DLAA、关闭帧生成。两座不同的城市,同一个问题:CPU 是不是天花板?

瑝珑(v1.0 城市):

  • 平均帧率:40.2 · CPU 最大线程:97% · GPU 平均占用:54%

CapFrameX 帧时间——瑝珑 v1.0 城区巡游
CapFrameX 帧时间——瑝珑 v1.0 城区巡游
瑝珑城区巡游期间的帧时间分布。注意持续的尖峰模式——以及约 t=85s 处 245ms 的离群值,那是我以最高速飞出城市边界时故意触发的(模式 B)。

星炬学院(v3.0 城市):

  • 平均帧率:38.5 · CPU 最大线程:100% · GPU 平均占用:58%

CapFrameX 帧时间——星炬学院巡游
CapFrameX 帧时间——星炬学院巡游
星炬学院巡游期间的帧时间分布。175ms 的离群尖峰(自然发生,非人为触发)出现时与 GPU 负载毫无相关性——与 V8 GC 的 stop-the-world 暂停相符。

两座城市。两次会话。答案都一样:CPU 最大线程饱和,GPU 大量闲置。两次会话互相印证——这不是某个区域或某个补丁的偶然产物。RTX 5070 平均只跑到 54–58%,而 CPU 最大线程顶在 97–100%,这是最清晰的信号。GPU 在等。而这只是待在城里——不是战斗,也不是压力测试。

移动端线程统计——直接确认:

社区成员 @xLOCKnLOADx(第 5 节引用的那篇 Reddit 移动端性能帖的作者)提供了移动端运行鸣潮的每线程负载数据。线程分布把答案直接摆出来了:

线程平均峰值
GameThread74.9%93.9%
RenderThread65.1%83.8%
RHIThread52.1%69.5%
TaskGraphHP 313.3%17.4%
TaskGraphHP 48.5%12.5%
TaskGraphHP 56.6%10.3%
V8 DefaultWorker ×23.3% / 3.2%16.1% / 23.1%
其余所有线程各自 <5%—

GameThread 与 RenderThread 是绝对主力。TaskGraph 工作线程在工作,但都停在 13% 及以下。V8 DefaultWorker 的出现,则是 V8/Puerts 与主游戏线程同时活跃的直接运行时证据。

架构对比——鸣潮 vs. 明日方舟:终末地:

在同一台设备上剖析,终末地(AKE)的线程分布呈现出根本不同的形态:

线程平均
UnityMain70.0%
Job.Worker 120.3%
Job.Worker 220.0%
Job.Worker 314.8%
Job.Worker 011.0%

主线程负载相近(约 70%),但终末地的 Job Worker 是真正在干实事的——每个都有 20% 以上。这是 Unity 的 Job System 在有效地把模拟工作摊到多核上。相比之下,鸣潮的 TaskGraph 工作线程停在 7% 及以下。

还有一个很重要的行为差异值得指出:鸣潮的工作线程呈现锯齿状/突发式模式,而不是持续负载。它们短促地被激活、快速完成任务,然后停在那里等主线程派发更多工作或解决同步点。这不是缺乏并行能力——而是协调瓶颈:系统本可以用更多核心,但工作喂过来的方式不支持持续并行执行。这个说法比“更多核心没用”更精确——准确的说法是,额外的核心无法加速那些仍然串行在协调点上的工作。

鸣潮线程统计——GameThread 与 RenderThread 占据主导
鸣潮线程统计——GameThread 与 RenderThread 占据主导
移动端鸣潮的每线程负载。GameThread 与 RenderThread 承担了绝大部分工作。TaskGraph 工作线程活跃但贡献很小。V8 DefaultWorker 实例证实 Puerts/V8 运行时处于活动状态。来源:@xLOCKnLOADx

架构对比——鸣潮 vs. 明日方舟:终末地:

鸣潮线程分布
鸣潮线程分布

鸣潮——GameThread 74.9%,TaskGraph 工作线程 ≤13%。来源:@xLOCKnLOADx

终末地线程分布
终末地线程分布

明日方舟:终末地——Job Worker 各自 20% 以上,工作分摊到多核。来源:@xLOCKnLOADx

@xLOCKnLOADx 把实际含义总结得很直接:“基本上全是主线程,他提到的那些线程跟终末地这种游戏比起来其实干不了多少活。一颗 8 Elite 配上一颗猛的主核都卡成这样,低端硬件会怎么挣扎你可以自己想象。”

@argon1ut 的测试用例——分辨率与设置的受控扫描(Ryzen 9 7900X + RTX 5070 Ti)

在同一套硬件上测了三组配置,用来在分辨率/设置的光谱上分离瓶颈行为。图片:argon1ut_4k_rt_night.png、argon1ut_4k_rt_day.png、argon1ut_768p_low_fg4x.png。

argon1ut 4K 光追 夜间
argon1ut 4K 光追 夜间

配置 A——4K DLAA,光追最高,关闭帧生成,夜间 FPS 35 · 1% Low 31 · GPU 99% · CPU 12% 可见 150.2ms 的延迟尖峰

argon1ut 4K 光追 白天
argon1ut 4K 光追 白天

配置 B——相同设置,白天 FPS 40 · 1% Low 15 · GPU 99% · CPU 17% 可见 127ms 的延迟尖峰

配置 C——1024×768 窗口化,全部最低(把人群密度与视距拉到最高来给 CPU 上压力),DLSS 超级性能,帧生成 4×,关闭光追: FPS 412 · 1% Low 200 · GPU 37% · CPU 17% · 延迟 26.7ms

argon1ut 768p 低画质 帧生成 4×——GPU 被刻意压低负载
argon1ut 768p 低画质 帧生成 4×——GPU 被刻意压低负载

配置FPS1% LowGPU%CPU%瓶颈
A——4K 光追最高,夜间353199%12%GPU
B——4K 光追最高,白天401599%17%GPU + 模式 B 尖峰
C——768p 低画质,帧生成 4×41220037%17%都不是——星炬学院入口区域,压力未起

这组受控数据说明了什么:

配置 A vs B——设置完全一致,只是夜间与白天。夜间帧率更低(35 vs 40),但 1% Low 更好(31 vs 15)。这个 1% Low 差异值得注意,但没有这两次会话的详细帧时间图,成因是模糊的——它可能反映模式 B 的复合尖峰、不同场景复杂度下 GPU 帧时间的波动,或者两者兼有。两次会话中 GPU 都是 99%,说明在 4K 光追下 GPU 是主要瓶颈。

配置 C——录制于星炬学院内部,但位于低密度出生区,尚未进入高 NPC 密度区域。在 768p 且 GPU 被刻意压低负载时,GPU 与 GameThread 同时都有裕量——帧率 412,1% Low 干净地达到 200。这不代表星炬学院的峰值压力表现。

配置 A/B 与配置 C 合起来看——同一颗 CPU,帧预算却天差地别。几组配置的总 CPU 利用率都在 17% 左右,说明在 A/B 中 CPU 并不是限制资源——帧时间主要被 GPU/光追开销占据。CPU 可能仍然在协调路径上,但在那些 4K 光追场景里,它不再是可见的首要限制因素。

来源:@argon1ut(独立技术分析者)

作者本人的 7800X3D 数据 + @argon1ut 的 7900X 对照——L3 缓存、稳态稳定性与离群行为

CapFrameX 会话录制于星炬学院区域,设置与 12700KF 基线会话大体相同。硬件:Ryzen 7 7800X3D(96MB L3 缓存)+ RTX 5070,1080p,最高画质,光追,DLAA,无帧生成。

7800X3D CapFrameX——星炬学院帧时间
7800X3D CapFrameX——星炬学院帧时间

额外的对照数据由 @argon1ut 提供:Ryzen 9 7900X + RTX 5070 Ti,1080p,最高画质,DLAA,无帧生成,不使用 NVIDIA Filters。这不是一次严格的 CPU 隔离测试——GPU、驱动/运行时状态、着色器缓存、路线、流送状态、叠加层/滤镜栈都不同——但作为同一类星炬学院压力场景的第二个高端 Zen 4 参考点,它很有价值。

7900X CapFrameX——星炬学院帧时间,来源:@argon1ut
7900X CapFrameX——星炬学院帧时间,来源:@argon1ut

指标12700KF 基线7800X3D + RTX 50707900X + RTX 5070 Ti(@argon1ut)解读
平均帧率38.567.367.9X3D 与 7900X 都大幅超越 12700KF 基线
1% Low 平均14.819.235.7这次采集里 7900X 的尾部表现更健康
0.1% Low 平均9.95.323.47800X3D 这一轮被异常的灾难性离群值拖得很惨
卡顿累计—2.66s(2%)0.28s(0.2%)@argon1ut 的那一轮避开了 300–470ms 的尖峰簇
最严重的可见尖峰175ms约 450ms约 70ms 级别的可见尖峰尾部行为取决于不止缓存大小一个因素

稳态下的 X3D 信号: 7800X3D 的采集在灾难性尖峰簇出现前后,都有着非常干净的常态帧时间主体。这正是 X3D 看起来最起作用的部分:对缓存敏感的密集巡游、GameThread 对象遍历、指针追逐、Actor/系统协调,以及流送管理开销。在这种稳态负载下,96MB 的 L3 缓存降低了 UE4 对象零散访问的代价,让模式 A 明显更干净。

7900X 的尾部行为: @argon1ut 的 7900X + 5070 Ti 采集尾部指标健康得多:1% 与 0.1% Low 更高,卡顿总时长短,也没有 300–470ms 的灾难性尖峰簇。他还表示整轮体验都很顺滑。这是一个重要补充:现代非 X3D 的 CPU,只要单线程性能强、频率高、核心多、缓存也还算大,依然可以带来流畅且好玩的鸣潮体验。X3D 并不是让游戏在 PC 上体验变好的唯一路径。

为什么不该过度解读那个 450ms 尖峰: 7800X3D 的最坏尖峰并不是一份干净的 CPU 证据。它可疑的地方在于:那一轮开了 NVIDIA Filters,而 @argon1ut 没开。NVIDIA Filters/Freestyle 会挂进渲染/后处理/驱动路径,可能引入罕见的运行时顿帧,却未必降低平均帧率。流送状态、边界行为、着色器/材质预热、V8/GC 压力、驱动/运行时差异,以及叠加层/滤镜行为,都可能贡献最坏的离群值。在移除 NVIDIA Filters 重测 7800X3D 之前,那个 300–470ms 的尖峰簇应当被视为外部变量或复合事件变量,而不是 X3D 的常态表现。

更健康的那一轮里模式 B 依然存在: @argon1ut 的曲线上仍然有可见的 70ms 以上尖峰,而基线帧时间只有约 15–20ms。这些尖峰未必主观上能感知到,但它们说明类似模式 B 的波动即使在一轮健康的高端非 X3D 测试里也不会消失。差别在严重程度:他的采集呈现出可管理的尾部,而开了滤镜的 7800X3D 那一轮呈现出可疑的灾难性放大。

更有力的结论: X3D 很可能是目前能买到的、对鸣潮稳态密集巡游负载——也就是模式 A——最有效的硬件缓解手段,因为鸣潮在星炬学院这类区域表现出很强的缓存敏感性。但最严重的离群事件牵涉到缓存局部性之外的更多系统。X3D 并不会让鸣潮对复合顿帧免疫;它只是让常态城内巡游的主体干净得多。

问:那么鸣潮需要的是更多 CPU 核心,还是更高的频率?

—— 勘误致谢:@eggsee(鸣潮移动端配置作者)提供了客户端日志证据,确认了鸣潮的 TaskGraph 线程组,从而使 1.1 节中的线程模型更准确。原文“只有一条 GameThread 车道”的表述属于过度简化。

密集城区的另一个贡献因素——mip 流送竞争:

社区成员 @eggsee 还指出了星炬学院这类区域卡顿的第二个机制:纹理 mip 流送竞争。即便是被遮挡的几何体——墙后、视野之外——依然会被流送进来,与可见内容争夺 mip 流送带宽。这会堵塞 mip 流送管线,并迫使 CPU 把 GameThread 预算花在管理那些当帧根本不会被看到的几何体的流送请求上。

在移动端,这一点还会与 GameThread 协调开销叠加,同时抬高 GPU 帧时间——这也是移动端追踪里 CPU 与 GPU 利用率有时不符合预期模式的原因。

一个实际推论:在新区域里的卡顿,常常会在待一段时间后自行好转,因为着色器编译完成、流送系统稳定下来了。这与“流送预热效应”相符,而不是纯粹的静态 GameThread 天花板——天花板是真的,但它的严重程度会随流送状态变化。

答案比二者都更微妙。原始频率与 IPC(每时钟指令数)确实重要,但对鸣潮来说,最有价值的 CPU 升级往往是更大的 L3 缓存——原因直接来自 1.3 节描述的架构问题。

简而言之:GameThread 的工作涉及不断访问散落在 RAM 各处的数据。更大的 L3 缓存让更多这类数据留在 CPU 附近,减少每次访问的停顿时间。这就是为什么 AMD 的 X3D 处理器——把额外 L3 缓存堆叠在芯片上——往往能给出明显更顺滑的鸣潮体验,即使它们的纸面平均帧率并不更高。一颗 Ryzen 5700X3D 的平均帧率可能低于 i7-12700KF,但城内区域的 1% Low 与帧一致性大概率会明显更好。

但要说清楚:X3D 是当前最好的硬件缓解手段,不是解药。 它降低卡顿的严重程度,却无法消除卡顿——因为根因是架构性的,不是靠堆硬件就能完全补上的缓存容量问题。GameThread 天花板、V8 GC 暂停、OOP 指针追逐,这些都还在。更大的缓存让它们痛得轻一些,而不是消失。

实用建议:如果你玩鸣潮会卡,而 GPU 利用率低于 80%——最该升级的是大 L3 缓存的 CPU,而不是新显卡。并且据此校准你的期待。

1.3 OOP、缓存未命中,以及引擎为何内耗#

GameThread 瓶颈之下还有一个更深的问题。问题不只是所有事情都跑在一个线程上——而是那个线程在运行时如何访问数据。

UE4 是围绕**面向对象编程(OOP)**模型构建的。这不是什么非正式观察——这是引擎有文档记载的设计哲学。Epic 官方的编程文档描述 UE4 构建在一套类层次结构上:UObject 是所有引擎对象的基类,AActor 是所有可放入游戏世界的对象的基类,组件附着在 Actor 上以定义其行为。

来源:UE4 编程基础 · 虚幻引擎中的 OOP 原则

在实践中,许多与玩法相关的世界实体——NPC、可交互物件、脚本道具、以 Actor 承载的特效,以及其他基于 UObject/AActor 的系统——都是通过 UE4 的对象层次表示的。每个 AActor 都有一个虚表指针,持有一串 UComponent 对象,而这些组件是各自独立在堆上分配的,落在内存分配器在创建时放置它们的任意位置。静态几何、植被、HISM/HLOD 实例以及其他面向渲染端的资源可以走别的路径;这里说的是玩法关键的对象图,不是世界里每一个可见三角形。

简单说:许多玩法对象各自住在 RAM 的角落里。要更新它们,CPU 得去找。

设身处地想一下:GameThread 需要在单帧内为大量活跃 Actor 处理逻辑 Tick——带行为树的 NPC、带交互 Handler 的建筑、物理道具、粒子系统——这在星炬学院或 Septimont City/Ragunna 这样密集的场景里时刻都在发生。UE4 确实有 HISM、HLOD 这类系统来减少渲染负载,但它们并不降低 GameThread 上的逻辑 Tick 负担——行为树仍然要执行,交互逻辑仍然要评估,脚本 Tick 仍然会跑,无论该 Actor 是否以完整细节被渲染。CPU 在 GameThread 上的工作看起来是这样:

Tick ActorA → 跟随指针 → 找到 ComponentList → 跟随指针 → 找到数据
[CPU 从 RAM 取数:缓存未命中]
Tick ActorB → 跟随指针 → 找到 ComponentList → 跟随指针 → 找到数据
[CPU 从 RAM 取数:另一个地址,又是缓存未命中]
Tick ActorC → [又是另一个地址]
Tick ActorD → [又是另一个地址]
...重复 10,000 次

每一次 Actor Tick 都涉及在内存中的指针追逐。当 CPU 找的数据不在 L1 或 L2 缓存里时,它就得停在那里等主内存响应。在现代 CPU 上,L1 命中大约 4 个周期。最坏情况下从主内存取数大约 200 个周期——当然实际中许多访问会命中 L2 或 L3,未必一路打到 RAM,CPU 也用预取缓解一部分。真实惩罚取决于访问模式、数据局部性与缓存命中率。尽管如此,在一个每帧有数千个 Actor 更新的场景里,对缓存不友好的访问模式带来的累积影响是真实且可测量的——CPU 有相当一部分时间是在等待而不是计算,即便并非每一次访问都是最坏情况的未命中。

关于 UE4 分箱分配器的一点说明:UE4 的默认内存分配器(Binned/Binned2)相当成熟——它使用分层分配系统,按大小类对对象分池,并针对单次分配的 L1/L2 缓存利用率做了设计。相比幼稚的分配器,这明显减少了堆碎片并改善了单次分配的缓存行为。但它并不会把不同 Actor 的缓存行连起来——上述指针追逐的遍历模式与分配器策略无关,因为需要被一起处理的 Actor 并不保证在内存中相邻。分配器在微观分配层缓解了问题;OOP 遍历模式是宏观层的问题。—— 澄清致谢:@aizen76(Indie-us Games,UE 专家)

关于 Actor Clustering 的一点说明:UE4 确实有 Actor Clustering 功能,但它的作用很具体,值得澄清。Actor Clustering 帮助在 UE4 的垃圾回收过程中跳过随机访问式指针搜索——减少 GC 引发的缓存未命中开销。它不是 Tick 的缓存局部性系统,也不会改善上述每帧逻辑 Tick 的访问模式。因此它的收益更多与 GC 相关暂停(模式 B 的领域)有关,而不是与每帧逻辑处理瓶颈(模式 A)有关。—— 澄清致谢:@aizen76

一个有用的心智模型:想象一位图书管理员需要查 10,000 本书。在组织良好的图书馆(DOD/ECS)里,同一主题的书放在一起——她抱来一个书架,顺序读完。在 UE4 的模型里,每本书到馆时被随手塞进当时有空位的架子。她为每一本书都要横穿整栋楼。读书的工作量是一样的,走路才是杀死性能的部分。

L3 缓存就像她桌边的一个小房间,用来放最近用过的书。房间更大(L3 更多),横穿大楼的次数就更少。这正是为什么带堆叠缓存的 X3D 处理器对鸣潮这类负载特别有帮助,而其他游戏却看不到同样的收益。

这正是对鸣潮而言大 L3 缓存比原始频率更重要的原因。L3 缓存充当 CPU 核心与主内存之间的缓冲——它越大,就能有越多 Actor 数据待在附近,而不必每次都往 RAM 跑一趟。一颗 96MB L3 的处理器(比如 AMD 的 X3D 系列)能让鸣潮那些散落的 Actor 数据中有更多保持驻留,而 20MB 或 30MB 的处理器做不到,哪怕后者频率更高。实际结果:跑分时平均帧率更低,但城内区域的 1% Low 与帧一致性明显更好——而问题恰恰就住在那儿。

这不是库洛引入的问题。这是 UE4 对象模型运作方式的结构性后果。引擎就是这么设计的,UE4 内部没有任何机制能在不重写实体系统的前提下改变它。2018 年,虚幻引擎社区论坛上一位开发者就直接指出了这个限制:

“对于大规模实体模拟,UE4 很可能不是一个好选择。据我所知,目前没有哪个大型引擎特别适合这类大规模模拟。”

来源:https://www.reddit.com/r/unrealengine/comments/9uzkpk/

最后那半句——没有哪个大型引擎——在当时是准确的。此后,有两家工作室改变了这个等式——不是靠换一个更好的引擎,而是靠投入足够大的资源,改造手里的那个。

为什么《原神》和《明日方舟:终末地》感觉不一样#

原神的比较在这个社区里天天出现,值得认真回应而不是直接否定。

原神跑在 Unity 上。但原神处理实体密度更从容的原因,并不是“Unity 比 UE4 好”——而是米哈游把十年的研发投入压进了一套高度定制的引擎,它名义上叫 Unity,实际运行方式与任何工作室能拿到的原版相去甚远。

米哈游上一款主力作品《崩坏3》就构建在 Unity 上,并在大规模下运行多年。这段经历给了他们两样库洛在启动鸣潮时不具备的东西:一支有深厚 Unity 功底的团队,以及在硬性极限暴露之前用数年时间去理解如何把 Unity 架构推向面向数据模式的余裕。到原神上线时,他们交付的已经不是 Unity——而是米哈游的 Unity,一套经过重度改造的引擎,凝聚着对自定义系统、工具链与 DOD 架构的十年机构级投资。

同理,明日方舟:终末地也不是原版 Unity 游戏——鹰角在网络管线定制上投入巨大。他们赶上了可以直接利用官方 DOTS/ECS 基础设施(Unity 直到 2022 年才将其投入生产)的时机,同时叠加了自己的引擎功力。

关键点在于:功劳属于米哈游与鹰角的工程投入,而不是作为产品的 Unity。 今天一家用原版 Unity 的工作室,不会自动获得原神那样的实体处理能力。经典形态的 Unity 同样是重度 OOP。我们在这些作品里观察到的性能特征,是那些工作室在引擎之上建成的东西的结果——而不是引擎本身。

Unity 的组件化设计在架构上确实比 UE4 那套继承沉重的 AActor 模型更接近 DOD——这是一个更有利的起点。但“更有利的起点”和“更好的引擎”不是一回事。原版 Unity 与《原神》实际运行的引擎之间的距离是巨大的,而这段距离代表的是任何工作室都无法靠“选择 Unity”直接获得的多年工程积累。

OOP 方式(UE4 AActor):
Actor_A.Position → 地址 0x1A3F00 (堆里某处)
Actor_B.Position → 地址 0x7C2104 (完全不同的位置)
Actor_C.Position → 地址 0x4E8830 (又是别处)
→ CPU 缓存:每一次 Actor Tick 都被逐出并重新填充
DOD/ECS 方式(Unity DOTS / 自定义):
PositionArray: [ A.pos | B.pos | C.pos | D.pos | E.pos | ... ]
↑ 一整块连续内存
→ CPU 加载一条缓存行,处理若干实体,再移到下一条
→ 整批处理期间缓存保持温热

注:上文中的内存地址(0x1A3F00 等)仅为示意,不是任何真实进程的实际数值。

来源:https://unity.com/ecs

Unity 的 C# Job System 把这种数据布局在线程安全上的含义讲得很清楚。Unity 自己的文档说:

“为了让多线程代码更容易编写,Unity C# Job System 会检测所有潜在的竞态条件,并保护你免受它们引发的 bug 影响。” “C# Job System 的解决办法是:发给每个 Job 的是它所需数据的一份副本,而不是主线程里那份数据的引用。”

来源:https://docs.unity3d.com/2020.1/Documentation/Manual/JobSystemSafetySystem.html

这就是 Unity 的 Job System 能安全地把工作分派给工作线程的架构原因。UE4 的 AActor 图没有对应物——Actor 之间共享引用,使得对任意游戏逻辑进行安全多线程几乎不可能,除非动架构手术。但在原神这样的游戏里拿到这种能力,需要米哈游在官方支持出现之前就自己建起其中大部分基础设施。

一句诚实的提醒:原神的视觉上限显著低于鸣潮。 这个交换是双向的。对 DOD 友好的架构在内容制作与系统交互上带有实打实的约束。而且米哈游拥有大量前期积累与机构级余裕,而库洛在组建新团队、上手新引擎启动鸣潮时并没有这些。

UE4 里没有 ECS 或 DOD 框架。虚幻引擎 5 在一定程度上改善了线程状况——Mass Entity 提供了有限的大规模实体模拟支持——但根本的 OOP 血统仍然深深嵌在里面。鸣潮早于 UE5,也不构建在 UE5 上。


2. 脚本层及其代价#

2.1 Puerts 是什么,为什么这里会有它#

在 UE4 的 C++ 地基与你实际接触到的玩法逻辑之间,鸣潮还有一层脚本层。这已经不是推断了——游戏自身的运行时日志和二进制文件提供了直接证据。

直接证据——运行时日志:

鸣潮每次启动都会向 Client\Saved\Logs\Client.log 写入诊断输出。以下条目在多个日志备份(Client-backup-*.log)中稳定出现,排除了一次性偶然的可能:

[Client.log line 2564] 启动v8 V8ThreadHelper
[Client.log line 2565] PuertsJsEnv: ... v8 version: header-11.8.172.18, lib-11.8.172.18
[Client.log line 2566] LoadModule: Puerts

游戏在启动时明确初始化了一个 V8 线程辅助器,加载 Puerts 模块,并报告它的 V8 版本。在不挂完整 profiler 的前提下,这已经是最直接的运行时证据了。

直接证据——二进制字符串:

对 Client\Binaries\Win64\Client-Win64-Shipping.exe 做字符串提取,可以得到以下内嵌标识符:

Puerts
PuertsJsEnv
typescript
TypeScriptGeneratedClass
KuroPuerts
puerts/*.js
/Game/Aki/TypeScript/... (多个路径)

其中 KuroPuerts 这个标识符尤其值得注意,它暗示这是库洛对 Puerts 框架的自有分支或定制,而不是原样集成。

数据挖掘的配置证据:

这与 Arikatsu/WutheringWaves_Data 仓库在内容管线结构上展示的情况一致:

BinData/level_entity/ ← 旧 TypeScript 路径(v1.0 起存在)
BinData/prefab/ ← 旧 TypeScript 路径(v1.0 起存在)
BinData/level_entity_csharp/ ← 新 C# 路径(v2.8 首次出现)
BinData/prefab_csharp/ ← 新 C# 路径(v3.0 首次出现)

并且在游戏的主数据库清单 aki_base.csv 中得到确认:

v1.0 至 v2.7:没有任何提及 "csharp" 的条目
v2.8:LevelEntityForCSharpConfig, db_level_entity_csharp.db ← 第一个迁移信号
v3.0:PrefabForCSharpConfig, db_prefab_csharp.db ← 扩展到 prefab 系统
v3.1–v3.2:同样这两条,基础设施保持稳定

关于访问 pak 内容:

库洛每个版本都会轮换 AES 密钥,但社区项目 https://github.com/ClostroOffi/wuwa-aes-archive 持续维护各版本的密钥归档,使得通过 FModel 这类工具访问 pak 内容成为可能。通过这条流程检查解包资源,可以在 Client/Content/Aki/ 下看到 ScriptAssemblies 目录,其中包含 C# 运行时程序集,如 CSharpScript.dll、Microsoft.CSharp.dll、mscorlib.dll 以及大量 System.* 程序集——证实 C# 运行时环境已经与 TypeScript/Puerts 层并存部署。

目录在。数据库条目在。日志条目证实它在运行时被活跃使用。关于 Puerts/V8 的论断已不再依赖间接证据——它被直接确认了。

问:既然影响性能,库洛为什么还要选 TypeScript 脚本层?

因为在鸣潮开发启动的那个时间点,首要目标是开发速度,而脚本 VM 层正好提供这个。

库洛上一个主力项目是《战双帕弥什》——构建在 Unity 上,不是 UE4。为鸣潮转向虚幻引擎,意味着团队本身就处在一个不熟悉的引擎里摸索。像 Puerts 这样的脚本层解决了一个非常实际的问题:策划与玩法工程师可以用 TypeScript 编写和迭代逻辑,不必碰 C++,也不必重建引擎。这一点比听起来更重要——原生 C++ 开发意味着每一次调试迭代或新功能实现都要重编译整棵源码树,在大型项目上极其耗时。热重载可用,迭代周期更短。代价是大规模下的运行时性能——但游戏还小的开发早期,这个代价是看不见的。

这是经典的“长线服务启动期权衡”:先买开发速度,后面再付性能代价。工程团队几乎肯定清楚风险——一个会在规模上累积 GC 压力的脚本 VM 并不是什么冷门边缘情况。但当速度不可妥协,而替代方案是在不熟悉的引擎上更慢地迭代时,这个权衡就变成被迫的选择,而不是错误。这笔债是明知故借的。迁移就是还款。

2.2 V8 的 GC 问题——以及模式 B#

V8 有两套垃圾回收器协同工作。Minor GC(Scavenger) 处理年轻代里的短命对象——便宜、频繁、基本无感。问题在 Major GC(Mark-Compact),当长寿对象积累到填满老年代堆时才触发。

V8 官方工程博客这样描述默认做法:

“一种直截了当的做法是暂停 JavaScript 执行,在主线程上依次完成这些任务。这会在主线程上造成卡顿与延迟问题,并降低程序吞吐量。”

来源:https://v8.dev/blog/trash-talk

同一篇博客也点出了让 major GC 变得昂贵的特定场景——正是鸣潮从密集区域切换到开放世界时制造的场景:

“复制存活对象的垃圾回收器有一个潜在弱点:当我们分配了大量长寿对象时,复制这些对象的成本会非常高。”

来源:https://v8.dev/blog/trash-talk

在星炬学院或 Septimont,JavaScript 对象在不断被创建:NPC 行为树、任务触发状态、建筑交互 Handler、UI 更新回调。许多对象存活时间足够长,被晋升到老年代堆。当玩家离开该区域,这些对象变成不可达——但 V8 要等到 major GC 运行时才知道。

把 V8 的垃圾回收器想象成一支“随叫随到”的清洁队——但他们一到,就会把整栋楼锁起来,直到干完为止。你在星炬学院里时,杂物四处堆积(每个 NPC tick、每次任务状态更新、每次交互都创建一个对象)。你走出去时,清洁队觉得该打扫了。一切冻结,直到他们完工,然后恢复正常。

v3.1 的流送更新改进了你离开时楼里清空的速度。它没有改变清洁队的工作方式。TS→C# 迁移才是真正把清洁队换成一支增量式工作、无需封锁的团队。

这就是模式 B 背后的机制——离开城市时,或一次性卸载大量几何体时的硬冻结。触发条件并不局限于城市边界;任何“大量 pak 卸载”与“累积 GC 压力”同时发生的切换,都可能产生同样的事件。密集城区的出口只是最稳定、最可预测的触发点。它是一个复合事件:

玩家跨过城市边界
│
├─► UE4 资源流送:卸载城市 pak 数据
│ 内存释放、I/O 刷新
│ → 流送尖峰
│
└─► V8 major GC:整个城内停留期间
堆一直在被填满
→ stop-the-world 暂停在切换瞬间触发
两者同时发生 → 可见顿帧;在严重或较早版本的情况下,玩家会感知为 500ms–2s 的 0 FPS 冻结
边界切换期间还有一个额外贡献因素——**按需着色器编译**。在开放世界游戏中,着色器常常在首次遇到新材质或特效时被编译——而边界切换往往会引入需要新着色器变体的新资源类型。当着色器编译与流送刷新、GC 压力撞在一起时,会进一步放大这个复合事件。严重程度高度依赖硬件:单线程 CPU 性能更强、缓存更大的系统能更快完成着色器工作,从而降低可见影响。

现代 V8(“Orinoco”回收器)通过并发回收改善了这一点,但也承认了极限:

“这里的优势是主线程完全可以自由执行 JavaScript——尽管会因与辅助线程的一些同步而带来少量额外开销。”

来源:https://v8.dev/blog/trash-talk

即使有 Orinoco,major GC 周期也无法被完全卸载。在一个 GameThread 已经背着城市负载的游戏引擎里,流送刷新与 GC 周期的复合时点,正是长期玩家记忆里那种严重 0 FPS 冻结的来源。

为什么模式 B 改善了但没消失: 库洛 v3.1 的流送管线更新(“通过更新加载管线并加速数据流送,优化了游戏的资源加载性能”)解决了这个复合事件中资源流送的那一部分。GC 那一部分仍然在——因为那需要把脚本层从 V8 上迁走,而 TS→C# 迁移针对的正是这个。这种“修好了两个并发成因中的一个”所带来部分改善,完全在预期之内。

这就是那种“没有对应的 GPU 负载变化、没有温度变化、屏幕上没有任何能解释它的东西”的尖峰。

我有两次基准采集,合起来把这件事讲得很清楚。

星炬学院会话——一次包含骑摩托从学院驶向开放世界的巡游。最坏单帧:175ms,而 P99.9 只有 77ms。这个离群值远远落在正态分布的尾部之外。在平均 58% 的 GPU 利用率下,GPU 不可能是 175ms 停顿的来源。这个画像与区域切换时触发的 V8 major GC stop-the-world 周期相符。

瑝珑会话——同样的硬件,同样的设置。这次我故意用滑翔翼以最高速度飞出城市边界。最坏单帧:245ms。这不是自发卡顿——这是对模式 B 的一次受控触发。越过边界越快越突然,复合事件就越严重:流送刷新与 GC 同时发生,两者之间留出的错峰时间更少。

这两个数字放在一起很有用。175ms 的自发情形(星炬学院)与 245ms 的故意快速离城(瑝珑),分别展示了模式 B 严重程度的下限与上限。常态巡游对应 175ms 那一档。激进的边界穿越对应 245ms 那一档。在 v3.1 流送管线改进之前的早期版本,很可能产生更高的数值,因为当时流送刷新的占比更大。

对这两个尖峰的说明:没有脚本 VM 的 profiler 访问权限,我无法确认它们是 V8 GC 事件。其停顿画像、量级与缺乏 GPU 相关性的特征,都与 V8 major GC 行为一致。我把它定位为推断,而非确认。

2.3 这次迁移究竟修好了什么#

TS→C# 迁移不是一次性能补丁,而是一次架构地基的更换。

现代托管运行时可以使用增量式、并发式 GC,在这种负载下相比 V8 的 major GC 能显著降低暂停的严重程度——取决于运行时配置,最坏情况下的暂停可以从数百毫秒降到个位数毫秒。C# 在某些配置下同样有 stop-the-world 阶段,但无论库洛最终采用哪种具体实现,从 V8 的 mark-compact 行为迁往可调的托管运行时,都代表着在模式 B 上的实质性进展。

迁移没有解决的:GameThread 的单线程天花板。UE4 的线程架构仍然会把大量玩法关键的协调路径串行在 GameThread 上。即便迁移彻底完成,鸣潮在密集场景里仍然会撞上同一堵 UE4 扩展性墙。

迁移确实解决了的:去掉脚本 VM 对最坏帧时间尖峰的贡献。那些 175ms 的离群事件——可以显著减轻,如果脚本 VM 的贡献是主要成分,某些情况下甚至可能完全消失。

TypeScript(V8)C#(托管运行时)
GC 模型stop-the-world 的 major 周期增量式、并发式
最坏 GC 暂停100–500ms通常显著更低;视运行时配置与负载,有时可到个位数毫秒
每个版本的债务增长累积(每个新系统都增加堆压力)大幅减少
与 UE4 的互操作经过 V8 桥接更直接

迁移还会产生复利。之后每一个建立在 C# 路径上的新内容系统,其 GC 压力都比建在 V8 上更低。投资回报随每个补丁增长。这就是为什么在一轮活跃的长线运营周期中,这项迁移值得做——即便玩家永远不会在补丁说明里看到它。

数据挖掘给出的项目规模数据,说明了他们面对的是什么:

版本文件总数BinData 目录数
v1.01,264228
v2.01,442266
v2.71,903337
v3.22,292402

在 C# 迁移的基础设施甚至还没搭起脚手架之前,内容数据库就已经比开服时增长了 81%。那段时间里新增的每一个实体、任务触发器和 prefab,都走的是 V8 路径。这次迁移是在追赶将近三年累积下来的脚本债。

关于脚本层是放大器而非根因

虚幻引擎一直支持高层脚本系统——从早期版本的 UnrealScript 到 UE4 的蓝图。这些系统运行在同一套基本执行模型内,玩法逻辑最终都要通过 GameThread 协调。

鸣潮里 Puerts 与 V8 的存在,说明它是一个额外的脚本层,让基于 JavaScript 的玩法逻辑与原生系统并存。这会带来开销:相比原生 C++ 更高的执行成本、跨语言交互、包含 GC 的内存管理,以及额外的同步点。

然而,区分“原因”与“放大”至关重要。底层的协调瓶颈独立于脚本层而存在。脚本系统——无论是蓝图、UnrealScript 还是 V8——都在同样的架构约束内运作。它们的影响是增加了必须经过协调点的工作量,从而使瓶颈更早暴露、更频繁被突破。

脚本层并不创造瓶颈——它只决定瓶颈被压得多狠。用一种脚本系统替换另一种,不会移除约束,只会改变在约束内运作的成本。

这就是为什么 TS→C# 迁移降低了模式 B 的严重程度,却无法解决模式 A。瓶颈在架构层面。脚本系统只改变它被压满的速率。

—— @argon1ut

为什么不干脆重构线程模型?

理论上,可以通过对 UE4 的线程模型进行大规模改造来解决这些限制。实践中,这样做的工作量已经接近“造一个新引擎”。

UE4 的协调模型深度集成在核心系统里——玩法执行、渲染、脚本、引擎 API 全都围绕它构建。改变这一结构需要对引擎地基做大规模改动,带来可观的复杂度、成本与风险。

到了那一步,问题就从“这能不能改”变成“改现有引擎和造新引擎,哪个更现实”。自研新引擎给出更大的架构自由度,但伴随巨大的前期工程成本、漫长的开发周期,以及一个战略前提:新引擎必须能在未来多个项目上广泛复用,才配得上这份投入。工作室必须评估未来作品是否会持续从这笔投资中受益。结果就是,大多数工作室选择在既有引擎的约束内工作,逐步改进性能,而不是做一次彻底的架构重写。

—— @argon1ut


3. 参照系:其他游戏告诉了我们什么#

3.1 《霍格沃茨之遗》:同一种病,不同的病人#

如果你想搞清鸣潮的卡顿到底是库洛的问题还是 UE4 的问题,《霍格沃茨之遗》是目前最干净的样本。

它运行在 UE 4.27 上,由 Avalanche Software 制作,背后是华纳兄弟的预算。而它发售时带着近年来 3A 作品中最被详细记录的 PC 性能问题之一——模式与鸣潮一模一样:

  • 恰恰在密集区域(霍格莫德、城堡里人口密集的段落)受 CPU 限制,而开阔区域跑得很好
  • 相对于玩家实际感受到的卡顿,GPU 处于低利用率状态
  • 单核 CPU 频率重要,增加核心数没用

一位使用 Ryzen 9 5900X 与 RTX 4090——当时最强的游戏配置之一——的玩家,在 Steam 讨论帖里直接记录了这个现象:

“5900x 配 4090 在此。我玩 4K。如果不开帧生成,我能看到 GPU 利用率 90–99%、CPU 利用率 30–40%,但帧率很糟(50 到 70 帧)。”

来源:https://steamcommunity.com/app/990080/discussions/0/3824161508141330042/

高 GPU 负载、中等的整体 CPU 负载、糟糕的帧率。与鸣潮的画像同形。另一条帖子里,有人已经摸到了真正的成因:

“它受 CPU 限制,只是因为引擎无法扩展到超过一两个核心,所以核多的 CPU 也帮不上忙,它要的是且仅是单核性能(IPC)。如果引擎能扩展到 8 核,我们就不会看到 CPU 限制。”

来源:https://steamcommunity.com/app/990080/discussions/0/3789254716328881575/

第二段引文来自一位玩家——不是开发者——他靠观察得出了正确的技术诊断。它对 UE4 GameThread 架构的描述,比大多数技术文章都准确。

这不是巧合。同一个引擎、同一套线程模型、同一个单线程天花板——而且是在一个资源远多于鸣潮上线时的项目上。这个教训不是“库洛没能解决别人解决过的难题”。目前还没有任何一款明确可比的 UE4 开放世界长线运营作品,公开展示过对这种模式的彻底摆脱。

问:既然有这些限制,库洛为什么还要选 UE4?

因为他们想做一款下一代抽卡游戏,而在 2019–2020 年,UE4 是这份野心最现实的地基。UE5 还不存在。Unity 的 DOTS/ECS 在那个规模上尚未稳定到可生产。从零自研引擎——像 CD Projekt 做 REDEngine、Rockstar 做 RAGE 那样——需要多年专门的研发,而这是当时拿不到的。

UE4 提供了成熟的工具链、庞大的人才池,以及足够被深度压榨的灵活性。而且要承认,库洛交付的 UE4 并不是原版——它在 UE4.26 内研究并部分重建了 Lumen 硬件光追方案的思路,做了一套自定义的、受 Lumen 启发的全局光照系统。这套东西由库洛渲染组负责人王鑫在 Unreal Fest Tokyo 2025(由 Epic Games Japan 主办)上正式分享。演讲说明库洛分析了 Lumen 的硬件光追实现——Screen Probe Gather、Radiance Cache、Reflections——并在 UE4.26 中重建了其中一部分。关键在于,核心组件无法直接移植:“UE5 版本的 Lumen 使用 Surface Cache 来保存物体表面的辐射亮度,但这在 UE4.26 中无法直接使用,所以鸣潮省略了这部分移植工作。” 于是库洛用自研的 Clipmap Irradiance Cache 作为近似的替代,并在全流程中穿插了基于探针、SH 压缩与缓存近似的技术。最终得到的是一套受 Lumen 启发、逼近相似光照目标的系统——而不是跑在 UE4 里的 UE5 Lumen。(来源:4Gamer.net)这种研究加重建的工作不会偶然发生。交付鸣潮的团队对虚幻引擎的理解足够深,能通过研究并部分重建 UE5 的做法,自建一套全局光照系统。

同一场演讲也直接确认了迁移 UE5 的壁垒,用库洛自己的话说:“由于鸣潮是一部已经上线、长期运营的作品,稳定性是第一位的。项目从立项之初就有海量资源是按 UE4 制作的,此时迁移到 UE5 已相当困难。” 这就是本文分析所描述的架构锁定——不是猜测,而是库洛自己的工程团队在解释为什么迁移没发生。

更难预见的是,这些特定限制在鸣潮的规模下会暴露得多严重。2020 年立项时,同等野心的 UE4 开放世界作品还很稀少——《幻塔》2021 年才上线,《霍格沃茨之遗》是 2023 年。在长线运营的开放世界语境下、以这样的视觉保真度运行这种引擎模式,代价的完整图景当时还看不清。之前没人走过这条完全相同的路。事后看技术债一清二楚,当时远没有那么明显。

往前看,同一场演讲也提到了库洛的研究方向:“用类似 Nanite 的 Mega Geometry 技术处理复杂几何场景。” 这不是 Nanite 的移植——他们明确在朝那个方向研究方案。结合已经上线的、受 Lumen 启发的自研 GI,可以看出一个稳定的模式:库洛先识别出 UE5 中针对特定瓶颈的能力,然后在自己的 UE4.26 版本里重建或选择性改造等价技术,而不是把整个引擎迁移过去。

3.2 《战争机器 5》与《堡垒之夜》:UE4 的两个例外(以及为什么它们不适用)#

The Coalition 做的《战争机器 5》常被举为 UE4 最佳实现。它在主机上稳定 60 帧,向 PC 移植也干净,帧时间平滑。原因非常直接:它不是开放世界。 线性关卡设计允许激进的预烘焙剔除。没有流送复杂度,没有不可预测的 Actor 密度,也没有长线运营的内容堆积。The Coalition 是围绕 UE4 的约束,为一个天然贴合这些约束的游戏类型做设计。

《堡垒之夜》值得在这里作为另一个常被提及的 UE4 例外——而且它确实是例外,因为它是 Epic Games 自己做出来的,而 Epic 就是虚幻引擎的开发者。能直接接触引擎团队、能在任何时间线对引擎做任意深度的修改、拥有任何外部工作室都无法匹敌的机构级知识,产出的是另一个量级的结果。另外,《堡垒之夜》也不是鸣潮意义上的开放世界——它的密集城市场景与对局结构,在架构上不同于一个持久开放世界、成千上万个 Actor 同时 Tick 的形态。但即便承认这些差异,也值得指出:市面上最顺滑的 UE4 体验,来自引擎自己的创造者。

鸣潮两者都不是。游戏类型不允许它走《战争机器 5》的路,库洛也没有 Epic 那样的内部权限。

3.3 《赛博朋克 2077》与《大镖客 2》:把苹果拿去比自研引擎#

另一种常见的社区比较是这样的:“《赛博朋克 2077》《荒野大镖客:救赎 2》画面惊艳、跑得又稳,为什么鸣潮就卡?”这一点值得直接回应,因为这种比较从根本上误解了它在比什么。

REDEngine 4(赛博朋克)与 RAGE(大镖客 2)没有同样的 GameThread 问题,因为它们不是按 UE4 的设计哲学造的——甚至根本不是 UE4。

CD Projekt 是为开放世界流送专门重新设计 REDEngine 的。它的 job 系统是原生架构的一部分,不是外挂上去的。绘制调用提交分散到多个线程。实体组件系统是从零为这个用例构建的。

RAGE 有二十多年专门针对密集开放世界环境的迭代。Rockstar 的流送系统会在镜头抵达之前预判几何体。它的内存布局早在“面向数据设计”这个词成为主流之前,就是按缓存一致性设计的。

拿鸣潮的帧时间去比这些游戏,不是一次公平的性能比较。它是“一个被用在苛刻用例上的通用引擎”与“多年来专门为该用例打造的自研基础设施”之间的比较。不同的工具、不同的约束、不同的历史。


4. 库洛到底在做什么#

4.1 补丁里的性能改进#

每一条提到性能优化的补丁说明,都是对技术债余额的一次还款。把 v1.2 到 v3.2 的官方补丁说明放在一起看,一个模式会浮现出来——不只是“库洛在搞性能”,而是在做什么样的工作,以及它如何随时间演进。

早期补丁集中在表层稳定性:

v1.2——“针对部分移动端设备优化了游戏性能。” 定向、针对特定机型。没有架构改动。

v1.4——优化 PC 端着色器编译;把它挪到标题界面,以减少游戏过程中的画面异常。为安卓加入自动帧率以防止过热。这个补丁值得注意,因为它处理的是 UE4 最臭名昭著的痛点之一——游戏内着色器编译——并把它推出了主要玩法循环。

中期(v2.x)显示出范围扩张:

v2.0——“优化了 NPC 的 CPU 占用,使屏幕上可以同时出现更多 NPC。” 这是直接的 GameThread 优化。库洛明确承认 NPC 密度是 CPU 成本而不是 GPU 成本——并且在着手处理。

v2.2——“优化了着色器编译流程的效率以及 PC 端的硬件资源占用。” 对着色器问题的第二轮。

v2.8——数据集里最实质的性能补丁:

  • “将降低光线追踪造成的 GPU 负载,并提升帧率”
  • “在显示各类场景时,将降低内存与显存占用”

一个补丁里两类不同的改进——渲染开销与内存压力同时下降。这不是表层修补。

这次光追改进背后的机制后来在 Unreal Fest Tokyo 2025 上揭晓:库洛的渲染团队修掉了 RHI 线程上的 CPU 侧光追瓶颈——Gather Instances(3.2ms)、Build Acceleration Structure(2.8ms)、Bind SBT(2.4ms)。他们把静态与动态工作拆开,把 Gather Instances 放到独立线程,并把 Build Acceleration Structure 与 Bind SBT 中静态的部分提前到帧内更早执行。结果:开启光追后的帧时间从 19.8ms 降到 10.5ms。这是 RHI 线程的修复,不是 GameThread 的修复——正如 @argon1ut 指出的。这也解释了为什么那个补丁周期里光追开销改善了,而模式 A 的城内卡顿没有变化。

v3.1——“通过更新加载流程并加速数据流送,优化了游戏的资源加载性能,以帮助减少部分配置较低的 PC/移动端设备上的加载卡顿。” 直接针对加载卡顿——正是资源流送问题产生的症状类别。

v3.3(2026 年 4 月 30 日)——引入了自研的移动端帧生成,面向中端机型。这件事在两个方向上都有意义:它表明团队对移动端性能基础设施的持续投入;同时也是一种含蓄的承认——本补丁周期并没有在架构层面解决移动端的 GameThread 天花板,帧生成被用来补偿帧率限制,而不是从源头消除限制。PC 端同时加入了 DLAA 与各向异性过滤选项。移动端的“资源清理”现在允许移除地图环境资源,基础资源包体积也有所缩减——这是上一篇存储膨胀分析里讨论的存储占用问题上的渐进进展。

这条轨迹有意义:从 1.x 的机型定向微调,到 2.0 的 CPU/NPC 优化,到 2.8 的光追开销与内存削减,再到 3.1 的流送管线改进。每一次还款都是渐进的,没有一个能解决架构天花板。但它在累积。

两点值得点出来:

第一,v3.1 的流送管线更新——“更新加载流程并加速数据流送”——正是明显减轻模式 B(离开城市时的硬 0 FPS 冻结)的那个补丁。从 2024 年一路玩过来的玩家会有直观感受。这个改善是真实的,在日常游玩中可测量。剩下的,是这个复合事件里 GC 的那一部分,流送修复管不到。

第二,v2.8 同时在两个方向上都是里程碑:它既是数据集里最实质的性能优化补丁,也是 LevelEntityForCSharpConfig 首次出现在 aki_base.csv 里的版本。表层的优化与架构层面的迁移,在同一个补丁里开始。它们不是先后关系——而是两条并行轨道。

4.2 把这次迁移放回上下文#

TS→C# 迁移值得与其他“理论上也能做”的优化工作做一次专门对比。

比如 CPU 侧剔除改进、VFX LOD 制作、着色器精度调优。每一项都是对真实问题的真修复,都不需要换引擎,然而它们都没有被完整交付。

原因不是库洛不知道它们。原因是“技术上可修”和“在长线运营的发版节奏里可行”是两件不同的事。

工程团队在跑步机上奔跑。想深度优化,就得停下来修跑步机——但跑步机一停,你就摔下去了。TS→C# 迁移相当于在跑步机照常运转、玩家毫无察觉的情况下,一件一件换掉它的核心部件。

举例来说,为鸣潮的角色技能套件补做 VFX LOD,不是一件工程任务,而是一件内容生产任务:把每一个既有技能特效重新打开——50 多个角色、每个 5–6 个技能——制作 LOD 变体,对每一个都已上线的内容做视觉回归 QA,培训美术使用新工作流,并在之后每一次发版中强制执行。这些工作直接与未来六周内必须上线、以维持游戏存续的角色、活动、区域争夺同一份资源。

TS→C# 迁移之所以被排在这些修复之前,因为它是唯一一项具有基础设施复利的改动。未来每一个走 C# 路径的新内容,其 GC 风险都低于走 V8。回报是指数级的,不是线性的。着色器精度调优是一次性回报;迁移的价值则随每个补丁增长。

库洛是在维持六周内容节奏之外做这次迁移。它做对了,玩家不会察觉。这就是基础设施工作的本质。

4.3 MagicDawn:更长远的布局#

MagicDawn 是腾讯游戏的内部渲染研究团队。他们 2025–2026 年公开的工作包括:

  • Neural Dynamic GI(CVPR 2026):面向时序光照贴图集的神经压缩,使动态全局光照的存储与计算开销大幅下降
  • Gaussian Probe Compression(SIGGRAPH 2025):光照探针压缩比最高达 1:50,并支持 GPU 实时解压
  • Lightmap Compression(Eurographics 2026):UV 空间光照贴图存储减少 83%,同时 PSNR 有所提升

来源:https://magicdawnlab.github.io/

更重要的是,MagicDawn 自己的宣传视频明确表示:“MagicDawn 已经在为包括鸣潮在内的众多大作提供动力”——这确认了它与库洛之间存在活跃合作,而不只是理论研究。

生产集成已通过 Engine.ini 确认: 鸣潮发行版里的 Engine.ini 包含以下插件路径:

Paths=../../../Engine/Plugins/MagicDawn/Content

这是 MagicDawn 作为集成插件存在于鸣潮生产引擎构建中的直接证据——不是论文,不是宣传口号,而是游戏自身配置里注册的内容路径。这把 MagicDawn 的说法从“已确认合作”升级为已确认的生产引擎集成。

同一份 Engine.ini 还揭示了几个其他值得注意的库洛自定义插件:KuroDynamicMeshBatch(自定义网格批处理)、KuroWorldPartition(自定义 World Partition)、KuroPSOTools(PSO/着色器缓存)、KuroPerfCat(内部性能剖析)、FastGeoStreaming(实验性几何流送),以及 OpacityMicroMap(NVIDIA OMM——在 Unreal Fest Tokyo 2025 的演讲中被提及)。这份插件列表,与一个大量定制、并选择性使用自研与第三方渲染技术的 UE4.26 构建相符。

MagicDawn 具体哪些特性在生产中启用、覆盖范围多大、对渲染质量或性能贡献多少,库洛的官方补丁说明里都没有详述。集成已经确认;程度仍未公开。

可以确认的是:腾讯在与鸣潮视觉野心相关的研究层面持续投资渲染基础设施,而 MagicDawn 与库洛之间的关系是活跃的,而非猜测。


5. 对 Reddit 那篇性能分析的回应#

社区成员 @xLOCKnLOADx 在鸣潮社区发布了一份详细的性能分析,基于一台骁龙 8 Elite 设备的硬件追踪(https://www.reddit.com/r/WutheringWaves/comments/1rtuzc8/wuthering_waves_performance_issues_analysis/)。我要先把话说明白:那篇帖子里的数据是真实的,值得认真对待。 测出的数字——开放世界 50% 的图元剔除、战斗中最高 99%、L1 缓存未命中率 70–100%、FP32 着色器占主导——都来自真实的硬件追踪,不应被轻易否定。

一个重要的平台前提:本节讨论的是移动端剖析数据。移动端与 PC 有根本差异——移动端使用基于瓦片的渲染、统一内存以及激进的温控/功耗管理,PC 使用独立 GPU、更高带宽的内存与不同渲染管线。图元剔除率、缓存行为、GPU 利用率这类指标不能直接在平台之间平移。这里的因果论证特指移动端语境;PC 的表现应当用 PC 原生的剖析数据单独评估。PC 的协调瓶颈论证,由 1.2 节中另一组 PC 原生数据支持。

问题出在框架上,不在数据上。

那篇帖子把每一个 GPU 侧问题都当成一次独立的优化失败:剔除应该更好、纹理批处理应该更好、着色器该用更低精度。这些单独看都是准确的观察。它的结论——这些都不需要换引擎——也是准确的。

那篇帖子没有解释的是:为什么剔除偏偏在战斗负载下失效。

战斗中 99% 的平凡剔除(trivial rejection)率,不是剔除代码写得差。而是剔除代码根本没时间跑——因为当游戏走到 GPU 提交这一步时,GameThread 早已把整帧预算消耗在粒子系统更新、脚本 Tick 和 Actor 物理上。没给剔除留下时间。

在 GameThread 仍处于饱和状态时去修剔除算法,解决不了问题。它只修了一个症状。级联关系是这样的:

根因:GameThread 饱和 ← 引擎架构
│
▼
症状 1:CPU 侧剔除在 GPU 提交截止前就用光了时间
│
▼
症状 2:未经剔除的几何体被提交 → GPU 做 trivial rejection
(开放世界 50%,战斗最高 99%)
│
▼
症状 3:绘制调用在互不相关的纹理之间跳转 → L1/L2 缓存被打穿
(L1 未命中 70–100%,L2 未命中约 45%)
│
▼
症状 4:GPU 停在等内存,然后下一帧再等一次

把症状 2–4 当作独立失败,就漏掉了它们共享同一个根。GPU 在浪费工作,不是因为有人忘了写剔除代码;而是因为本可以阻止它的代码没被调度到。

关于因果归属的说明:上面的级联是对观测数据在架构上最自洽的解释,但在没有代码库完整 profiler 权限的情况下无法验证。剔除失效至少存在三种合理假设:(1) GameThread 没给剔除留下时间预算——引擎架构解释;(2) 剔除算法本身实现得很糟——实现质量问题;(3) 剔除管线在结构上就不适配这种负载——两者的混合。诚实的态度是:引擎约束与实现质量是两个独立变量,从外部无法分离它们各自的贡献。两者都存在,相对权重未知。本分析把假设 (1) 当作主要解释,因为它与更广泛的数据模式最一致,但读者应当据此权衡。

还有一个值得记下的数据点:社区引擎配置证实 r.ParallelFrustumCull=1 与 r.ParallelOcclusionCull=1 在鸣潮发行版里是可用的——这说明剔除工作可以部分地从 GameThread 卸载到并行线程。这是否能实质性地改变密集城区的瓶颈画像,需要剖析才能确认,但这些开关的存在说明剔除管线并非完全串行在 GameThread 上。

—— @HtooMyatLin3 指出

Android 的调度维度#

那篇移动端性能帖还漏掉了一个平台特有的放大因素,它对理解移动端体验尤其重要。

在 Android 上,能效感知调度(EAS) 系统负责决定每个线程跑在哪个物理核心上,它会根据最近的负载历史在大核(高性能)与小核(能效)集群之间动态迁移线程。

问题在于:EAS 用历史加权平均来决定一个线程该放在哪。如果 GameThread 在最近几帧里很轻——比如处在一段加载过渡里——EAS 可能判定它是轻负载,把它分配到能效核上。然后场景变密集了,GameThread 负载飙升。但操作系统调度器并不会瞬间反应:它需要时间识别新的负载水平,再把线程迁到主核。

在这个迁移窗口里,GameThread 在一个并非为它设计的核心上干重活。剔除预算进一步崩塌,级联更糟。

Android AOSP 文档直接承认了这个问题:

“如果不对调度器做修改、使前台应用更可能被移到大核集群,那么在前台应用获得调度器将其负载均衡到大核 CPU 之前,它可能没有足够的 CPU 容量来渲染。”

来源:https://source.android.com/docs/core/tests/debug/jank_capacity

这个维度在 PC 上不存在——x86 桌面 CPU 是对称的。移动端卡顿在同一个 GameThread 问题之上,还多压了一层放大。症状看起来相似,机制不一样。

关于 FP32:跨平台的约束#

那篇性能帖建议把着色器迁到 FP16,作为移动端 Adreno GPU 的一个快速优化——在那些 GPU 上,FP16 吞吐确实快 2 倍。

对 Adreno 而言这没错。但它忽略了鸣潮同时运行在 Adreno、Mali、Apple GPU、桌面 AMD、桌面 Nvidia 以及 Intel 核显上。FP16 的表现与收益在这些 GPU 家族之间差异极大。为每个 GPU 家族维护独立的着色器变体,意味着更大的 pak 文件(社区对鸣潮的存储占用另有一场持续的讨论),以及随设备类别成倍增加的 QA 负担。

这个权衡是真实的,并不像那篇帖子暗示的那么简单。社区成员 @HtooMyatLin3 补充了一个重要的硬件层面细节:在 GTX 系列硬件(例如 GTX 1060)上,FP16 吞吐相对 FP32 是 1:64——也就是说 FP16 在该 GPU 家族上会慢到灾难性的程度。要交付任何有意义的着色器精度优化,先得放弃对 GTX 的 FP16 支持。相比之下,AMD RX 570 在 FP32 与 FP16 上吞吐相同,所以这个优化空间高度依赖架构,而不是简单地“把 FP16 打开”。

唯一一个真正容易的修复#

那篇帖子从 Perfetto 追踪里记录了 r.Streaming.PoolSize = 400。在拥有 12–16 GB 内存的旗舰移动设备上,只为纹理流送预留 400 MB,直接就是 L1 与 L2 缓存被打穿的原因——纹理没有被提前载入更快的内存,因为这个池子太小装不下。

这是一个配置值。它没有任何架构层面的权衡。它是那份分析里唯一一个真正可以在不涉及我在别处描述的任何复杂性的情况下解决的发现。

关于 r.Streaming.FullyLoadUsedTextures 与 r.Streaming.HLODStrategy: 这两个是真实存在的 UE4 引擎 cvar——FullyLoadUsedTextures 强制所有活跃纹理立即流送进来,HLODStrategy 2 完全禁用 HLOD 专属的流送。社区成员 @HtooMyatLin3 报告说,这两个 cvar 在旧版补丁中可用(在早期 AlteriaX 配置提交里可见),但现在被库洛通过代码或控制台以更高优先级设置——经日志分析确认——也就是说 Engine.ini 里的条目会被覆盖,运行时不再生效。这代表一个案例:一个容易的修复曾经存在、曾被社区使用,之后在发行版里被锁掉了。


6. 更艰难的真相#

库洛实际处在什么位置#

库洛不是什么都没做。但他们也不处在那种可以靠优化在所有硬件档次上都做到顺滑的位置上。他们处在一种更难受的境地:局部最优陷阱——一种任何可行动作都会让某方面变得更糟的状态。

  • 如果为深度优化而暂停内容更新:玩家参与度下降、收入下降、项目存续受威胁——而这又抽走了优化所需的预算与理由
  • 如果继续推内容而不做优化:技术债继续累积、性能恶化、玩家不满加剧
  • 如果尝试大规模架构重构:就要在一个无法容忍严重回归的在线系统上冒灾难性风险

每个方向都有代价。当前的状态不是懒惰,而是当所有替代方案都更糟时涌现出来的均衡。

TS→C# 迁移之所以重要,是因为它不是在这个陷阱内部做优化——而是试图改变陷阱本身的形状。局部搜索——修某个剔除问题、调着色器精度——能逐步改善性能,却改变不了底层约束。脚本迁移则是一次结构性变更,打开了从当前位置无法触及的解空间。

它不会解决 GameThread 天花板。UE4 的线程模型仍会限制可并行的工作量。但把 V8 的 stop-the-world 暂停从帧预算里拿走,会给 GameThread 留出更多裕量,并降低最坏卡顿事件的严重程度。即便这不是完整方案,这也是一项有意义的改善。


视觉野心是放大器,不是病因#

性能讨论里有一种常见叙事:“库洛在画面上太激进了——如果他们把视觉降下来,游戏就顺畅了。”

硬件数据不支持这个说法。

检验方法很简单: 如果视觉野心是卡顿的根因,那么关掉它就该消除卡顿。AlteriaX 的社区 Engine.ini 配置允许玩家关闭光追、降低阴影分辨率、调低 ViewDistanceScale、剔除植被密度、把人群密度降到接近零。使用这些配置的玩家报告帧率变好了——但学院里的模式 A 卡顿、边界切换时的模式 B 尖峰依然存在。严重程度下降了,模式没有消失。

这个区别很重要:严重程度受画质设置影响,瓶颈的存在不受。

三段基准视频展示了在差异极大的画面配置下,硬件缩放的模式:

i5-4690 + GTX 750Ti,全最低,开 FSR(v3.0,3.1 流送修复之前): 在学院里行走:30 大几到 45 帧。使用载具或快速移动:掉到 20 中到 35 帧。开放世界道路上高速载具:稳定约 30 帧。复杂特效战斗:20 中到 30 帧。—— 基准视频

这是一块 2014 年的、2GB 显存的 GPU,跑全最低。游戏是可玩的。而卡顿的模式——走路时稳定,快速穿越边界与使用载具时掉帧——与高端系统经历的完全一致,只是帧率地板更低。

i5-1035G1 + 核显,8GB 内存,720p 全最低(低于最低配置要求)—— @argon1ut:

整体表现:约 15 帧。值得注意的是,与运行在更高帧率的高端系统相比,它的帧生成节奏反而相对稳定。线程利用模式未变——由少数线程决定性能,TaskGraph 工作线程继续锯齿式突发。工作线程的活动看起来比高端硬件更均匀、突发幅度更小——很可能是因为主线程吞吐降低后,向工作线程喂活的速率更慢也更稳定,而不是大批量突发。

尽管 8GB 内存在显著的显存/内存压力下(已完全占满),这似乎并不是帧生成节奏的主要驱动因素——它增加了总体性能成本,却没有从根本上改变执行模式。

注:这台系统跑在最低配置以下,会引入来自内存压力、GPU 吞吐下降与持续 CPU 性能不足的额外变量。绝对指标不可直接比较。此处的目的是在受限条件下的行为观察——而在那个语境里,结果与高端观察保持一致。

这个数据点最重要的洞见是:在约 15 帧时,每帧的帧时间预算约 66ms,而 60 帧时约 16ms。协调瓶颈仍然存在——线程利用率可以证实——但它对主观流畅感的影响不那么显眼,因为系统本来就运行在远低于卡顿可感知阈值的水平上。这与 GTX 750Ti 用户的观察一致:瓶颈模式存在于所有硬件档次,但严重程度随硬件能力与引擎天花板之间的差距而缩放。

i7-4790K 超频至 4.7GHz + GTX 1660 Super,中低画质,开 FSR(v3.1): 未使用载具的巡游:约 55–60 帧。学院内使用载具:30–45 帧。开放世界道路上的载具:40 大几到 50 帧。战斗:视特效复杂度 35–60 帧。学院内 1% Low:8–15 帧。—— 基准视频

那 8–15 帧的 1% Low,很可能捕捉到了快速切换时类似模式 B 的复合尖峰,同时叠加了模式 A 带来的常规城内帧节奏不稳、流送预热、着色器编译与特效复杂度。一颗超到 4.7GHz 的 4790K 依然撞上协调瓶颈,因为在这里 IPC、缓存行为、内存延迟与 GameThread 的工作量都比原始频率更重要。

i7-9700K + RTX 2080 Super,高画质,无光追,DLSS 质量(v3.1): 1080p——学院内行走:稳定约 60 帧。学院内载具:45–60 帧。开放世界载具:稳定 60 帧。战斗:55–60 帧。1440p——学院内行走:平均约 47 帧,区间 47–60 帧。学院内载具:掉到约 40–50 帧。4K——学院表现与 1440p 类似。开放世界高速移动:40 大几帧。战斗:40 大几到 50 帧。—— 基准视频

注意:此配置下光追是关闭的。并没有为光追支付“视觉野心”的溢价——但随着分辨率提高、GPU 成为共同因素,瓶颈模式在 1440p 与 4K 下依然存在。

这些数据合起来说明了什么:

跨越这三个硬件档次——从 GTX 750Ti 到 RTX 2080 Super,从全最低到高画质,从 2GB 显存到 8GB 显存——同一个模式反复出现:巡游平稳,快速移动与穿越边界时掉帧,战斗特效再添一层压力。瓶颈机制在所有档次上都存在。

视觉野心确实贡献了真实成本——光追增加 RenderThread 压力,复杂特效争夺 GameThread 预算,更高分辨率抬高 GPU 帧时间。当这些成本与 GameThread 协调瓶颈叠加,帧预算缩得更快,天花板就更早显现。这就是 @argon1ut 所说的:“视觉野心放大的是引擎里既有的约束。”

但约束本就存在。关掉视觉野心降低严重程度,却消除不了架构瓶颈。正确的表述不是“库洛画面太激进所以游戏卡”,而是:库洛的视觉野心让一个既有的架构问题在更多硬件档次上、更频繁地暴露出来。

GTX 750Ti 的数据把这一点具体化了:一块跑不了光追、扛不住高画质、以最低人群密度运行的显卡——依然呈现出同样的卡顿特征。引擎架构是地板。画质设置只决定你多快撞到它。


“优化差”到底意味着什么——以及为什么这个比较比看起来更难#

当有人说鸣潮优化差时,隐含的基准通常是两种之一:“赛博朋克能 60 帧以上跑得很顺,鸣潮为什么不行?”或者“原神、终末地都很顺,为什么这个不行?”

这两种比较都忽略了鸣潮同时在尝试做的事情。

PC 社区里还存在一种预期错配。在 i5-12400F + RTX 3060 被视为主流游戏基准的市场里,玩家习惯了优化良好的 3A 作品跑得顺,于是也期待鸣潮是那样。这份期待可以理解——但它也可能是误导性的。在最高画质、DLAA/光追、密集城市、高特效密度、NPC 逻辑、流送以及长线运营的内容规模下,鸣潮已经不再表现得像一个轻量的抽卡负载了。它的视觉上限比许多玩家以为的更接近现代高预算开放世界作品。

这就是为什么单看 CPU/GPU 利用率会把讨论带偏。一款自研引擎的 3A 作品可能把 GPU 压满、体感流畅;而鸣潮可能表现出较低的 GPU 占用与不均匀的帧节奏,因为 GPU 在等 GameThread 协调、脚本、流送或 RenderThread 同步。这种形态确实是真实的性能限制,但它并不能自动证明渲染器低效,或开发者“什么都没做”。

同样重要的是,把游戏层面的优化问题,与用户侧配置和运行时环境问题区分开。鸣潮确实存在真实的引擎/内容侧问题:GameThread 饱和、流送尖峰、着色器/材质预热、GC 压力、移动端特效/剔除成本,以及帧节奏一致性。但并非每一份糟糕的反馈都是游戏层面优化失败的干净证据。设置与硬件不匹配、帧率不锁、显存裕量不足、着色器缓存损坏、叠加层、NVIDIA Filters、Process Lasso 亲和性调整、强制优先级、激进的 INI 修改、内存/CPU 超频不稳定,或者驱动/运行时冲突,都可能制造或放大卡顿。把这一切都归为“优化差”,只会让诊断更糟。

不妨看看库洛身处的完整约束栈:

  • 开放世界游戏,拥有大型、持续加载的环境
  • 3A 级视觉野心——光照、几何复杂度与特效密度可比高预算 PC 作品
  • 人口密集的城市,可能有数百个活跃的 NPC 与交互实体,部分带 AI 行为树与脚本逻辑
  • 以战斗为核心的设计,要求紧凑的输入延迟、复杂特效与同时进行的物理模拟——它们共享同一份 GameThread 预算
  • 跨平台——PC、PS5、iOS、Android、Mac——各自的 GPU 家族、内存约束与调度器行为都不同
  • 硬件跨度过大,从旗舰手机到低端安卓设备,需要一套能服务所有人的配置调优
  • 六周长线运营节奏,持续交付新角色、新区域与新系统
  • 以 UE4 为地基——一个通用引擎,并不是为上述任何一种需求的组合而设计的

目前没有其他游戏在 UE4 上同时做所有这些事。不是《霍格沃茨之遗》(离线、无移动端、战斗更简单)。不是《幻塔》(视觉标准更低)。不是《原神》(不同引擎、不同架构、图形天花板更低)。对“与处在相同约束下的游戏相比,鸣潮是不是优化差”这个问题的诚实答案是:没有处在相同约束下的游戏可供比较。

在下结论说鸣潮的表现比它应有的更差之前,你需要找到另一款同时具备以下条件的 UE4 游戏:开放世界、3A 级视觉保真度、六周节奏的长线运营、战斗密集且特效复杂、从移动端到 PC 的跨平台,并且支持从低端机型到旗舰的宽硬件跨度。在这个比较对象出现之前,“库洛的优化低于某个合理基线”这一说法是不成立的。

我们能说的是:考虑到它尝试的所有东西,鸣潮已经跑在它的引擎架构所允许的天花板附近了。这个天花板是真实的、是引擎层面的,并且在可比语境下,还没有外部团队展示过如何逃出它。

赛博朋克跑在 REDEngine 上——一个工作室花了十年为开放世界性能专门打造的引擎。大镖客 2 跑在 RAGE 上——为密集开放世界环境迭代二十余年。这些不是性能比较。它们是“承压的通用引擎”与“专为避免这种压力而设计的自研基础设施”之间的比较。

诚实的比较是 UE4 对 UE4:《霍格沃茨之遗》带着华纳兄弟的预算,照样在密集区域 CPU 受限。《绝地求生》——虽然不是开放世界游戏,但它是规模最大、运营最久的 UE4 作品——按 PlayerUnknown 本人的说法,自 2017 年起就不得不深度修改引擎代码(“我们不得不对核心引擎代码做大量改动,才能让东西在那种规模下跑起来”——Rock Paper Shotgun),而社区关于微卡顿与帧节奏不稳的报告,在投入了这么多年之后依然存在。正如密切关注绝地求生性能史的 @argon1ut 所指出的:“到目前为止,我只找到绝地求生是最有可能的候选者[即逃出 UE4 限制],但除了修改深度引擎代码的说法之外,还没有明确的证据。” 品类上最接近的《幻塔》在其整个生命周期里也一直面临性能抱怨。目前还没有一款明确可比的 UE4 开放世界长线运营作品,公开展示过对这种模式的彻底摆脱。

还有一个因素会叠加在一切之上:跨平台支持。鸣潮同时登陆 PC、iOS、Android、PS5 与 Mac。每一个优化决策、每一个配置值、每一处着色器取舍,都必须同时适配从 Adreno 到 Mali 到 Apple 再到桌面 AMD 与 Nvidia 的 GPU 家族,适配从 6GB 内存手机到 64GB 工作站的设备。这是单平台游戏根本不会面对的约束,它让每一个“为什么不直接把 X 修了”的问题都难答得多。

准确的表述不是“库洛没能解决其他工作室解决过的问题”。准确的表述是:当一个具有这种视觉野心的项目运行在这套架构上、并且采取了一种让深度基础设施改动几乎不可能的长线运营部署模型时,硬件跟不上累积起来的技术债与引擎限制。

这跟“开发者懒”是完全不同的诊断。它指向的也是完全不同的预期。

有一个具体数据点为这一点提供了落脚:一款真正优化很差的游戏,不可能在一台 i5-4690、DDR3 内存、GTX 750Ti 2GB——2014 年的硬件、低于最低配置要求——上还能玩。鸣潮可以。这不是“工程师没努力”的游戏该有的表现,而是“工程师在真实约束下工作,依然交付了一套跨越少数 3A 作品才敢尝试的硬件跨度的可扩展体验”的表现。

话虽如此,本文描述的架构背景为当前约束下的当前性能提供了合理性依据——但这不是给未来补丁开出的空白支票。随着 C# 迁移成熟、MagicDawn 集成深化、腾讯的全面支持带来更长期的架构投入能力,可接受的阈值会移动。如果未来在资源条件明显更好的情况下推出的城市——v4.x、v5.x 及之后——依然表现出与星炬学院同等严重程度的模式 A,那么架构约束的论据就会变弱,实现质量就会成为更诚实的解释。星炬学院是鸣潮引擎的首次此类压力测试。未来密度相当的城市将不再有那个借口。这就是库洛现在必须跨过的标杆。


TL;DR#

鸣潮为什么会卡——简短版

游戏在密集城市里的性能瓶颈位于 GameThread——UE4 的中心协调点,所有游戏逻辑都必须经过它。鸣潮确实使用了 UE4 的 TaskGraph 工作线程系统,但当场景密度很高时,GameThread 上的协调与同步开销仍然是天花板。更多 CPU 核心解决不了这个问题。我的基准测试(i7-12700KF + RTX 5070,城区巡游,不开帧生成):GPU 平均占用 54–58%,而 CPU 最大线程达到 97–100%。GPU 在等,它不是瓶颈。

把它想象成一条八车道高速——但只有一个收费站。所有车流都必须先并流、通过那一个点,才能继续往前。加再多车道,拥堵也不会缓解。那个收费站就是 GameThread。星炬学院里有成百上千个 NPC、带活跃交互逻辑的建筑,以及复杂的氛围系统,全都想同时通过它。线程饱和了,一切都在等。

为什么他们不能直接修好?

因为那个收费站是虚幻引擎 4 设计方式的一部分。它不是库洛写出来的 bug——它是每一款 UE4 游戏所建立在其上的架构地基。《霍格沃茨之遗》有同样的问题,《幻塔》也有。目前还没有一款明确可比的 UE4 开放世界长线运营作品,公开展示过对这种模式的彻底摆脱。

库洛实际在做什么?

持续打补丁——几乎每次大版本更新都有性能改进。同时悄悄把脚本层从 TypeScript(使用 V8 的垃圾回收器)迁往 C#(一个具有增量式、低暂停 GC 的运行时)。这次迁移已由运行时日志、二进制分析与数据挖掘证据确认。它昂贵且对玩家不可见——而他们一边维持六周内容节奏,一边还是在做。

底线

“开发者懒”解释不了为什么《霍格沃茨之遗》在华纳兄弟的预算下也出现同样的卡顿模式。“引擎天花板 + 长线运营技术债”能解释。要说库洛的优化低于应有水平,需要一个可比的游戏——UE4、开放世界、长线运营、跨平台、战斗密集——作为基准。目前还没有公开例子能清晰满足这组比较条件。


如果你想看每件事在架构层面为什么会发生的完整技术拆解——从头读起。


本次分析的局限性#

本分析依赖来自 Arikatsu/WutheringWaves_Data 的数据挖掘配置文件、本人的基准采集(CapFrameX,i7-12700KF + RTX 5070——两次会话:瑝珑与星炬学院)、社区报告的硬件追踪、虚幻引擎官方文档,以及社区的剖析数据。

关于 Puerts/V8 的论断: 现在有直接证据支持。运行时日志文件(Client.log 以及多个 Client-backup-*.log)在每次启动时都明确记录了 V8 初始化、Puerts 模块加载与 V8 版本字符串。从 Client-Win64-Shipping.exe 提取的字符串包含 Puerts、PuertsJsEnv、KuroPuerts、TypeScriptGeneratedClass 以及多个 /Game/Aki/TypeScript/ 路径。通过 FModel 检查 pak 内容(借助 https://github.com/ClostroOffi/wuwa-aes-archive 的社区 AES 密钥归档)可以看到 ScriptAssemblies 目录,其中包含与 Puerts 层一起部署的 C# 运行时程序集。这一论断是被确认的,不是推断的。

关于 C# 迁移: 基础设施已在数据挖掘中得到确认(aki_base.csv v2.8+、_csharp 目录)。ScriptAssemblies 的 pak 内容确认 C# 运行时已部署。内容迁移仍在进行中——配置文件仍只被部分填充。

把 175ms 与 245ms 的帧尖峰解释为 GC 暂停事件,与 V8 有文档记载的行为相符,但没有脚本 VM 的 profiler 访问权限就无法确认。两个尖峰都是真实的、由硬件数据测得的。在 GPU 与温度相关性缺失的前提下,这个成因是目前技术上最一致的解释。

GameThread 饱和与剔除失效之间的因果归属(第 5 节),是对数据在架构上最一致的说明,但在没有代码库级 profiler 权限的情况下,无法与实现质量的贡献完全分离。

关于定制化的 UE4 构建: 社区引擎配置分析发现了 r.Streaming.UsingKuroStreamingPriority——一个原版 UE4 流送系统中并不存在的 cvar——进一步确认引擎经过的修改远超标准集成程度。它有文档记载的行为(分别控制保留优先级与加载优先级,并针对不同资源类型做游戏特定的取舍),与一套建立在 UE4 基础框架之上的自定义流送管线相符。

在无法获得直接证据的地方,相关论断均标注为推断。

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

[转载/熟肉] 鸣潮的性能与卡顿:引擎架构、硬件误区与长线运营的真实代价
https://justpureh2o.cn/articles/32709/
作者
JustPureH2O
发布于
2026-10-17
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
JustPureH2O
穷方圆平直之情,尽规矩准绳之用
公告
JustPureH2O 的博客现已正式迁移至 Astro!原 Hexo 网站将移至 https://hexo.justpureh2o.cn/
音乐
封面

音乐

暂未播放

0:000:00
暂无歌词
分类
标签
站点统计
文章
108
分类
17
标签
64
总字数
418,721
运行时长
0 天
最后活动
0 天前
站点信息
构建平台
Vercel
博客版本
Firefly vunknown
文章许可
CC BY-NC-SA 4.0

文章目录