UE4 Hi-Z遮挡剔除实现详细解析

Posted IChessChess

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了UE4 Hi-Z遮挡剔除实现详细解析相关的知识,希望对你有一定的参考价值。

Hi-Z RenderTarget的生成

Pixel Shader处理Hi-Z RenderTarget情况:

InvSize:输入上一级(高等级)HiZ图的MinMap大小,(比如输入是512的MinMap0,输出是256的MinMap图)。

InputViewportMaxBound:采样的贴图UV最大边界,min作用防止采样超出贴图界限。

获取到上一级Minmap的4个深度后将最小的那个深度返回。

这里为什么不是0.5而是0.25,看下图就能够一目了然了。

float4 DeviceZ = Gather4(ParentTextureMip, ParentTextureMipSampler, BufferUV);

float FurthestDeviceZ = min(min(DeviceZ.x, DeviceZ.y), min(DeviceZ.z, DeviceZ.w));

图一计算出4个像素的深度,然后将最小深度输出到下一级MinMap。

Primitive裁剪信息存到GPU

将Primitive裁剪包围盒写到两张RT上:

bAllowBoundTest:表示在玩家视锥有效范围内的PrimitiveOcclusion

LastTestFrameNumber:记录Primitive在哪一帧被加入计算裁剪

HZBTestIndex:在这里调用AddBounds将裁剪包围盒添加到队列中,并返回当前自己在队列中的Index。

队列是顺序队列,按照顺序依次加入

FHZBOcclusionTester::Submit这里将会把所有Primitive的裁剪包围盒分别写到两张RT上(Center和Extent)。

填充方式:

Size:整张贴图的大小,目前ue给的256*256。

Block:大小是8*8,下图中绿色方块。

Cpu上会把裁剪包围盒的信息以Block*Block作为一块进行存储,并依次从贴图左上角从左往右填充,满了会换行继续填充,直到所有裁剪包围盒被填充完为止。

PS上裁剪测试:

首先根据根据Center和Extent两个值算出包围盒8个顶点的位置,然后将每个点转换到屏幕空间中,在将它们中3个维度的最小值和最大值存储到RectMin和RecMax中,XY用于后续计算MinMap使用,Z用于裁剪测试。

·根据RectMin和RectMax构造出在屏幕空间的矩形,因为NDC下的点是-1~1之间,所以乘0.5+0.5转到0~1之间。

·Hi-Z图Size的XY存储的是Hi-Z图这张图的分辨率,ZW存储的是1/HZB这张图的分辨率,因此RectPixels算出矩形在Hi-Z图这张图上所在的位置。

·然后通过最大减最小算出两条边的长度RectSize。

·最后通过拿到最长的那条边做Log2计算算出要使用哪一集MinMap。

HZBUvFactor的XY是当前Pass输出的宽高大小与HZB图大小的比值,所以Scale是图与Pass输出的适配,除3没看明白是为了什么,我猜应该是测试得出不会出现裁剪错误的个值。

然后通过采取HZB图在当前像素中周围16个像素,并获取最小深度那个值,最后与RectMax的做对比,如果裁剪包围盒最大深度小于HZB的16个像素中的最小深度,那么被遮挡

在下一帧的头部会把数据进行回读到Resultsbuffer。

接下来就是遮挡查询,前面提到的在AddBound的时候把LastTestFrameNumber保存了,这里利用这个值测试该Primitive是否在上一帧有参加遮挡剔除计算,然后将Index传入查询。

IsVisble里的查询其实就是将Index根据上面将裁剪包围盒存储到图中的方式逆向得出索引来索引ResultBuffer,最终获得遮挡结果。

移动端实现:

移动端最大的问题是回读、和设备兼容性的问题,这里不讨论兼容问题。

由于UE4的RHI线程往往会滞后Render线程一个InitView(4.27版本),如下图,第2帧的RHI在第3帧的Render线程InitView FlushRHI前才完成所有DrawCall提交,如果没完成Render线程会等RHI提交完成,所以理论上会在下一帧前保证指令发送到GPU上,如果是回读指令,这帧未必能读回,因为GPU有可能没有执行完,然后就要再等一帧,所以可能要过2帧才能读回。

保守起见回读延迟3帧。

创建3个RWBuffer:创建可读写的Buffer。

创建3个GPUBufferReadback:用于将RWbuffer中的数据回读到CPU。

为什么需要创建UAV类型的RWBuffer,因为移动端不支持RT的回读,但通过Computer可以操作UAV的Buffer回读。

然后在FHZBOcclusionTester::Submit函数中根据平台分一下回读的方式。

·首先% 3求余获取需要读到哪个Buffer上

·然后将GPU裁剪测试输出的结果RT,通过Computer Shader输出到RWBuffer,由于当前数据还是在GPU,所以使用GPUBufferReadback回读到CPU,仔细跟踪到opengl底层会发现回读方式使用的是PBO(Pixel Buffer Object)。

PBO细节:OpenGL深入探索——像素缓冲区对象 (PBO)_ShaderJoy的博客-CSDN博客_gl_pixel_pack_buffer

将回读的结果拷贝出来到数组中,PC平台调用的是void FHZBOcclusionTester::MapResults,这里需要改成如下,根据Buffer的大小将裁剪结果数据读回到,Buffer的大小是贴图的内存大小。

这里需要注意的是这里数据的获取是在Render线程的下一帧,也就是结果是Render线程提交的。

竟然是上一帧的为什么这里是ValidFrameNumber – 2而不是减3,如下图,在开启HZB遮挡剔除的情况下,ValidFrameNumber的递增是在ReadBackResults/MapResults之后的。

移动端实现细节就这样。

问题:在Meta(ios)机器上,如果你需要GPU跑60fps,当帧率低于60fps的时候回读的数据会有错乱,目前还未找出原因。

Hi-Z遮挡剔除学习后的分享,如有什么错误欢迎在评论区提出。

以上是关于UE4 Hi-Z遮挡剔除实现详细解析的主要内容,如果未能解决你的问题,请参考以下文章

ue4 角色或物体被遮挡半透明渲染显示

ue4 角色或物体被遮挡半透明渲染显示

ue4 角色或物体被遮挡半透明渲染显示

剔除算法总结

(UE4 4.27) UHierarchicalInstancedStaticMesh(HISM)原理分析

ue4 网络的最佳实践