全局光照加速结构

Lumen学习笔记,有错误的地方欢迎指正

软件光线追踪

UE默认采用软件光线追踪,因为软件光线追踪可以无视平台和硬件的限制带来跟硬件光线追踪质量相近的结果,同时软件光线追踪采用的大量加速结构使得它在很多复杂的场景性能反而比硬件光线追踪高。

Ray Trace加速结构

Lumen里面软件光追用到的比较普遍的加速结构有三种,Hi-Z、MDF和GDF。

Hi-Z准确度最高,但却是三种加速结构里面最慢的一种,一般用于屏幕空间Ray Trace。

MDF准确度较高,速度中等,一般在ray即将到达object需要获取物体表面的Lighting Cache时使用。

GDF准确度较低,但拥有最快的tracing速度,GDF由多个低分辨率MDF通过一定计算得出的,GDF用于长距离的ray trace或者是远处物体(相对于屏幕)的ray trace。


UE加速方案如下

  1. 先用Screen Space Trace。基于Hi-Z走50步,如果能Trace到,直接获取结果。
  2. 如果Screen Space Trace失败,采用MDF,SDF Trace的距离为1.8米,Trace距离短,也就是只能Trace很近的物体,如果能Trace到返回Mesh ID,可以通过ID获取Surface Cache。
  3. 远处物体用GDF,如果需要高精度,先用GDF步进到一定距离再用MDF,如果精度低,直接用GDF一步到位获取结果。
  4. 如果GDF也失败了,从CubeMap采样。

UE一般混用这几种加速方案
image

Hi-Z(Hierarchical Z Buffer)

Hi-Z原理

Hi-Z是一种屏幕空间的算法,该算法记录一些列屏幕深度的MipMap,越下层的MipMap越精细,每个上层 MipMap里面的一个像素,都对应它下层Mip里面四个点的像素。
image

有了这种结构,我们可以仅仅通过判断上层粗糙结构与Ray有没有交点就可以直接决定要不要走下层结构的分支了。

Hi-Z能做什么?
SSGI屏幕空间加速求交

在做SSGI的过程中我们需要沿着Lobe的方向去发射Ray去获取第一个命中点的颜色,收集到这个颜色就可以计算这个颜色对我们要计算的Pixel的贡献了。
image
而这个过程如何从这么多三角形中找到第一个命中点呢?

最简单的方式就是固定步长步进,Ray每次步进一个固定长度,然后根据屏幕空间深度判断一下这时候的Ray在当前Pixel前还是后,如果Ray位置在Pixel后面说明已经打到该Pixel里某个三角形片段上了。
image

固定步长的方法虽然能够找到交点,但是速度太慢了,如果步长太小可能要走成千上万步,步长过大击穿太多物体可能会导致找不到第一个交点。

Hi-Z就是一种自适应步长的加速方案,在该方案中,不同级别的深度MipMap记录着该Pixel内所有物体的最浅深度,也就是离屏幕的最近距离,先用较粗糙的深度MipMap做光线求交,如果有交点则说明光线有可能和该Pixel内物体有交点,继续找更精细的MipMap,如果没有交点则代表Ray跟该Pixel内所有物体一定不相交。
image

该算法适应选择步长的过程有点像篮球里的试探步,一开始先从最低级的MipMap迈出一小步,发现没有打到交点
image
下一步就可以大胆一点了,向高一级粗糙一点的MipMap迈出一步,高层的MipMap的一步相当于底层MipMap的两个步,发现还是没有打到交点
image
下一步再大胆一点,在更高级的MipMap再走一步,最后发现和这一级的MipMap有交点了
image
发现已经有交点以后就不能这么鲁莽的迈一大步了,我们开始缩小步长,回退一级MipMap开始小步迈进
image
这时发现和这一级的MipMap仍然有交点,我们需要更小心了,再迈更小的步,如果已经是最底层的MipMap我们就保持最小步行进
image
最后的小布前进成功使我们找到了交点,这种步进的方式其实很类似于二分查找,最终以logn的复杂度远胜固定步长步进。

遮挡剔除

遮挡剔除同样是利用Hi-Z实现类似于二分加速的方案,由于视角的问题,其实场景里有很多物体是看不到的,所以我们可除掉这些看不到的Mesh的提交来减少Draw Call,这个过程就需要用到Hi-Z加速。
image
遮挡剔除的粒度是整个物体而不是像素,所以一般通过判断包围盒的深度来选择是否要进行剔除,而一个包围盒有的时候是很大的 ,比如我们近距离看一颗草的包围盒,下图草的包围盒覆盖了13*13个像素,我们总共需要判断169次屏幕深度和包围盒深度,哪怕有一次包围盒深度要小于等于屏幕空间深度(包围盒里可能有一小部分要比屏幕深度离相机更近),那就不能剔除掉这个物体,因为只要出现一次这种情况,那就说明这个物体有可能出现在屏幕内被我们看到。
image
可以用Hi-Z的方式去减少包围盒判断的次数,就比如生成一个更高级的MipMap里面存储低一级MipMap里面四个像素的离相机最远的深度,那么包围盒只要比最远深度还要远那就剔除掉,因为这种情况这个物体一定是不可见的,且我们只需要非常少的判断次数就能得到结果。
image
通常我们会一直找更高级别的MipMap直到该层级MipMap里面一个像素能完全包裹一个包围盒再做上面的判断,这样就最少一次就可以剔除掉物体了,但UE一般采用的是4×4的判断方式,因为极端情况下,如果包围盒中心恰好在屏幕正中心,而包围盒又占不满那一个Tixel,该包围盒就可能受到Tixel内其他位置的深度影响,本该被剔除却因为这种原因没被剔除,这就会导致遮挡剔除命中率降低。

image
最后需要考虑的就是实现上的问题了,因为要做剔除就需要知道深度,而深度要绘制了物体才能知道,这就好像是先有鸡还是先有蛋的问题。

为了解决这个问题,研究者们提出了很多Hi-Z遮挡剔除的方案,以下介绍几种

  1. 通过一个简单的CPU软光栅化得到一张简单的深度图用来指导Hi-Z做遮挡剔除,但CPU光栅化速度会比较慢。
  2. 利用前一帧的ZBuffer重投影后的信息再Compute Shader里指导Hi-Z做遮挡剔除, 这种方法存在两个问题,如果重投影个别像素在上一帧没有对应位置就会导致生成的ZBuffer存在漏洞,这导致高级别的深度MipMap几乎剔除不了任何物体,另一个问题会导致动态物体重投影区域产生延迟且快速移动物周围会产生物体突然出现的问题,因为动态物体这帧可见了但是按上一帧判断还是被剔除了。
  3. 《刺客信条大革命》提出了第3种方式的优化方案,采用Two Pass的方式,第一个Pass对大概300个occluders做光栅化来获取深度补全上一帧重投影导致的漏洞,通过补全后用上一帧重投影的ZBuffer和这一帧绘制的occluder生成一个尽量贴近当前帧实际情况的zbuffer指导Hi-Z做遮挡剔除,留下的物体存进容器A,被剔除的物体存进容器B,这样可以得到大概率不可被剔除的物体,第一个Pass是非保守Pass,好处是可以根据不是完全准确的ZBuffer得到部分需要保留的物体。第二个Pass绘制上一个Pass得到的A内的物体,然后用算出的ZBuffer信息再对B内的物体做遮挡剔除,第二个Pass目的是采用保守的方式找回第一个Pass错误剔除的物体。
  4. UE内不依靠硬件的Hi-Z遮挡剔除通过异步加载上一帧的ZBuffer+爆算部分这一帧的ZBuffer,通过这种方式算出来的ZBuffer仍需要回读到CPU里做剔除然后上交Draw Call,这种方式没办法使用GPU-Driven的原因是因为由于材质和物体信息没办法解耦,Texture需要每个Draw Call重新绑定,而通常一个场景有成千上百个Material。
  5. UE可通过硬件特性做粒度是像素级的Hardware occlusion query,这种方式会上交一种特殊的Draw Call,这种Draw Call只通过depth test阶段且不会进入PS和blending,这种方法仍然需要回读,但是有一个好处就是不需要根据Material切换PSO,只需要在开始提交occlusion query前设置好一次PSO就行了,这样提交Draw Call速度会非常快。
  6. 用Deferred Texture管线,这种管线,因为上传的是Visibility Buffer所以不需要考虑材质问题导致每个Draw Call更新材质,Draw Call的参数被存储在Buffer中,GPU可以在完成剔除操作后,自行决定要绘制哪些物体,而无需将数据回传给CPU重新组织Draw Call,这样就可以用GPU-Driven来做遮挡剔除。

这边主要介绍一下Nanite里的Hi-Z遮挡剔除

UE里针对Nanite 遮挡剔除采用的是类似于《刺客信条大革命》的方式,但它不是用上一帧投影到这一帧,而是把这一帧的物体重投影到上一帧,利用上一帧的Hi-Z做遮挡剔除,这种方法不会出现Hi-Z的漏洞,只会出现部分错误的剔除,第二个Pass再用这张ZBuffer做二次遮挡剔除。
image
遮挡剔除要遵循的原则是保守性剔除,宁可放过,不可杀错。

SDF(Signed Distance Field)

SDF原理

用SDF做Ray Tracing可以理解为用SDF的值去当做步长来做Ray Marching,形象一点理解就是下图的Sphere tracing,SDF的值代表着这个点到最近Mesh的距离,也就是以这个距离为半径的圆的范围内一定没有物体,以SDF的值去步进可以快速的跳过大范围的空白区域。
image
哪怕在接近表面的时候因为某些插值过后的SDF导致Ray穿进了物体内部,因为SDF的有符号性,内部的负数也会让Ray回退回去,所以SDF是一种安全且快速的步进方法。

MDF和GDF(Mesh&Signed Global Distance Field)

Lumen混合使用了SDFGI,也就是基于MDF和SDF的Marching,混合的SDFGI对大世界复杂场景有着更高的效率和兼容性,因为在大世界物体非常多,我们不可能对每个MDF都遍历一遍,所以做Ray Marching必须要场景管理,通常的BVH或者是八叉树在GPU中可以起到加速的作用,但往往会因为树的深度不一或者是BVH和八叉树的不规则性导致负载平衡的问题,也就是某些线程处理的任务较多但其他线程处于空闲的状态,这会导致GPU并行性较差。

Lumen采取了MDF和GDF混合的巧妙方法来解决GPU并行性问题,在离相机非常近的物体采用MDF来做Ray Marchintg,由于相机附近场景范围比较小,所以采用了均匀网格这种对GPU并行友好的方式管理MDF。如果MDF没有Hit物体,则使用GDF来对远处的物体进行Ray Marching,GDF是由MDF合成的全局的SDF,不需要遍历速度非常快,而远处物体对近处Pixel的影响其实不会特别大,所以精度也不需要很高,MDF和GDF结合的办法大大加速了Ray Marching的速度。

SDF能做什么?
SDF加速ray求交

每次步进SDF的距离,可以以log2的代价对光线快速求交,同样SDF也能避免左图固定步长带来的Ray穿过薄物体的问题。
image

快速模拟软阴影

我们可以通过SDF过程中产生的最小角度来模拟光照的最大通量,如下图黄圈,黄圈p3的角度最小,可以近似理解为光能通过这条光路照到o点的覆盖角度只有θ3的度数。
image

无损放大渲染字体

由于贴图的分辨率是有限的,所以我们在非常近距离观察贴图的时候往往会出现锯齿现象,通常情况下物体渲染我们往往采用MipMap的方式来解决这一种问题,直接为近处的物体加载高分辨率的贴图,远处的物体加载低分辨率的贴图。

在这里插入图片描述

但是在字体渲染中,我们希望字体无论远近都需要能够清晰可见,如果用MipMap的方式会导致远处的字体变得非常模糊,所以这里引入SDF的方式,当我们需用用64×64的texture去渲染640×640分辨率的图像时,通常会通过在两个点之间插值出18个采样点来,直接插值颜色信息是错误的,因为颜色信息是离散的,插值注定只能得到模糊的结果,如下图b所示。

在这里插入图片描述

但是文字渲染要求的是c中锐利且平滑的结果,所以就不能通过对颜色的插值,因为SDF连续且可导,因此它具有插值正确性。我们利用这个特性插值出来高分辨率的SDF是准确可信的,也就是通过检测对应Pixel的SDF值是否为0.5来判断边界可以得到上图c中的连续且平滑的结果。

图像平滑过渡

还是因为SDF的插值正确性,因此在插值两张毫不相干的贴图的时候能有很平滑的结果,具体来说就是这两张贴图之间互相转换的过程是十分平滑且流畅的。

原图

在这里插入图片描述

插值过程

在这里插入图片描述

正是由于这个特性,很多卡通渲染的阴影和高光都可以通过这个方式去平滑过渡。

MDF(Mesh Distance Fields)
MDF原理

UE里面每导入一个Mesh都生成对应的个Mesh Distance Fields,MDF代表对应点到Mesh表面最短的距离,通过这个距离我们可以以log2的代价快速对ray求交。

MDF一般在Mesh Card导入的时候就预计算生成了,因为MDF一般是相对于Mesh本身,而Mesh基本又不会形变,所以MDF一般都不变的。
image
生成MDF的时候需要一定的偏移修正,不然会丢失精度
image
计算SDF选择的位置都是一系列离散的点,如果有些物体太薄了,SDF的间距都要比物体厚,这样生成的SDF就永远不会为0或者是负数(这是因为两个SDF采样点之间的SDF值是插值得来的,两个正数插值还是正数,如上图红点插值后还是5),这时如果用SDF做Ray Marching,在接近物体表面的时候由于找到的所有点都是正数,所以Ray一定会击穿物体,所以这就会导致漏光,同时通过梯度计算的法线也会出错。

如何生成MDF?

因为SDF显存占用过高,所以对SDF进行稀疏化处理,在生成MDF的时候只生成很薄的一层SDF,这样可以节省存储量。
image

MDF LOD

SDF可以做LOD,同时LOD是空间上连续可导的,可以用LOD反向求梯度,也就是说我们用了一个 Uniform 的表达,可以表达出一个无限精度的一个 Mesh ,我既能得到它的面积,又能对它进行快速的求交运算,还能够迅速的求出它连续的这个法线方向。
image
LOD和稀疏SDF可以节省40%到60%的空间,对于远处的物体我们可以用Low SDF,近处的物体再用High SDF,这样可以很好的控制内存消耗。

为了节省内存和传输的带宽,一般不同LOD的Mesh SDF都会存在同一张Page Atlas里面,这样紧凑的布局可以最大程度的提高效率
image
Mesh SDF的数据通过一个简单的线性分配器来管理,所有的数据都存储在一个固定大小的池子里,且并不是所有的Mesh SDF的数据都传入显存中,一般情形下,只加载200米以内的Mesh SDF数据,这些数据通过流式加载进GPU,每次只更新需要的Mesh SDF,不需要的就删除掉,这样所有需要用到的SDF数据紧凑的排布在内存池内,可以避免数据碎片化带来的内存浪费。

GDF(Global Distance Fields)
为什么需要GDF?

如果只用给MDF去做Ray Trace速度会很快,精确度也很高,但是如果Mesh特别多的时候,因为MDF只有薄薄一层的SDF,所以远处的ray需要对每个Mesh生成一个SDF再判断哪个SDF最小再使用,这样就需要遍历很多Mesh的MDF,非常耗费性能,所以提出了GDF的概念。
image
详细来说其实是GDF和MDF结合的方式,上面的场景每个Pixel都有非常多的网格,这种情况用BVH或者八叉树能够提升Ray Marching效率,但尽管如此,同一个包围盒里面还是会有非常多的物体,包围盒遍历的物体数量不同带来的负载平和和树的深度不一致也会打断GPU的并行,所以UE采用了近处的物体的Mesh SDF直接用均匀网格的方式进行划分,如果没找到近处的Mesh SDF,则采用GDF进行远距离的步进。

GDF原理

GDF是整个场景的SDF,通常GDF的精度都比较低,低精度的GDF是通过场景中每个MDF共同合成的。

GDF可视化
image
MDF可视化
image

因为GDF代表的是远处没那么高精度的场景,所以我们可以采用Low LOD的MDF去合成,完整的MDF可以用于整个场景的SDF求交加速,比较常用的方法是先用GDF快速Trace一大段距离,再用MDF去做精细的求交。

GDF一般需要实时生成,因为GDF相对于世界空间,而世界空间里面物体随时可能变换位置,所以GDF需要实时生成。

GDF的更新

image
把场景中所有的Mesh SDF合并成GDF是非常消耗时间的过程,如果对每个MDF都遍历一次,这是承受不起的,UE采用了动静态物体更新和时间切片更新来减少GDF的不必要的更新。

  • 动静态物体更新策略,只更新自上一帧来发生变化的部分

要注意的一点是,GDF采用的ClipMap存储的策略,所以更新策略是基于世界空间而不是屏幕空间的,也就是转动屏幕并不会导致GDF的更新,只有物体动起来或者世界场景真正变化的时候才会更新。

image
动静态物体更新就是只更新发生改变的部分,具体的做法是把物体分为静态和动态两种,静态物体默认不更新,只需要更新被动态物体影响部分的GDF就行了。UE采用了分Brick的策略,这里的Bricks其实可以理解为ClipMap里面对应的世界空间的方形区域,当物体移动的时候,标记这块区域,由此得到需要修改的Brick列表,最后对所有在列表里的Brick进行更新。

找到了要更新的Brick,只需要遍历这个Brick里面的对象,然后计算空间中的点到多个对象的距离最小值就可以得到结果了,注意这里并不是对空间中所有点进行最小距离距离计算,而是采用窄带距离场(Narrow Band Distance Field)的方式只对物体表面很薄一层空间进行SDF计算。

在更新了每个Brick里面的GDF以后,UE采用了更粗糙的MipMap的方式来标记一整个区域的Brick内的物体,这主要用于后续的Ray March加速。

  • 时间切片的方式来减少远处较小对象的更新频率,为每个ClipMap设置单独的LOD来减少远处物体的GDF更新计算量

UE采用时间切片的方式,对于最精细的ClipMap(离相机最近的)每帧都更新GDF,对于远处的ClipMap采取多帧才更新一次的策略来减少更新次数。

同时每个ClipMap也设置了单独的LOD,越远的场景越粗糙,这样相当丢弃了远处较小对象的实例,越远的ClipMap需要合并的实例就越少。

Voxel Tracing

Cone Tracing

通常要算一个Pixel的Irradiance一般都是朝着BRDF对应的Lobe发射多根射线采样得到结果,这意味着每个Pixel都得发射非常多的射线,Cone Tracing就是用来解决这个问题的。对于间接光这种不需要非常准确的信息,本身就可以用Voxel表达,Cone Tracing十分聪明的把朝着Lobe发射多跟射线采样的问题转变成了朝着Cone中心只发射一根射线采不同大小的Voxel的问题,也就是Cone Tracing只需要采样一次。

朝着Lobe发射多根射线采样

在这里插入图片描述

朝Cone方向发射一根射线采样

在这里插入图片描述

Cone Tracing基于建立在Clip Map上的Voxel,越远的Voxel也就越大,这样只需要根据距离采样不同层级ClipMap上的Voxel就可以只用1根射线得到类似于在Lobe内采样多次的结果

ClipMap

在这里插入图片描述

ClipMap Ray Marching步长却决于Cone的Ray Marching长度和张角

下面引用KillerAery文章(实时光线追踪(3)Ray Casting - KillerAery - 博客园 (cnblogs.com)

在这里插入图片描述

HDDA(Hierarchical Digital Differential Analyzer)

HDDA可以理解为体素版的BVH+DDA,其核心思想是场景体素化后把场景体素进行层级划分,用类似于MipMap的处理方式处理Voxel。

在这里插入图片描述

Ray Marching的时候会先判断起始点在Voxel 3D Texture的位置,先遍历高层级的3D Voxel Texture,如果没有交点则直接跳过,如果由交点则继续遍历更低层级知道找到最小的Voxel命中点为止。

如果每次射线走的距离小于或等于Voxel的间距,则会出现多次步进仍处于同一个Voxel的情况,这就会导致步进浪费,如果每次走的距离要大于Voxel的间距,会出现跳过最终命中Voxel的错误情况

一次步进没能走出同一个Voxel

在这里插入图片描述

步进距离超出一个Voxel的间距,跳过命中Voxel

在这里插入图片描述

为了防止多次步进射线仍处于同一个Voxel里,这里使用DDA来确保每次Ray Marching一定能走出当前层级的Voxel。

先把Ray归一化后投影到XYZ轴,在哪个轴上的值最大则选为主轴,此时主轴的投影长度与单位长度的比值则为步进距离的放大长度,Voxel间距乘放大长度可以得出这次步进需要的长度,按这个长度步进可以保证每次Ray Marching必定能穿过当前的Voxel

在这里插入图片描述

ROMA(Ray-aligned Occupancy Map Array)

ROMA是一种用Voxel来加快物体步进的方式,任意物体都可以用Voxel来表达,该方法在物体体素化同时对每个Voxel面方向生成一张BitMap,如下图,如果光线和Z轴平行(光线和下右图对应的Voxel面垂直的情况),那只需要根据BitMap上0和1的值来判断该光线会不会和物体相交。

在这里插入图片描述

在最理想的情况下,Ray和Z轴同方向,这时候从BitMap中读取一位则相当于做了9次DDA步进,如果Ray和Z轴方向不一致则需要读取多位且按位或来判断相交,这显然违背了ROMA的初衷

在这里插入图片描述

解决方法是只光栅化一次Voxel,用CS来旋转生成新的BitMap,生成的OM(Occupancy Map)越多所能匹配的光线方向也就越多,但是预计算和存储也会带来性能影响,这里可以在性能和质量之间进行权衡。

在这里插入图片描述

Ray Intersection加速结构

上面介绍的Ray Trace加速结构主要用于加速Ray的步进,但Ray不仅需要步进的快,还需要能够快速求交以及获取对应的材质信息,因此我们需要加速光线求交得到Hit Point的过程。

Hi-Z的Ray Intersection

Hi-Z的Ray Intersection其实可以在Ray Trace一步到位,因为Hi-Z是完全基于屏幕空间的,而屏幕空间的物体信息也就是GBuffer几乎是免费的,所以完全可以在Ray Trace的过程直接通过光线方程获取到Hit Point,再由此算出UV就可以直接获取一整套需要用于后续计算的材质信息了。

SDF的Ray Intersection

Mesh Card求交

由于每个模型都会有自己的Mesh SDF,当Ray发射的时候其实是不知道要用哪个物体的Mesh SDF的,这个时候需要遍历多个物体的Mesh SDF,比较得到一个最小的SDF值来作为步进长度。同样SDF往往只存储距离信息,我们没办法直接得到Hit Point的材质和颜色信息,需要去遍历物体来求交才行。

上文说到更新Brick的时候还会用MipMap来标记一整个区域的Brick内的物体,因此我们就不需要额外计算BVH了,可以在更新GDF的过程中一并得到物体属于的区域划分,这里用类似于BVH的思想,光线打中了MipMap区域(MipMap标记的世界空间网格)就才会对该MipMap区域内的物体进行遍历。

上面的方法成功适用于GDF的远场追踪,远场追踪可以快速得到一个粗糙的可能和Ray相交的物体列表来辅助Ray Intersection,这时候需要更精细的结构划分来减少Ray求交所需要遍历的物体。

UE采用Froxel(代表视锥体空间内的一小块体积)的方式来进行精细化划分物体。

  1. 首先先把视锥体外的物体都剔除,标记视锥体内所有包含几何体的Froxel,这样我们就不会在追踪过程中浪费时间在永远不会被使用的Froxel上,注意这里只是一个粗糙的边界测试。
  2. 接着对每个Froxel内物体进行一次精确的距离场采样得到一些列可能和Ray相交的物体,最后把列表压缩成一个连续的对象数组。

整体流程就是,先判断Ray命中了上述MipMap中的哪个区域,再判断Ray命中了哪个有效的Froxel,最后从命中的Froxel中拿到对应的物体列表,遍历所有列表里的物体对他们做Ray Intersection就可以得到Hit Point了,同时加载对应物体的Mesh Card或者其他存储结构可以得到对应的材质信息。

Voxel求交

Voxel一般是由3D Texture存储,通常来说对于远距离的光照信息用GDF直接采样Voxel,由于Global SDF的唯一性,这时候就不需要再遍历多个物体获取SDF值了,直接通过Global SDF一步到位找到空间中的Hit Point,再变换空间位置找到该点再3D Texutre中的索引就可以直接采样Voxel的信息了。

硬件光线追踪

UE要启用硬件光追条件限制比较多

  • 要NVDIA20系及以上的显卡才能启用硬件光追
  • 加速结构每帧都得更新,因此实例数量不能超过十万个,不然性能支撑不起

硬件光追的优点

  • 支持所有的几何类型(如skinned mesh)
  • 对三角形直接求交相比于粗糙的距离场有着更高的质量

硬件光线追踪的Ray Trace [TODO]

硬件光线追踪的具体流程如下

在这里插入图片描述

硬件光线追踪的Ray Intersection

硬件光追利用了硬件并行计算能力强的特性,通过**硬件单元( RT Core)**指定算法来对大批量数据并行计算。

RTX 2080 Ti [在标准测试模型上每秒可以计算超过 100 亿个最近命中的光线网格交叉点。

由于算法固定,所以不能像软件光追一样用各种Trick来加速,同样的固定算法直接对三角形求交,如此暴力的算法带来的是高质量的结果。

顶层加速结构(Top Level Acceleration Structure)&底层加速结构(BLAS)

TLAS&BLAS原理
  • 顶层加速结构(Top Level Acceleration Structure)

顶层加速结构一般对应着场景,其叶子节点为Instance

  • 底层加速结构(Bottom Level Acceleration Structure)

底层加速结构对应着物体实例,其叶子节点为三角形

UE硬件光线追踪采用顶层加速结构来加速Ray Intersection

顶层加速结构类似于一个大型的BVH,不过BVH里节点指向的是底层的加速结构(BLAS),底层加速结构其实就是由多个三角形或者几何表达组成的实例。
image

同时底层加速结构还会包含一些旋转或者平移的矩阵,这样多个不同的实例就可以通过一个实例矩阵变换而来,这样减少了更新的成本。

但有一点需要注意的是,如果光线追踪的几何体是根据材料而不是空间局部性来分组的,那么性能会受到很大的影响。

image

TLAS主要思想就是把大量Mesh组装成实例再求交,对实例求交而不是对三角形求交可以大大加速整个过程。

TLAS加速结构的开销与实例数量成正比,一般限制在10万个实例以内,如果场景太大,就得提前设置Nanite’s Proxy Triangle Percent来生成Proxy Mesh再做硬件光追。

数据存储

硬件可以通过上面的加速结构快速的算出Ray和三角形的交点,但是这个交点的材质信息和需要用什么Shader去渲染其实是不知道的,因为命中的物体可能各式各样所以并不能用通用Shader和材质去hack。

为了拿到Hit Point的Material和Shader信息,加速结构一般会存在Shader Table这种多级结构,Shader Table里存的是Shader Record,这里面对应着Hit Group数据,而光线追踪管线里的最后一部Closest Hit Shader就可以通过Hit Gruop里预先存的可能出现的Shader和参数去找到对应的Shader,通过TextureArray或者Bindless的方式拿到材质贴图数据。
在这里插入图片描述

硬件光追与软件光追的对比

一般情况下,硬件光追质量好但速度慢,软件光追速度快但质量没那么好
image
如果项目需要绝对的最高质量,比如建筑可视化,那么应该使用硬件光线追踪,如果项目需要镜面反射,或者需要蒙皮网格以显著的方式影响间接照明,那么也应该使用硬件光线追踪。
image
但很多情况下,由于硬件光追依赖于类BVH划分的加速结构,当场景里每个点都有非常多重叠的网格的时候,不管怎么划分都需要遍历非常多次,效率远远不如十几次步进就能得到结果的SDF高。
image
但在部分没有那么多几何重叠的场景,硬件光追的表现很不错,和软件光追速度差不多却能带来更好的效果,这时用什么取决于硬件。
image

参考文章

最强分析 |一文理解Lumen及全局光照的实现机制
实时阴影技术(2)Shadow Ray & Shadow Volume
【虚幻5】Lumen技术细节
UE5 Lumen系统浅析
UE5 Lumen实现分析
Real-time Global Illumination in Unreal Engine 5
【Unity】使用Compute Shader实现Hi-z遮挡剔除(Occlusion Culling)
大世界技术浅析——超远视距与剔除(3)
Hierarchical-Z map based occlusion culling
理解Nanite(一):遮挡剔除

  • 13
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值