Vulkan ray tracing 与 rasterization 管线对比

Yuri
发布于

在之前关于体积云的文章《软光栅下体积云理论与实现》中,提到了CS2的可交互体积云;

Counter-Strike 2的可交互烟雾

这个效果如果想移植到PIccolo引擎中,需要把原来的Vulkan rasterization模式的渲染管线修改为ray tracing模式。这个任务需要分为三步:

  1. 构建出Vulkan ray tracing的管线
  2. 在引擎结构中做pipeline的替换
  3. 实现volume rendering的组件

首先就来聊一下在Vulkan中ray tracing pipeline和 rasterization pipeline 的区别,为后续在引擎中做替换做准备。Let’s go.

流程对比

假设使用rasterization的手段来渲染一个模型,一般都有这样的流程:

rasterization pipeline

对于ray tracing的流程,很多部分也是相同的,区别主要集中在三个部分

  1. 实例配置中增加对于raytracing扩展的配置
  2. pipeline的配置和加速结构的构建
  3. Shader binding table 处理

Vulkan 应用起手都会涉及到可交互的window以及Vulkan API、逻辑设备的实例化,对于rasterization pipeline 和ray tracing pipeline都是相同的不再赘述。它们的区别主要从application的配置开始。

Setup application

  1. setup extension

对于渲染任务来说,涉及到一般会涉及到instance 和 device,这两类extension需要分别进行配置。
例如,常用的swapchain相关涉及展示的扩展就是device extension。

对于ray tracingpipeline 则需要在rasterization的基础上增加一些Device extension。
例如:对于ray tracing pipeline所需要的feature:VK_KHR_RAY_TRACING_PIPELINE_EXTENSION_NAME
还有对于构建加速结构所需要的VK_KHR_ACCELERATION_STRUCTURE_EXTENSION_NAME

  1. Setup swapchain

首先创建成对的surface和swapchain以展示图形。还需要配置surface format和深度缓冲所用到的depth format。
此部分对于ray tracing和rasterization都是相同的。不少和成像逻辑无关的整体配置两者都没有区别。

  1. Setup depth buffer

创建depth image的vkImage、以及对应处理内存分配的vkDeviceMemory,同时为了让程序能够访问vkImage还要绑定 Imageview。
Ray tracing 天生自带depth test,自然不需要depth texture。

  1. Setup render pass、framebuffer

初始化 render pass与两个 attachments:color、depth,不涉及g-buffer相关的程序一般颜色和深度两个attachment就足够了,同时根据swapchain的数量配置对应的framebuffer。
不过Ray tracing管线不涉及renderpass这个部分。

  1. 模型载入

载入的模型数据一般会包含vertex 和index相关的数据,对于rasterization pipeline会把这部分数据作为VkBuffer进行处理,最后作为descriptor set传入shader中参与运算。
而对于ray tracing的管线,需要先将vertex数据构建了acceleration structures,之后同样也需要作为descriptor set参与shader中的计算。

Setup pipeline

pipeline开始两种成像方式开始出现区分,还是以rasterization为基础进行对比

  1. Setup render target

对于渲染的target,首先肯定得有一个承载图形的容易,所以需要为color imageview 和 depth imageview 配置 image layout。之后为offscreen render 准备对应的renderpass和framebuffer作为渲染的target。
raytracing并不涉及offscreen renderpass的部分。

  1. Setup pipeline

Ray tracing和rt分别有自己的pipeline。pipeline一般包括三个部分:shader stage、各类资源的layout、 fix-function。
而Ray tracing 由于有多个shader的协作涉及到更加复杂的shader stage、以及shader binding table的绘制,会在ray tracing pipline的概念部分展开。

  1. 配置uniform、descriptor set 等资源

对于通用的camera信息、模型顶点信息等,ray tracing pipeline 和rasterization pipeline两者是相同的。

Render loop

rasterization的方式通过在commandbuffer中提交关于vertex、index的信息,最后通过"vkCmdDrawIndexed" 确定绘制的图元,最终由vkQueuePresentKHR 进行展示。
对于ray tracing,则不会涉及具体的vertex信息,因为已经在加速结构中进行处理,不需要直接暴露在render
loop的过程中,同时在descriptor set中已经提供了足够的信息,则采用“vkCmdTraceRaysKHR”的方法进行绘制。

对于渲染完成后的cleanup两者大同小异,这里不再赘述。所以抽象下来,ray tracing pipline的流程可以抽象为:

Ray tracing pipeline

raytracing引入的新概念

Acceleration Structures

加速结构(AS)的目的是提高ray tracing的计算效率,我们可以将其与soft ray tracing render(就是CPU端实现的ray tracing)中的实现方式进行对比。
Ray tracing需要对于vertex构成的物体进行更高效的组织,以提高计算速度并避免不必要的计算。在面向大量三角形的场景中,相交测试计算可能非常耗时,而加速结构的目的就是解决这个问题。

在soft ray tracing render 中有许多实现方式,例如:BVH(Bounding volume hierarchy)、八叉树(Octree)和kd-树(kd-tree)等多种数据结构可供选择。在硬件实现中,我们无需亲自管理底层数据结构,因为硬件提供了其他方式。

硬件将这个问题分为多个层次的架构,对于软件开发者而言,有两个层次是可编程的:一个是顶层加速结构(Top-Level Acceleration Structures,TLAS),另一个是与之配合的底层加速结构(Bottom-Level Acceleration Structures,BLAS)。这种分层架构有助于简化开发过程,同时充分利用硬件资源以提高光线追踪性能。

AS的分类

底层加速结构(BLAS),就像BVH结构中最深层叶子结点。它包含每个图元(Primitive)的AABB bounding box(Axis-Aligned Bounding Box),在对整个场景进行分割时,可以找到特定物体并获取该物体包含的图元的一些属性信息,如具体的顶点数据。
然而,BLAS的构成比这更为复杂。在场景中,相同几何形状的物体可能会重复出现,它们的几何形状是相同的,但位置或旋转属性可能不同。因此,BLAS不仅包含顶点缓冲区的数据,还可以通过应用物体变换的变换矩阵(Transform Matrix)来描述,这使得单个BLAS能更高效地描述场景中的所有物体。

顶层加速结构(TLAS),类似于BVH中的叶子节点,它包含各种物体实例。借助BLAS的特性,多个TLAS可以引用同一个BLAS,因为可以通过变换矩阵实现转换。
每个实例还具有相应的ID,以便进行区分。从结构上看,TLAS类似于BLAS的容器,它会保存BLAS的指针以及其他信息,如变换矩阵。这种设计使得TLAS和BLAS可以共同描述复杂的三维场景,同时提高光线追踪的效率。

TLAS与BLAS的组织方式

在工程中,解析模型文件之后,就可以得到Vertex、Index等信息,在这个阶段就可以开始构建AS结构了,与soft render的过程是非常类似的。

Pipeline

Pipeline部分是rasterization pipeline 和 ray tracing pipeline之间的核心区别所在。
对于光线追踪,有两种管线可供选择:Ray Query 和Ray Tracing。
Ray Query更接近soft render的工程结构并且理解起来更容易,通过使用Compute shader进行处理,更适用于通用计算任务,如视频编解码和图像处理。
然而,Ray Tracing管线在实际工程中使用更广泛,更适合成像任务,因此我们将重点讨论这部分。

Ray tracing的一个挑战在于,与光栅化不同,无法根据不同的材质划分不同的渲染顺序来选择着色器。
但是对于ray tracing 是不可能根据intersect的情况选择的,在tracing的过程中,所有的shader都必须随时可用。这部分的功能会通过 Shader Binding Table(SBT)功能来实现,SBT将在后续进行解释
为解决这个问题,引入了Shader Binding Table( SBT )的概念,后续将对SBT进行详细解释。

光线追踪管线主要由三个部分组成:shader stage、fix-function、layout;与rasterization有明显区别的部分在shader部分。Ray tracing shader stage 中涉及到多个shader:

  • Ray generation shader
    在图像中的每一个像素都需要通过 tracing来获取,在soft render中,光线的生成涉及到相机位置和FOV(Field of View)配置。同样在光线追踪管线中,需要使用着色器内置函数traceRayEXT进行处理。

  • Miss shader
    光线生成后,需要在场景中进行相交测试,判断光线与哪些物体相交。这里的光线不受材质限制,会检测所有可能的交点。当加速结构(AS)判断没有交点时,Miss着色器开始工作。通常需要多个Miss着色器,因为hadow ray 和primary ray需要分开处理。

  • Closest hit shader
    根据上一个着色器的相交测试结果,生成距离光线起点最近的交点,并计算光线的radiance;类似rasterization pipeline中fragment shader的作用,控制最终的图像结果。

  • Intersection shader
    对于特定的图元进行intersect test,因为有些特殊的图元可能不再blas中,所以需要自定义判断相交的条件

  • Any hit shader
    通常用于透明场景,用于判断多个点中哪个点走Closest Hit shader,从而构建一些风格化效果。

而在descriptor set方面,ray tracing 还需要一些额外的信息,例如TLAS就需要通过descriptor set 以告知shader,大部分descriptor set的配置与rasterization pipeline类似,不再展开赘述。

Shader Binding Table

ray tracing pipeline中包含多个shader stage,因此需要一种机制进行调度和管理。
这个机制就是Shader Binding Table (SBT),它包含了所有的着色器以及一些附加参数。这样,在光线追踪过程中,可以调用各种着色器及其参数。这整套结构作为SBT的基本单元 - Shader Record。

Shader Record包含一个或多个着色器以及一些附加数据。这些附加数据会在运行时传入着色器中。
每个Shader Record都需要作为一组function handle才能写入SBT中,因此,涉及到GPU中句柄对齐(Handle Alignment)的问题。为了提高GPU的效率,通常需要进行对齐处理。在同一个组中,两个handle的地址差距必须是shaderGroupHandleAlignment的整数倍。

Shader Record包含一个或多个着色器以及一些附加数据。这些附加数据会在运行时传入着色器中。每个Shader Record需要作为一组函数句柄才能写入SBT。因此,涉及到GPU中句柄对齐(Handle Alignment)的问题。为了提高GPU的效率,通常需要进行对齐处理。在同一个组中,两个句柄的地址差距必须是shaderGroupHandleAlignment的整数倍。

解决了着色器的组织管理问题后,还有一个调度问题:当光线与一个几何体相交时,该如何知道哪一个着色器应被调用?

如果是soft rendering的情况下,反正geometry和material的信息都在CPU侧,通常在判断交点时可以直接获取对应的geometry和material了,自然可以选择对应的方式来计算radiance。
但是在GPU环境下,具体的geometry的排列顺序、BLAS中的信息、光线的相交判断都在不同的shader、甚至可能在不同的pipeline中,这问题就变得难以解决。
因此,需要一种机制,能让shader与BLAS中的geometry进行联动,定位到底需要哪一个shader,在BLAS中由于有各种geometry的reference,可以提供一个index用于定位,类似每个geometry分配一个ID。这样可以提供给SBT一个offset,让SBT的hit group获得起始位置。
可以将SBT的hit group records类比为一个array,我们知道SBT的地址头,也知道剩下的shader是对齐排列的,但是如何知道所需的着色器在哪里,就需要一个公式:

其中HG就是地址,所需要的地址就是: 地址头 + (预设的offset + SBT hit group recoreds的步长 + geometry的ID + sub-table的hit group recored所开始地方的offset )
通过这种方式,最终构建出着色器的索引。
在着色器中,对应的代码如下:

void traceRayEXT(accelerationStructureEXT topLevel,
                 uint rayFlags,
                 uint cullMask,
                 uint sbtRecordOffset,
                 uint sbtRecordStride,
                 uint missIndex,
                 vec3 origin,
                 float Tmin,
                 vec3 direction,
                 float Tmax,
                 int payload);

(sbtRecoredOffset对应公式中的 $$R_{offset}$$ 、sbtRecordStride 对应公式中的 $$R_{stride}$$

通过这种方法,可以完成对着色器的调度。假设是miss shader,首先确保所有的Hit Group经过shaderGroupBaseAlignment处理后,每个着色器的入口再经过shaderGroupHandleAlignment的对齐处理,就可以通过公式获得着色器组的handle。

实现与优化

Ray tracing pipeline 创建

创建Ray Tracing管线的核心步骤可以分为以下几个部分:

  1. 填充VkPipelineShaderStageCreateInfo结构,为多个shader准备createinfo。
  2. 填充VkRayTracingShaderGroupCreateInfoKHR结构,用于描述Ray Tracing管线中的shader group,将需要的shader编成一个group。
  3. 对于没有自定义需求的shader进行设置,例如groupInfo.anyHitShader = VK_SHADER_UNUSED_KHR。
  4. 定义push constant,用于后续在shader中提供一些关于光源位置、强度等常量数据。
  5. 创建pipeline layout,包括三部分:shader stage、固定功能和Ray Tracing特有的类似TLAS的信息。而在stage中,与传统的图形管线中只有两个可编程的stage不同,Ray Tracing shader stage可以有更多的自定义空间。
  6. 设置最大光线深度为2,表示光线可以反射两次以实现全局光照效果。
  7. 销毁对应的资源,例如shader module、layout

SBT的创建与管理

SBT的核心目的是实现shader 管理和调度,在具体实现上的主要步骤是:

  1. 假设有一个shader group,其中包含ray generation、miss、hit三类shader。
  2. 进行group align和shader handle align处理,得到shader的size和stride数据。
  3. 根据handle的数据计算SBT大小,并申请对应大小的buffer。
  4. 分配SBT buffer,其大小为ray generation、miss、hit group区域大小之和。
  5. 根据SBT buffer的设备地址,分配ray generation、miss和hit group的deviceAddress。
  6. 将所需的handle映射到SBT buffer。对于ray generation、miss和hit,进行内存复制操作。
  7. 最后,清除临时资源并解除SBT buffer的映射。

Tracing 过程

在光栅化过程中,通过调用vkCmdDrawIndexed来启动绘制操作。
而在光线追踪过程中,使用vkCmdTraceRaysKHR来启动绘制。这部分的区别仅仅是之前管线区别的延伸,不再赘述。


以上就是对于Vulkan中ray tracing pipeline与rasterization pipeline在实现中的一些对比,Vulkan概念繁多复杂,上面也省略了不少细节,lz自己也跟着做了一些实践,如果感兴趣可以参考:(Vulkan-ray-query)


若有错误疏漏,恳请包涵指点。

3
评论
收藏 2