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遮挡剔除实现详细解析的主要内容,如果未能解决你的问题,请参考以下文章