实时光追(Real-time Ray Tracing)往往是综合了 sampling、ray casting、denoising 等各方面的方案,本文主要记录的是 ray casting 这部分,但是术语可能更多仍然称为 ray tracing。
硬件光追(Hardware Ray Tracing)
ray tracing 最经典的实现莫过于 ray traversal:投射一条 ray 然后遍历所有 triangles 去做 ray-triangle 相交测试,并得到对应的 hit point。但是场景中的 triangles 数量往往数量庞大,ray traversal 也变得非常耗时,而后为了加速相交测试,便引入了加速结构(BVH,八叉树,k-d树等),来剪枝掉一些不可能发生相交的 triangles;但即便如此,软件实现 ray traversal 仍然是件耗时的事。
再后来,Nvidia 推出了 RTX 系列显卡,开始提供硬件单元来大大加速 ray traversal 这一过程,使得 real-time ray tracing 成为了可能。从此之后,GPU 厂商都逐渐开始普及硬件光追的支持。
加速结构(Acceleration Structure,AS)
硬件光追提供了两大层级的 AS,我们可以对这两大层级(BLAS 和 TLAS)进行配置,但不用关心 AS 具体采用哪种空间加速结构或者具体实现。
注:我们在 GPU 里通过 ray traversal 得到 hit point 后,要获取 hit point 的材质属性是非常困难的,因为材质函数和参数等都是在 CPU 端的,而粗暴的直接根据 geometry id 和 material id 去 CPU 拉取对应的 shader 和参数无疑是不可行的(通信时间过长)。
为了解决这个问题,才有了 shader table 的机制:GPU 里的 shader table 预先存储着一堆 shader records,每个 record 包含一个或多个 shaders(其实就是 hit group)以及参数;再得到 hit point 后,根据 geometry id 和 material id 去索引到相对应的 shader record,并直接执行对应的 shader,无需通知 CPU。
any hit shader(AHS):当 hit point 命中的是半透明 triangle 时,来决定是否 ignore 掉本次 hit point。通常用于累积 hit point 的 alpha 值,如果累积 alpha 值较低则选择 ignore ,否则终止 tracing 提交本次 hit point。
closest hit shader(CHS):负责对生成出来的最终 hit point 计算着色结果。通常用于计算 hit point 的 radiance 或 material 等。
miss shader(MS):如果没有生成最终的 hit point,则会运行 MS。通常用于计算天空盒环境光照,或者 visibility ray 中的 shadow factor 等。
Ray Query
ray query 则相当于一个各个 pipeline 都能调用的查询接口(在 CS,PS 乃至于各种 hit shader 等都能调用),而不是像 ray tracing pipeline 那样独立的一套管线。
通过调用 TraceRayInline() 接口来使用 ray query 硬件光追:同样对 AS 进行 ray traversal,但与 ray tracing pipeline 不同的是,ray query 只会提交 intersection 的信息(position,primitive id,instance id 等),并不会调用任何其它 shader。
ray query 明显就是 ray tracing pipeline 的阉割版,或者也可以说是 ray tracing pipeline 的子部件,基本只负责了 ray-traversal 的操作。而 ray tracing pipeline 不仅做了 ray-traversal 的操作,还能自动调用合适的 shader 对 hit point 进行着色(一般是用于计算材质)。
软件光追(Software Ray Tracing)
Screen Space Ray Tracing
SSRT 可能是大家都比较熟悉的软件光追方式,它主要就是将屏幕所看到的表面几何信息当成一个场景,利用屏幕的 depth texture 来做 ray marching:每次 marching 一步后就可以检测当前点的 z 值与在 depth texture 中对应的 depth 哪个更小,如果 depth 更小则说明 marching 碰到了屏幕中的 pixel,就可以访问对应的屏幕信息作为 hit point 的属性。
显而易见的问题是:如果 marching 最终走出了屏幕外,那么 SSRT 将无能为力去处理(因为只记录了屏幕上的信息)。并且 SSRT 的命中判断是保守的:即 SSRT 产生 hit 了则 ground truth ray tracing 必定会产生 hit,但 SSRT miss 了但 ground truth ray tracing 不一定会 miss。
ray tracing pipeline 算是最理想的 ray casting 方法,它能获得 hit point 的材质,并且得益于硬件加速,其性能也并不亚于软件光追,因此基于 ray tracing pipeline 往往就可以设计出一套相当理想的实时光追算法。然而在硬件不支持或部分支持(仅支持 ray query)的大部分平台,如移动端,nintendo switch等,无论是 ray query 还是软件光追都不能直接获得 hit point 材质,因此往往还需要设计一套低精度的 material cache。
下面的实践主要是围绕 ray query 和软件光追来介绍,基本不介绍 ray tracing pipeline(因为它可以直接简单粗暴的做好)。
Primary Ray
可以通过传统的光栅化流程去做,而不是真的往屏幕投射 primary ray。
Shadow Ray & Visibility Ray
我们在常常需要计算某些着色点受到的直接光照,就往往需要从该点往光源投射 shadow ray 来检测是否被遮挡,我们可以通过一套类似流水线的流程去处理 shadow ray:
针对屏幕范围内的着色点,可以尝试进行 screen space ray tracing,若命中则视为被遮挡。
如果场景含有 height field,可以尝试进行 height field ray tracing,若命中则视为被遮挡。
若以上流程均未命中,则使用离屏的光追方案来做最后的遮挡测试(命中为被遮挡,不命中为不被遮挡):
其它软件光追。
硬件光追:ray query。
半透明阴影 - ray query
当阴影需要考虑半透明物体(例如树叶)时,意味着我们的 shadow ray tracing 不能只考虑是否被遮挡,还得考虑遮挡物体的 opacity,并且这一次 ray tracing 还可能将发生多次 intersection。
当我们通过 ray query 去做半透明阴影时(需要设置 RayFlags 为 NoOpaque),每当产生 intersection 时,需要中断返回到我们自定义的程序,该程序需要计算出交点 uv 坐标采样 alpha mask texture,并累积 alpha 值,若低于 opacity 阈值就继续进行 ray tracing。
一个优化方式是:我们可以进行多次非透明的 ray query(需要设置 RayFlags 为 Opaque):每次 ray query 产生 intersection 就 commit,然后做 alpha 相关测试(和上面一样),如果还需要继续 ray tracing,那就更新发射点位置为 intersection 的位置并进行新一次的 ray query。
这个优化方式的性能往往能提升2倍甚至更多的性能,一方面是 RayFlags 设置成 Opaque 后 ray query 的流程会简化很多,另一方面是因为 ray query 是特定的硬件流程,中断返回会导致额外的 latency,因此尽量让 ray query always commit。
Material Ray
而除了计算直接光照以外,我们还往往需要计算着色点受到的间接光照,这时候往往需要投射 material ray,获取 hit point 的材质信息以计算 radiance:
对于硬件光追 ray tracing pipeline 来说,material ray 是容易实现的:直接通过 closest hit shader 来计算出 hit point 的材质信息,并之后通过 payload 来获取材质信息即可。
若着色点位于屏幕范围内,可以优先尝试 screen space ray tracing,若命中则可以获取屏幕上的 normal, albedo, color 等材质信息。
若着色点不在屏幕范围内亦或者 SSRT 没命中,则可以使用 visibility ray + material cache:由于缺少 closest hit shader,我们只能通过投射 visibility ray 得到一个 hit point 的 position,而得不到几何信息或者材质信息。但是我们可以借助 material cache(例如:lumen 里的 mesh card;voxels 等)来获取 hit point 的材质信息。
注:几何信息有部分还是可以强行获得的,如通过 SDF 梯度算 normal ,或者 triangle id 获取 3 个 vertex 从而算出 face normal 等。