游戏开发论坛

 找回密码
 立即注册
搜索
查看: 2381|回复: 0

如何在 UE4 移动端中实现 HZB?

[复制链接]

4万

主题

5万

帖子

8万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
85906
发表于 2020-9-29 13:46:20 | 显示全部楼层 |阅读模式
Hierarchical Z-Buffering 分层 Z 缓冲(HZB)对遮挡剔除研究具有重要影响,是 GPU Driven Rendering Pipeline 的重要剔除手段。目前部分主流商业引擎可能因为某些原因导致该技术无法完全在 GPU 端工作,但依然是值得探讨的。本文先介绍 HZB 的基本原理以及 UE4 在 PC 端的实现方式,然后介绍如何移植到移动端并分析其性能和带来的价值,以及未来还可以做的工作。

HZB 的原理

一般来说,大多数基于 HZB 的遮挡剔除是这样的工作的:

1. 使用一些遮挡器生成一个完整的分层 Z-金字塔。

1.JPG

z-pyramid 的最低级别是一个标准的 z-buffer。在所有其他层,每个 z 值都是上一层对应的 2×2 像素中最远的 z。

2.JPG

2. 要测试的对象是否被遮挡,可以将其包围体投射到屏幕空间,并在 z-pyramid 中估计 mip 级别。将对象的包围体投影到屏幕空间。最长的边 l(像素)用来计算 mip 等级λ。

3.JPG

边长越长,选取的 mip 等级越高。

4.JPG

3. 根据选定的 mip 测试遮挡。如果结果不明确,可以继续使用更细的 mip 级别进行测试。

这个选择的原因是它使成本可预测——最多需要读取和测试四个深度值。此外,这种测试可以被看作是“概率性的”,因为大对象比小对象更容易被看到,所以在这些情况下没有理由读取更多的深度值,即节省了带宽,也增加了 Cache 命中。

UE4 的实现

UE4 只在 PC 端进行了实现,过程大致一样,不同的是在构建层级 Z 缓冲上分为 ComputeShader 和 PixelShader 两种方式,然后最终剔除工作主要在 CPU 端进行,意味着需要回读 GPU 的测试结果。以下是 UE4 的工作流程:

使用 SceneDepth 作为数据源构建层级 Z 缓冲。Mip0 为第一级,大小为 1024*512,总共构建 10 级。分为两种方式,PixelShader 方式比较简单,一次构建一级,总共执行十次。ComputeShader 则稍微复杂一点,利用 GroupMemoryBarrierWithGroupSync,每次同时构建 4 级,总共只要执行 3 次,便完成构建。

场景中的物体经过视锥剔除以后,剩下的会被收集起来,存放在一个数组中,并且每个物体会保存自己在数组中的索引值。然后,创建 2 张 RGBA32 格式的贴图,一张存放物体包围盒的质心坐标,一张存放物体包围盒的大小。每次从数组中取 64 个物体作为一组,保存到贴图的 64 个像素区域中。

采样第二步中的贴图,获取物体的质心坐标和包围盒大小,可以计算出物体包围盒的八个顶点的世界位置,对这八个顶点进行投影,选取其中最近的 Z 值。根据投影后的矩形区域,选取最长边长计算 mipmap 等级,然后在矩形区域内采样该 mipmap 的 16 个像素,选取其中最远的 Z 值。如果包围盒最近的 Z 值比它还小,则物体不可见。将结果保存到一张格式为 RGBA8 的 RenderTarget 中,作为下一帧读取。

当前帧读取上一帧的贴图数据到一个数组中。每个物体将上一帧保存的数组索引到该数据进行查询,查询结果决定了该物体在当前帧的可见性。

以上步骤看出,UE4 的 HZB 实现流程没有完全放在 GPU 端执行,在下一帧的时候需要回读上一帧的结果,然后进行查询以决定物体当前帧的可见性。另外,在第三步中,计算最远 Z 值时,采样了矩形区域内 16 个像素,而不是 4 个像素。

移动端的实现

移动端的 HZB 方案大部分可以与 PC 端共用一套逻辑实现,然后针对移动端性能点进行优化。移动端需要解决的第一个首要问题就是 SceneDepth 的获取,因为它是构建层级 Z 缓冲的重要数据来源。

在移动端上,一般来说,如果存在后处理材质需要访问场景深度信息,UE4 会将场景线性深度值保存在 SceneColor 的 Alpha 通道。而对于透明材质,如果使用了 DepthFade 材质节点,则会通过移动设备扩展 API 来直接提取 FrameBuffer 中的深度信息。

那我们如果想在移动端上直接访问深度纹理的话,需要怎么做了?可以通过设置 r.Mobile.ForceDepthResolve 为 1 来始终保留移动端的深度信息。强制深度解析,为设备保留深度纹理。

移动端获取深度纹理

5.JPG

获取了深度纹理,就可以开始构建层级 Z 缓冲。考虑到移动设备的兼容性,这里只使用了 PixelShader 方式,依然构建了 10 级 mipmap。为了保证深度值的精度,这里将每个深度值编码到 rgba8888 格式的贴图中。

移动端构建层级 Z 缓冲

6.JPG

构建完层级 Z 缓冲以后,接下来就是进行遮挡测试。算法沿用了 PC 端的方式,将结果保存在贴图中,下一帧回读。移动端上回读 GPU 贴图数据,需要注意的是,UE4 会默认处理上下翻转。因为这里只是存放遮挡结果的数据贴图,所以不需要做上下翻转。

7.JPG

最后一步遮挡查询,过程和 PC 端一样,每个物体用上一帧的数组索引去查询自己当前帧的可见性。

优化读取性能

移动端回读 GPU 数据相当耗费性能,我们可以来看下 glReadPixels 分别在 oppo 手机型号为 r15 和 r17 上的测试结果:

r15 耗时 6~8ms

8.JPG

r17 耗时 16~20ms

9.JPG

直接调用 glReadPixels 相当慢,不过,好在目前大多数移动设备的 opengles 已经达到 3.0 以上,所以,可以考虑使用 PBO 的方式进行优化,使用 glMapBufferRang 进行读取。过程大致如下:

初始化 2 个 buffer。

buffer1 用于异步 glReadPixels 读取,buffer2 用于 glMapBufferRange 读取。

下一帧交换 buffer,buffer1 用于 glMapBufferRange,buffer2 用于 glReadPixels。

10.JPG

再来看下,优化后的测试结果:

优化后,r15 耗时 0.9ms

11.JPG

优化后,r17 耗时 4~6ms

12.JPG

硬件遮挡查询 和 HZB 在 oppo r15 上的性能对比

经过初步优化,我们来看下,硬件遮挡查询和 HZB 各自在手机上的性能对比。测试分为静态物体和动态物体。新建一个场景,场景内随机生成 10000 个物体。在分别只开启硬件查询和只开启 HZB 的情况下,对帧率和被遮挡物数量的影响。UE4 针对硬件查询做了 Batch 优化,这样可以大大降低硬件查询带来的 DC 开销,不过 Batch 只对静态物体有效。所以,需要分开测试。

动态物体:

如图,只开启了硬件查询,因为动态物体无法 Batch,Occlusion queries 相当高,达到 987,而 draw call 数量达到了 1450。被遮挡物体为 579,可见物体为 411。帧率只有 18。由此可见,对于大量动态物体,硬件查询本身带来了巨大的 DC 开销。

13.JPG

14.JPG

如图,只开启了 HZB,硬件查询的 DC 开销已经没有了,被遮挡物体为 570,可见物体为 420。Draw call 为 467,帧率为 30。

15.JPG

16.JPG

结论:对于大量动态物体查询的场景,从被遮挡物体数量和可见物体数量两个数据指标来看,硬件查询和 HZB 不相上下。性能上,HZB 优势明显。

静态物体:

如图,只开启硬件查询,因为静态物体的原因,硬件查询发挥了 Batch 的优势,Occlusion queries 只有 58,draw call 只有 511,帧率达到 35。

17.JPG

18.JPG

如图,只开启了 HZB,硬件查询 Batch 带来的 DC 也没有了,所以 draw call 略有下降。帧率为 33。

19.JPG

20.JPG

结论:对于大量静态物体查询的场景,HZB 仍适用,性能与硬件查询 Batch 相当。

硬件遮挡查询 和 HZB 在 oppo r17 上的性能对比

同样分别测试动态物体和静态物体,r17 和 r15 表现了完全的不一样的结果,之前在优化读取时也发现,r17 依然有 4~6ms 的开销,这是为什么了?这跟 r15 和 r17 在硬件上不同有关。

R15 硬件参数

21.JPG

R17 硬件参数

22.JPG

另外,值得一提的是,UE4 针对高通设备,做了硬件查询的上限限制。最大 510 次查询,而其他设备默认是最大 4000 次查询。

23.JPG

24.JPG

这导致了 R17 在大量动态物体场景下,即便没有做 Batch,也只有不超过 250 的 Occlusion queries 数量。这并不是什么优化,而是 UE4 直接放弃了超过该数量的硬件查询,再加上依然存在的回读耗时,这样使得 HZB 的优势就不那么明显了。

如图,R17 上,开启硬件查询,关闭 HZB。

25.JPG

如图,R17 上,关闭硬件查询,开启 HZB。

26.JPG

结论:基于以上原因,在大量动态物体和静态物体场景下,HZB 在 R17 上都表现不佳。

其他性能开销

移动端除了回读耗时以外,在 HZB 构建以及遮挡测试阶段,性能消耗也不能忽视,特别是采样 16 次贴图的操作。如下分别是在 R15 和 R17 上的测试结果:

R15 在构建和测试阶段分别耗时 2.6ms 和 1.6ms。回读耗时 0.9ms

27.JPG

R17 在构建和测试阶段分别耗时 0.48ms 和 0.41ms。回读耗时 5.2ms

28.JPG

通过对比发现,R15 回读快,而构建慢。R17 回读慢,而构建快。不同的硬件架构带来的性能差异很大,关于移动硬件分析已经不属于本文探讨范畴了,在这里就不展开讲了。

总结

关于 UE 硬件查询的 Batch 结论:

动态物体不会做 Batch,全部一个一个去查询,带来巨大的 DC 开销。

静态物体在被遮挡的情况下会做 batch 查询,DC 显著减少。

高通手机最大查询次数为 510,其他为 4000,而实际推荐最佳查询次数是 250,2000(分别除了 2)。

移动端 HZB 结论:

大量动态物体查询,HZB 适用于非高通移动设备上。

大量静态物体查询,HZB 仍适用于非高通移动设备上,性能与硬件查询 Batch 相当。

针对移动端未来可以做的优化方案:

将物体数据由质心坐标+包围盒范围改为质心坐标+包围球半径,可节省一张 RGBA32 贴图。

将 16 次采样改为采样包围球表面最近点和包围盒四个顶点,可减少 11 次采样。甚至更保守点,直接将包围球面最近点作为采样点进行比较。

高通设备可以考虑使用 vulkan 图形 API 进行数据回读。

高通设备在构建 HZB 的时候可以考虑使用 ComputeShader。

文/Youwei
来源:GWB-腾讯创意游戏合作计划

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

作品发布|文章投稿|广告合作|关于本站|游戏开发论坛 ( 闽ICP备17032699号-3 )

GMT+8, 2024-11-29 12:36

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表