英特尔® 图形性能分析器帮助构建《Frostpunk》*中的雪景模拟

 

Frostpunk

位于波兰华沙的 11 bit 工作室* 花费大量精力设计并实施其雪景模拟和雪景渲染系统。他们的内部 Liquid 引擎有多个自定义地形和着色特性,在天气多变的游戏世界中能够获得逼真的视觉效果。本文首先简要介绍了雪景模拟和渲染的部分实施细节。然后介绍了 11 bit 工作室和英特尔如何通过英特尔® 图形技术提升雪景渲染的 GPU 性能,同时保持视觉保真度,以使雪的外观和动态轨迹满足沉浸式游戏和故事叙述的要求。

游戏针对锐炬® Pro 显卡 580 而优化,使用英特尔® NUC 套件 NUC6i7KYK - 该平台之前被称为 Skull Canyon(了解详情)和第六代智能英特尔® 酷睿™ i7 四核处理器(8 线程)和 Windows® 10 操作系统。在本文中,用于优化性能的主要工具是英特尔® 图形性能分析器(英特尔® GPA),您可以在英特尔下载页面上免费获取该工具。

得益于 11 bit 工作室开发人员的全力配合,我们将测试场景中的平均帧速率从约 17 帧/秒(fps - 约 58 毫秒)提升至约 35 fps(约 29 毫秒),进一步优化了游戏体验。在本文中,我们将全面介绍雪景模拟和渲染系统,然后展示对游戏整体可玩性至关重要的若干 GPU 优化。

真实的雪景模拟非常重要

《Frostpunk》* 的冰封世界中,雪扮演着重要的角色。玩家统治地球幸存者聚居的最后一座城市,在这里,严寒是永远的敌人。虽然热源可能在短时间内将无休止的风雪挡在门外,城市居民可以在行进过程中清除积雪,但是雪还会继续下。在《Frostpunk》中,雪景计算模拟和雪景渲染完全在 GPU 上执行。GPU 雪景计算模拟分为 3 个阶段:雪的初始化,雪的融化和雪的降落。

雪的初始化

模拟依赖于两个动态纹理:雪景高度图和降雪遮罩。降雪遮罩用于防止雪降落在划定区域,如建筑物下、加热区内和街道上(见图 1)。在游戏开始时,使用来自游戏素材的输入数据对雪景高度图进行初始化,清除降雪遮罩。为了获得出色的视觉效果,雪景高度图使用 R16F 格式,降雪遮罩使用 R8G8 格式。R16F 是一种 16 位浮点,提供较高保真度的雪景高度表示。R8G8 仅仅表示是否降雪,无需浮点表示(用 R8G8 表示该区域是否降雪都有些大材小用)。

Snow heightmap texture

Snow Fall Mask

图 1.雪景模拟中使用的纹理。左侧是雪景高度图纹理,右侧是降雪遮罩(以显示停止降雪的区域)。两个纹理都是 1K;第一个纹理使用 R16 格式,第二个使用 R8G8 格式。

雪的融化

在整个游戏中,几种不同类型的事件会导致融雪。所谓的“融雪”事件被批量实施为成本相对低廉的“融化四边形”(melt quad)绘制调用。融化四边形使用带正或负亮度的加法混合模拟融雪,并使用额外的纹理和程序化噪声函数创建各种融化效果。融化四边形还用于更新降雪遮罩纹理。

  • 市民或机器人移动。每个市民和机器人的每条腿都附带融化四边形,后者使用额外的纹理定义大小、下降、噪声、抑制等随机变化的参数。参见图 2 和图 3。
  • 建筑物或街道布局。每个建筑物和街区也附带融化四边形。施工开始后,建筑物周围的雪会立即融化。参见图 4。它被保存在降雪遮罩的红色通道中。
  • 蒸汽锅炉的运行。热源也附带融化四边形,但是不同于建筑物和街道,蒸汽锅炉周围的雪融化得更慢、动态性更强。参见图 5。它被保存在降雪遮罩的绿色通道中。

 snow melting due to in game action

 snow melting due to in game action

图 2.通过市民移动融雪的示例。

Snow heightmap changes to reflect melted snow
Animation of Input Assembler view

图 3.市民移动模拟。在左侧,雪景高度图不断变化,以反映融化的雪。右侧为输入装配器视图的动画,显示批量市民融化四边形绘制调用。

Texture used for the Ambulatorium building
Texture atlas used for common building

图 4.建筑物融化四边形中使用的纹理示例。在左侧,一个纹理用于诊所建筑物。在右侧,纹理图集用于普通建筑物。

Animation of snow melting trough steam and heat
图 5.通过工作的蒸汽锅炉和机器人移动融雪的示例。

雪的降落

雪一直在下,之前融化的地方重新被雪覆盖。该操作相对简单,不断增加雪景高度图的数值,直到它们达到原始初始化值。使用来自游戏素材的数据对动态高度图进行初始化(原始雪景高度图由图形工作者塑造)。这是降雪的起点,也是它的最终目标。降雪像素着色器首先应用新的雪,然后在雪景高度图中锁定该值,使其保持在适当的范围内(高于 0.0,低于从素材中读取的最大值)。为了提高视觉质量,使用额外的纹理和参数使雪不均匀地堆积。降雪遮罩用于防止雪降落在不该出现的地方,如街道、建筑物周围和活跃高温区内部(由蒸汽锅炉创建)。

网格和贴花创建雪景渲染系统

11 bit 工作室 Liquid 引擎中的 G 缓冲区通道包含 3 个阶段:渲染实体网格,实体柔化网格和贴花。实体柔化网格是通过前一阶段的实体网格提供光滑拼接支持的网格。使用柔化网格渲染雪地地形,以便在雪地和环境对象之间实现光滑拼接。

雪景系统的动态地形完全由 GPU 控制。支持的几何由 N x N 个地形图块构成。在几何体实例化的帮助下渲染地形图块。参见图 6。

Tiles division in checkboard pattern
N x N 个图块划分(黑白棋盘图案)
Coarse geometry wireframe, without tesselation
未应用曲面细分的粗几何线框

图 6.雪景系统的地形图块结构。

整个地形被分为 D x D 个区域,以支持 CPU 视锥剔除。参见图 7。

Terrain subdivision in bounding boxes for culling
图 7.针对 CPU 视锥剔除的额外地形细分。绿线表示用于视锥剔除的细分区域边界框。

每个地形图块包括 M x M 个使用曲面细分着色器进行曲面细分的碎片。地形图块数量和曲面细分碎片密度均可以在地形属性中配置。参见图 8。

Patches division per tile
选定雪地地形区域上每个图块被划分为 M x M 个碎片(黑色与红色棋盘图案)
Coarse geometry wireframe preview
选定雪地地形区域的粗几何线框预览

图 8.用于选定地形区域的地形曲面细分碎片结构。

地形系统取决于以下纹理图集:

  • 雪景高度图。由雪景模拟动态修改。高度图在几何位移和法线计算中被用作地形位移图。
  • 材料层图。用于将纹理 splatting 应用于雪地。
  • 材料混合图。用于存储每个着色材料的权重。
  • 材料纹理阵列。用于存储地形着色材料。
  • 密度图。用于存储可以在着色器中进行曲面细分的地形区域信息。
  • 柔化图。用于存储可以与实体对象混合的地形区域信息。
  • 细节图。用于存储额外的高分辨率法线/粗糙度/金属图。该纹理对于自然平坦的雪景对象尤其重要。

顶点着色器将基本变换应用于平坦地形碎片。外壳和域着色器基于密度图和距离 LOD 评估执行自适应曲面细分。它们还应用地形位移并执行地形碎片的 GPU 剔除。像素着色器负责生成几何法线、应用和混合材料等操作。相比在域着色器中执行计算,在像素着色器中计算法线矢量会大幅增加帧时间成本,但是生成的视觉效果却令人惊叹,并且通过低分辨率输入几何创建详细的地形。请参见图 9 中的示例输出。

Albedo texture render
Normal map texture
Final render of scene
Animation of Wireframe of scene

图 9.雪景渲染的示例输出。左上:反射率,右上:法线图。左下:最终渲染结果。右下:线框预览,距离基于自适应曲面细分

使用英特尔® GPA 进行性能分析

我们的许多性能分析都是使用英特尔 GPA(链接位于文章顶部)执行的。GPA 是一套图形应用性能分析工具,包括用于捕捉和回放游戏单帧的工具。英特尔® GPA 可用于集成和独立 GPU 硬件。被用于英特尔集成显卡时,GPA 提供了一系列详细的硬件指标,由此可以得出非常实用的瓶颈分析。在整个优化讨论中,我们将引用多个英特尔 GPA 特性。(有关这些特性的更多详情请参阅《英特尔 GPA 用户指南》)。

查看《Frostpunk》中的单个帧时,首先注意的是负责雪景模拟的第一个绘制调用在 Skull Canyon 上花费近 3 毫秒的 GPU 时间。

Intel GPA Output
图 10.英特尔 GPA 代码片段。它表示该帧的所有绘制调用,每个长条的高度表示执行特定绘制调用花费的时间。橙色条是负责《Frostpunk》中融雪与降落的绘制调用。

了解了该雪景模拟的实际作用后,考虑到我们的目标是在该硬件上实现最低 30 fps,该绘制调用似乎过于昂贵。

以下是该绘制调用中使用的像素着色器的 DirectX* 字节码:

DirectX* bytecode of the pixel shader

我们可以看到该着色器比较简单:4 个简单的指令,穿插着一些简单的数学运算。执行如此简单的着色器怎么会花费 3 毫秒时间?英特尔 GPA 的便捷特性可以帮助我们深入挖掘(参见图 11)。

Intel GPA screenshots
图 11.英特尔 GPA 视图片段。左侧是用饼图表示的着色器执行特征;右侧是英特尔 GPA 瓶颈视图。

在英特尔® 架构术语中,并行 GPU 内核被称作执行单元 (EU)。图 11 左上角的饼图展示了 EU 执行该绘制调的时间分配:

  • 绿色表示活跃时间(EU 正在运行着色器指令)。
  • 红色表示停止时间(EU 等待的时间)。
  • 灰色表示闲置时间(没有任何着色器在 EU 上运行)。

在该雪景模拟绘制中,EU 停止时间占 3 毫秒的 85%。

图 11 右侧的图表是一个管道流程图,使用硬件指标和启发法指出瓶颈可能出现在哪些地方。该视图中的 “Selection” 和 “Full Frame” 选项卡支持您查看根据管道阶段估计与排序的原始指标。请注意 “Shader Execution” 方框为绿色,但是 “LLC/EDRAM/DRAM” 方框为红色,表示瓶颈很可能与内存读取/写入相关,来自像素着色器的 4 个示例指令。为了探究原因和找出必要的优化,我们需要了解有关 GPU 和系统架构的更多信息。

英特尔优化与内存架构

英特尔® 处理器显卡不包括专用视频内存,而是实现统一内存的概念,即在包含 GPU 和 CPU 的整个系统内共享系统 DRAM 内存。

Flowchart of SoC chip level memory hierarchy
图 12.面向第九代英特尔处理器显卡计算架构的 SoC 芯片级内存层级结构及其理论峰值带宽。

英特尔处理器显卡拥有一个高速缓存层级结构,其中,每个纹理采样器单元都有自己的本地高速缓存层级结构,三级高速缓存由所有 GPU EU 核心和采样器共享。如果 GPU 需要不在三级高速缓存驻留的数据,必须发出访问共享系统内存的请求,才能检索该数据。末级高速缓存内存 (LLC) 先与 CPU 共享,最后与系统 DRAM 共享。(某些产品还在 LLC 和 DRAM 之间采用 64-128 MB EDRAM)。

EU 对纹理资源进行采样时会发生什么情况?由于 GPU 的并行性和典型的空间局部性,采样器不会只提取一个纹理的数据,而是提取整个高速缓存行的数据,在英特尔 GPU 上,一个高速缓存行有 64 个字节。因此,对 R16F 雪景高度图进行采样时,纹理提取将提取 32 个相邻纹理的数据。由于许多像素着色器并行运行,并且每个着色器从相同的纹理请求数据,我们通常希望我们请求的数据已经在高速缓存中。

我们回到英特尔 GPA 的问题上。图 13 显示该绘制调用的详细内存指标。请注意,数据显示为两列。在英特尔 GPA 中,您可以做各种实验,左列数据表示实验后的测量结果,右列表示原始值。在本示例中,我们没有进行任何实验,因此,两列的数据是相同的。

Detailed memory metrics
图 13.英特尔 GPA 中雪景模拟绘制调用的详细内存指标。

GTI 是英特尔显卡访问系统内存所用的接口名称。GTI 读取吞吐量有些可怕—仅仅为了该绘制调用,从 GPU 外部读取了超过 63 MB 数据—从 DRAM 或 LLC 传输至 GTI。执行单元很有可能长时间停止,因为它们请求的数据无法在本地采样器高速缓存或 GPU 三级高速缓存中获取。它们因等待 GTI 从 GPU 外部提取数据而停止。

图 14 中的采样器高速缓存指标证实了这一理论,显示约 170 万高速缓存未命中数量:

Sampler cache metrics
图 14.英特尔 GPA 中的采样器高速缓存指标

如果我们拥有出色的高速缓存层级结构,并且一次提取 32 个邻近纹理,为什么高速缓存未命中数量这么高?一种可能是如果我们采样的纹理非常大,那么相对 UV 会指向实际上相距甚远的纹理,因此,每个高速缓存行仅包含少量的有用数据。在这种情况下,没有必要将相邻但未使用的数据填入采样器高速缓存和三级高速缓存。

其中一个被采样的雪景模拟纹理是 4 K 纹理,其他均只有 1 K。通过分析我们怀疑,相对着色器采样的较低频率,该大型纹理的频率过高。我们将该反馈和洞察提供给 11 bit 工作室,并说明该硬件上的调用有多昂贵,他们迅速将 4 K 纹理替换为 1 K。图 15 和 16 显示修改后,该绘制的 GTI 和采样器高速缓存指标。这一修改不会对最终图像造成太大影响。

Detailed memory metrics
图 15.11 bit 工作室替换为 1K 纹理后,英特尔 GPA 中雪景模拟绘制调用的详细内存指标。

Sampler cache metrics
图 16.11 bit 工作室替换为 1K 纹理后,英特尔 GPA 中的采样器高速缓存指标。

GTI 读取吞吐量从 63 M 降至 18 M。目前,GTI 内存读取数量降至之前的 1/3,高速缓存未命中数量减少了约一半,从而节省了 2.4 毫秒的总帧时间。

改善高速缓存行为后,雪景模拟的瓶颈变成“线程调度”,部分瓶颈存在于采样器单元中(见图 17)。英特尔 GPA 提示着色器逻辑可能运行效率低下,为了改善这种状况,可以降低着色器线程有效负载(如寄存器使用)。如果您想发现更多不同的瓶颈,请参阅《第九代英特尔显卡 API 性能指南》(链接位于参考部分)。

将该建议反馈给 11 bit 工作室后,他们提出了其他几项优化。

Pipeline bottlenecks screenshots
图 17.线程调度和采样器中的管道瓶颈

其他优化

雪景模拟被修改为不在动态雪景高度图上直接运行。雪景模拟输出是单个动态 R8 格式雪景位移遮罩纹理。该纹理和静态高度图被绑定到雪地地形着色器,静态高度图包含初始化位移数据,后者通过相乘得出最终雪景高度值。

这将执行雪景模拟所需的示例总数从 4 个减少到两个,无需在降雪计算过程中执行锁定操作。这大大减轻了该绘制中的采样器压力。

修改雪景模拟纹理格式的一个优势在于减小了游戏保存和定时快照的大小。

进一步重新设计降雪算法,使其在图块(而不是整个高度图)上运行。目前,整个区域被划分为 N x N 个图块,以轮询的方式每帧只将降雪应用到一个图块。也就是说,现在每帧仅更新一个图块,而不是每秒更新整个地图上的雪景 30 次。因此,我们将雪景计算模拟与渲染分离。该解决方案是可行的,因为降雪的速度非常慢。因此,无论以每秒 30 次还是每秒一次的频率更新整个地图,其实都不重要。但是,这一变化会导致该绘制调用的整体性能大幅下降。通过禁用面向最低质量配置文件的程序化噪声应用,以及删除另一个采样指令,降雪着色器也被简化。在这些因素的共同作用下,降雪绘制的每帧成本约为 17 微秒,几乎可以忽略不计。如果您的游戏正在整个地图上执行此类 GPU 模拟,以类似的方式降低更新频率值得一试。

最终,11 bit 工作室简化了雪景渲染像素着色器,以避免昂贵的法线矢量计算,假设雪地区域为平面,法线矢量为 (0, 1, 0)。在法线图中使用地形细节图仍生成了令人满意的视觉效果。这无疑是一种高性能、低成本的优化方案。

结论

以下是在采用英特尔® 锐炬® Pro 显卡 580 的英特尔® NUC 套件 NUC6i7KYK 上运行的游戏成品截图。

Game in play mode
图 18.在英特尔® NUC 套件 NUC6i7KYK 上运行的游戏截图。

在英特尔与 11 bit 工作室合作期间,该工作室提升了《Frostpunk》的多项性能,包括雪景系统优化。如以上截图所示,实现该目标的同时保持了视觉质量的高标准 - 尤其是美丽的雪景。

如果您还没有使用英特尔 GPA 优化代码并增强性能指标评测,您应该抓紧时间使用它提供的各种工具。从本文可以看出,充分了解英特尔 GPA 工具套件使《Frostpunk》受益匪浅。我们鼓励您亲眼见证这惊心动魄的雪景。毕竟,凛冬将至……

致谢

首先,非常感谢 11 bit 工作室的全力配合,以及本文的合著者 Szymon。感谢帮助我们发表本文的所有评论员:Adam Lake、Stephen Junkins、Jefferson Montgomery、Geoffrey Douglas 和 Dietmar Souch。

资料来源

有关编译器优化的更完整信息,请参阅优化通知