Vulkan_Ray Tracing 05_光线追踪管线

本文主要参考NVIDIA Vulkan Ray Tracing Tutorial教程,环境配置与程序均可参照此文档执行(个人水平有限,如有错误请参照原文)。

一、光线追踪管线

在光线追踪时,与光栅化不同,我们不能按材质对绘制进行分组,因此,在执行光线追踪时每个着色器都必须随时可以被调用到,而且在运行时着色器在设备上是被有选择的执行的。接下来两篇文章的最终目标是组装一个着色器绑定表 (SBT):SBT的作用也就是让光追管线运行时所有着色器都可以随时被选择使用。其本质上是一个包含着色器句柄(可能是设备地址)的C++表,类似于vtable。一般情况下我们必须自己构建这个着色器绑定表(我们也可以使用shaderRecordEXT处理SBT 中的其他信息,本案例未涉及)。

手动构件的步骤包含以下几步:

  1. 和常规着色器创建方式相同,将光追着色器编译并加载至VkShaderModule中;
  2. 将这些VkShaderModules赋值进VkPipelineShaderStageCreateInfo结构体中;
  3. 创建一个VkRayTracingShaderGroupCreateInfoKHR的数组;每个创建的实例最终都会成为 SBT中的绑定实体。此时,由于VkPipelineShaderStageCreateInfo尚未分配设备地址,着色器组通过它们在上述数组中的索引来链接各个着色器。
  4. 使用vkCreateRayTracingPipelineKHR编译以上两个数组(通常加上一个管道布局)到光线追踪管道中;
  5. 管道将较早的着色器索引数组编译转换为着色器句柄数组。也可以用vkGetRayTracingShaderGroupHandlesKHR来 查询这个。
  6. 使用VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR标识位分配缓冲区,并将句柄复制进去。

光线追踪管道的行为更像计算管道而不是光栅化图形管道。vk光线追踪在抽象的 3Dwidth/height/depth空间中发射光线,结果使用imageStore函数写入。 然而与计算管道不同的是,您需要分派单个着色器调用,而不是本地组。

1.1 光线生成着色器(*.rgen)

光线追踪的入口点是光线生成着色器

  • 光线的生成着色器将对屏幕中每个像素生成光线。它通常会初始化一条从相机位置开始的光线,方向是通过在像素位置评估相机镜头模型而给定的。然后它会调用traceRayEXT()函数在场景中发射光线。traceRayEXT()会直接调用之后的几种着色器类型并且它会保证使用光线追踪中有效光路来传递结果。

光线追踪有效光路被声明为rayPayloadEXT或rayPayloadInEXT变量;它们一起在着色器阶段之间建立了调用者/被调用者关系。着色器的每次调用都会创建其声明的rayPayloadEXT变量的本地副本,当通过调用traceRayEXT()函数调用另一个着色器时,调用者可以选择它的一个有效光路载荷作为其rayPayloadInEXT变量(也称为“传入有效光路”)以对被调用的着色器可见。

一般我们需要合理的声明有效光路,因为过多的显存占有量使用会降低 SM(Shader便是在此单元上执行的,详细可参照GPU架构与管线总结相关知识) 占用率(并行性)。

1.2 未命中、最近命中着色器(.rmiss、.rchit)

接下来介绍的两种着色器便是:

  • 未命中着色器(.rmiss) : 是当光线与任何几何体都不相交时执行的着色器。例如,它可能对环境贴图进行采样,或通过有效光路值返回一个简单的颜色。
  • 最近命中着色器(.rchit): 是在命中与光线起点最近几何体实例的时候调用的着色器。例如,该着色器可以执行光照计算并通过有效光路返回结果。执行时可以根据需要存在多个命中着色器,就像光栅化时根据实例需要可以拥有多个片元着色器一样。

1.3 求交、任意命中着色器(.rint、.rahit)

此外还可以选择使用另外两种可选的着色器类型:

  • 求交着色器(.rint):其允许用户自定义几何形状的求交判断。例如,在进行按需加载几何数据时可用于与几何占位符相交判断,或与程序几何相交而无需事先细分它们的时候。使用此着色器需要修改加速结构的构建方式(本案例中未涉及)。相反,我们将依赖于扩展提供的内置光线三角形相交测试,它返回 2 个浮点值,表示(u,v)三角形内命中点的重心坐标。对于由顶点v0、v1、 v2组成的三角形,重心坐标定义顶点的权重如下:

在这里插入图片描述

  • **任意命中着色器(.rahit):**该命中着色器可能在任意一个可能与管线相交的情况下执行,在执行查找最近命中着色器时候,可能存在多个相交点(物体背面或者物体后面被遮挡的物体)。anyhit 着色器经常可以用来有效地实现 alpha 测试。如果 alpha 测试失败,光线遍历可以继续而无需traceRayEXT()再次调用。内置的任意命中着色器只是将交点返回给遍历引擎的传递,后者将确定哪个光线交点最近。在本案例中,因为我们在构建加速结构时指定了不透明标志,所以永远不会调用此类着色器。

以上五种shader具体的调用流程图如下(你也可以把此图看成Vulkan光追的渲染管线示意图):
在这里插入图片描述

白色的表示固定管线,彩色的是可编程管线。

通俗简单解释下,其具体执行过程如下:

  1. RayGenerationShader 用来发射光线,其中会触发RayTracing()函数;
  2. BLAS (Bottom Level Acceleration Struct) 是一种类似BVH(Bounding Volume Tree)的场景管理树,用来加速光线碰撞检测,这个是固定管线,也就是说vulkan已经帮你实现了光线 碰撞检测那一套逻辑;
  3. 在遍历BVH时如果进入intersection检测环节,就可能会调用这个求交着色器;
    但是这个shader未必进入,因为这个只针对自定义primitive,如果用通常默认的三角形网格原语,系统已经自己实现了相交判定逻辑,则跳过这个shader,简单来说,如果你想要自定义primitive,就要自己实现这个shader. 这个shader应该是自定义实现原语的相交逻辑。
  4. 固定管线相交检测, 判断是不是相交,如果不相交,继续BLAS遍历,否则调用Any-Hit Shader。
    Any-Hit Shader的作用是光线和材质发生碰撞之后,需要做什么处理,比如说是否抛弃交点之类的。你可以根据情况把这个交点抛弃,那么就认为没有发生相交,也就是anyhit的含义之后回到BLAS遍历
  5. 以上3)-5)若循环干圈,直到整个BLAS遍历完毕,进入FoundHit这个是系统固定管线,会根据你的any-hit等自己算出有没有追踪hit,如果没有,则进入MissShader,这个shader就表示光线没有碰到任何东西如何处理,比如给个默认色环境色之类的。如果发生碰撞,进入Closet-Hit Shader ,这个也就是说光线和物体发生碰撞了,需要最后的处理

这里大家可能有疑惑了,之前的Any-Hit Shader 不是也是碰撞处理吗?有什么区别?
做过光线追踪的同学应该知道,其实3)-5)的循环是拿一条光线和场景中所有物体求交(利用BLAS加速),每当发生交点时,就会走一遍3)-5)的逻辑;所有Any-Hit中的那个hit未必是离视点最近的hit,因此可能会被系统判定为无效hit,而你自己在Any-Hit Shader也可以把它判定为无效;所以Closet-Hit Shader中那个hit ,才是最终的有效hit,对于有效hit,需要特殊处理一下

  1. 最后,Closet-Hit Shader 除了输出结果之外还可以干一件事,就是Ray-Tracing() ,也就是发生光线,和RayGenerationShader中一样,为什么?这个就是光线追踪的光线反弹了,继续反弹,然后估计会走到BLAS那里继续循环 ,所以官方图示可能把这个箭头省略了;

放两张油管上大神的总结图:
在这里插入图片描述
在这里插入图片描述

1.4 代码实现

本案例只包含 3 个主要着色器程序的管道:一个单一的光线生成着色器、一个未命中着色器和一个仅由最近的命中着色器组成的单个命中组。与正常shader一样首先将每个 GLSL 着色器程序编译为 SPIR-V 。这些 SPIR-V 着色器将一起链接到光线跟踪管道中,该管道将能够将相交计算链接到正确的命中着色器。

为了能够专注于管道生成,我们提供了简单的着色器:

1.4.1 添加着色器

程序中主要的着色器包含以下三个:

  • raytrace.rgen包含光线生成程序。它还声明其对光线跟踪输出的image缓冲区和光线跟踪加速结构topLevelAS的访问权限,绑定在accelerationStructureKHR中。现在这个着色器程序只是将一个常量颜色写入输出缓冲区。
#version 460
#extension GL_EXT_ray_tracing : require
#extension GL_GOOGLE_include_directive : enable
#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require


#include "raycommon.glsl"
#include "host_device.h"

// clang-format off
layout(location = 0) rayPayloadEXT hitPayload prd;

layout(set = 0, binding = eTlas) uniform accelerationStructureEXT topLevelAS;
layout(set = 0, binding = eOutImage, rgba32f) uniform image2D image;
layout(set = 1, binding = eGlobals) uniform _GlobalUniforms { GlobalUniforms uni; };
layout(push_constant) uniform _PushConstantRay { PushConstantRay pcRay; };
// clang-format on

void main()
{
  const vec2 pixelCenter = vec2(gl_LaunchIDEXT.xy) + vec2(0.5);
  const vec2 inUV        = pixelCenter / vec2(gl_LaunchSizeEXT.xy);
  vec2       d           = inUV * 2.0 - 1.0;

  vec4 origin    = uni.viewInverse * vec4(0, 0, 0, 1);
  vec4 target    = uni.projInverse * vec4(d.x, d.y, 1, 1);
  vec4 direction = uni.viewInverse * vec4(normalize(target.xyz), 0);

  uint  rayFlags = gl_RayFlagsOpaqueEXT;
  float tMin     = 0.001;
  float tMax     = 10000.0;

  traceRayEXT(topLevelAS,     // acceleration structure
              rayFlags,       // rayFlags
              0xFF,           // cullMask
              0,              // sbtRecordOffset
              0,              // sbtRecordStride
              0,              // missIndex
              origin.xyz,     // ray origin
              tMin,           // ray min range
              direction.xyz,  // ray direction
              tMax,           // ray max range
              0               // payload (location = 0)
  );

  imageStore(image, ivec2(gl_LaunchIDEXT.xy), vec4(prd.hitValue, 1.0));
}

  • raytrace.rmiss定义未命中着色器。当没有几何体被命中时,这个着色器将被执行,并将一个恒定的颜色写入有效光路rayPayloadInEXT中。由于我们当前的光线生成程序暂时不跟踪任何光线,因此不会调用此着色器。
#version 460
#extension GL_EXT_ray_tracing : require
#extension GL_GOOGLE_include_directive : enable
#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require

#include "raycommon.glsl"
#include "wavefront.glsl"

layout(location = 0) rayPayloadInEXT hitPayload prd;

layout(push_constant) uniform _PushConstantRay
{
  PushConstantRay pcRay;
};

void main()
{
  prd.hitValue = pcRay.clearColor.xyz * 0.8;
}

  • raytrace.rchit包含一个非常简单的最近命中着色器。它将在击中几何体(三角形)时执行。作为未命中着色器,它需要光线有效载荷rayPayloadInEXT。它还具有定义hitAttributeEXT由内置三角形射线相交测试提供的相交属性(即重心坐标)作为第二个输入。这个着色器只是将一个恒定的颜色写入有效光路中。
#version 460
#extension GL_EXT_ray_tracing : require
#extension GL_EXT_nonuniform_qualifier : enable
#extension GL_EXT_scalar_block_layout : enable
#extension GL_GOOGLE_include_directive : enable

#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require
#extension GL_EXT_buffer_reference2 : require

#include "raycommon.glsl"
#include "wavefront.glsl"

hitAttributeEXT vec2 attribs;

// clang-format off
layout(location = 0) rayPayloadInEXT hitPayload prd;
layout(location = 1) rayPayloadEXT bool isShadowed;

layout(buffer_reference, scalar) buffer Vertices {Vertex v[]; }; // Positions of an object
layout(buffer_reference, scalar) buffer Indices {ivec3 i[]; }; // Triangle indices
layout(buffer_reference, scalar) buffer Materials {WaveFrontMaterial m[]; }; // Array of all materials on an object
layout(buffer_reference, scalar) buffer MatIndices {int i[]; }; // Material ID for each triangle
layout(set = 0, binding = eTlas) uniform accelerationStructureEXT topLevelAS;
layout(set = 1, binding = eObjDescs, scalar) buffer ObjDesc_ { ObjDesc i[]; } objDesc;
layout(set = 1, binding = eTextures) uniform sampler2D textureSamplers[];

layout(push_constant) uniform _PushConstantRay { PushConstantRay pcRay; };
// clang-format on


void main()
{
  // Object data
  ObjDesc    objResource = objDesc.i[gl_InstanceCustomIndexEXT];
  MatIndices matIndices  = MatIndices(objResource.materialIndexAddress);
  Materials  materials   = Materials(objResource.materialAddress);
  Indices    indices     = Indices(objResource.indexAddress);
  Vertices   vertices    = Vertices(objResource.vertexAddress);

  // Indices of the triangle
  ivec3 ind = indices.i[gl_PrimitiveID];

  // Vertex of the triangle
  Vertex v0 = vertices.v[ind.x];
  Vertex v1 = vertices.v[ind.y];
  Vertex v2 = vertices.v[ind.z];

  const vec3 barycentrics = vec3(1.0 - attribs.x - attribs.y, attribs.x, attribs.y);

  // Computing the coordinates of the hit position
  const vec3 pos      = v0.pos * barycentrics.x + v1.pos * barycentrics.y + v2.pos * barycentrics.z;
  const vec3 worldPos = vec3(gl_ObjectToWorldEXT * vec4(pos, 1.0));  // Transforming the position to world space

  // Computing the normal at hit position
  const vec3 nrm      = v0.nrm * barycentrics.x + v1.nrm * barycentrics.y + v2.nrm * barycentrics.z;
  const vec3 worldNrm = normalize(vec3(nrm * gl_WorldToObjectEXT));  // Transforming the normal to world space

  // Vector toward the light
  vec3  L;
  float lightIntensity = pcRay.lightIntensity;
  float lightDistance  = 100000.0;
  // Point light
  if(pcRay.lightType == 0)
  {
    vec3 lDir      = pcRay.lightPosition - worldPos;
    lightDistance  = length(lDir);
    lightIntensity = pcRay.lightIntensity / (lightDistance * lightDistance);
    L              = normalize(lDir);
  }
  else  // Directional light
  {
    L = normalize(pcRay.lightPosition);
  }

  // Material of the object
  int               matIdx = matIndices.i[gl_PrimitiveID];
  WaveFrontMaterial mat    = materials.m[matIdx];


  // Diffuse
  vec3 diffuse = computeDiffuse(mat, L, worldNrm);
  if(mat.textureId >= 0)
  {
    uint txtId    = mat.textureId + objDesc.i[gl_InstanceCustomIndexEXT].txtOffset;
    vec2 texCoord = v0.texCoord * barycentrics.x + v1.texCoord * barycentrics.y + v2.texCoord * barycentrics.z;
    diffuse *= texture(textureSamplers[nonuniformEXT(txtId)], texCoord).xyz;
  }

  vec3  specular    = vec3(0);
  float attenuation = 1;

  // Tracing shadow ray only if the light is visible from the surface
  if(dot(worldNrm, L) > 0)
  {
    float tMin   = 0.001;
    float tMax   = lightDistance;
    vec3  origin = gl_WorldRayOriginEXT + gl_WorldRayDirectionEXT * gl_HitTEXT;
    vec3  rayDir = L;
    uint  flags  = gl_RayFlagsTerminateOnFirstHitEXT | gl_RayFlagsOpaqueEXT | gl_RayFlagsSkipClosestHitShaderEXT;
    isShadowed   = true;
    traceRayEXT(topLevelAS,  // acceleration structure
                flags,       // rayFlags
                0xFF,        // cullMask
                0,           // sbtRecordOffset
                0,           // sbtRecordStride
                1,           // missIndex
                origin,      // ray origin
                tMin,        // ray min range
                rayDir,      // ray direction
                tMax,        // ray max range
                1            // payload (location = 1)
    );

    if(isShadowed)
    {
      attenuation = 0.3;
    }
    else
    {
      // Specular
      specular = computeSpecular(mat, gl_WorldRayDirectionEXT, L, worldNrm);
    }
  }

  prd.hitValue = vec3(lightIntensity * attenuation * (diffuse + specular));
}

在头文件中,我们添加光线追踪管道构建方法的定义,以及管道的存储成员:

void                                              createRtPipeline();

std::vector<VkRayTracingShaderGroupCreateInfoKHR> m_rtShaderGroups;
VkPipelineLayout                                  m_rtPipelineLayout;
VkPipeline                                        m_rtPipeline;

光追管线还将使用推送常量来存储全局统一值,即背景颜色和光源信息。

// 光追中用到的推入常量数据结构
struct PushConstantRay
{
  vec4  clearColor;
  vec3  lightPosition;
  float lightIntensity;
  int   lightType;
};

PushConstantRay m_pcRay{};

我们实现光线追踪管线生成的时候,首先需要添加光线生成和未命中着色器阶段,然后是最近命中着色器。所有阶段都存储在一个VkPipelineShaderStageCreateInfo的数值对象中。

此顺序是任意的,因为扩展允许开发人员以任何顺序设置管道。“阶段”术语是光栅化管道的延续;在光线追踪中,我们自己编排着色器的调用顺序以及它们之间的数据流。

如前所述,在此步骤中,此数组中的索引将用作着色器的唯一标识符。这三个阶段将使用相同的“main”函数入口。然后我们使用预编译的着色器创建一个vkCreateShaderModule结构体并定义它对应的阶段。

//--------------------------------------------------------------------------------------------------
// Pipeline for the ray tracer: all shaders, raygen, chit, miss
//
void HelloVulkan::createRtPipeline()
{
  enum StageIndices
  {
    eRaygen,
    eMiss,
    eClosestHit,
    eShaderGroupCount 
  };

  // 所有阶段
  std::array<VkPipelineShaderStageCreateInfo, eShaderGroupCount> stages{};
  VkPipelineShaderStageCreateInfo              stage{VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO};
  stage.pName = "main";  // All the same entry point
  // 光线生成着色器
  stage.module = nvvk::createShaderModule(m_device, nvh::loadFile("spv/raytrace.rgen.spv", true, defaultSearchPaths, true));
  stage.stage    = VK_SHADER_STAGE_RAYGEN_BIT_KHR;
  stages[eRaygen] = stage;
  // 未命中着色器
  stage.module = nvvk::createShaderModule(m_device, nvh::loadFile("spv/raytrace.rmiss.spv", true, defaultSearchPaths, true));
  stage.stage  = VK_SHADER_STAGE_MISS_BIT_KHR;
  stages[eMiss] = stage;
  // 当阴影射线未命中几何体时,第二个未命中着色器被调用。它只是表明没有发现光路闭合
  stage.module =
      nvvk::createShaderModule(m_device, nvh::loadFile("spv/raytraceShadow.rmiss.spv", true, defaultSearchPaths, true));
  stage.stage   = VK_SHADER_STAGE_MISS_BIT_KHR;
  stages[eMiss2] = stage;
  // Hit Group - Closest Hit
  stage.module = nvvk::createShaderModule(m_device, nvh::loadFile("spv/raytrace.rchit.spv", true, defaultSearchPaths, true));
  stage.stage  = VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR;
  stages[eClosestHit] = stage;

这些标识符存储在 VkRayTracingShaderGroupCreateInfoKHR结构中。这个结构体首先指定了一个type,它代表了结构体中所代表的着色器组的种类。光线生成和未命中着色器被称为“通用”着色器。在这种情况下,类型是VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR,并且只对generalShader成员填充着色器。其他设置为 VK_SHADER_UNUSED_KHR。在我们的布局中,光线生成首先出现 (0槽位),然后是未命中着色器 (1槽位)。

  // Shader groups
  VkRayTracingShaderGroupCreateInfoKHR group{VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR};
  group.anyHitShader       = VK_SHADER_UNUSED_KHR;
  group.closestHitShader   = VK_SHADER_UNUSED_KHR;
  group.generalShader      = VK_SHADER_UNUSED_KHR;
  group.intersectionShader = VK_SHADER_UNUSED_KHR;

  // Raygen
  group.type          = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR;
  group.generalShader = eRaygen;
  m_rtShaderGroups.push_back(group);

  // Miss
  group.type          = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR;
  group.generalShader = eMiss;
  m_rtShaderGroups.push_back(group);

如前所述,求交由 3 种着色器管理:求交着色器计算光线与几何体的交叉点,针对每个潜在的交点运行任意命中着色器,将最近的命中着色器应用于沿射线最近的命中点。这 3 个着色器绑定到一个命中组中。在我们的例子中,几何是由三角形组成的,所以type的VkRayTracingShaderGroupCreateInfoKHR是VK_RAY_TRACING_SHADER_GROUP_TYPE_TRIANGLES_HIT_GROUP_KHR。

我们首先将 重置generalShader为VK_SHADER_UNUSED_KHR。因此,光线追踪硬件取代了求交着色器,因此,我们将intersectionShader成员留给VK_SHADER_UNUSED_KHR。我们不使用任何命中着色器,让系统使用内置的直通着色器。因此,我们也留下anyHitShader为 VK_SHADER_UNUSED_KHR。我们定义的唯一着色器是最近命中着色器,通过设置closestHitShader 索引2( chit) 的成员,因为stages数组已经包含光线生成和未命中着色器。

// closest hit shader
group.type             = VK_RAY_TRACING_SHADER_GROUP_TYPE_TRIANGLES_HIT_GROUP_KHR;
group.generalShader    = VK_SHADER_UNUSED_KHR;
group.closestHitShader = eClosestHit;
m_rtShaderGroups.push_back(group);

请注意,如果几何图形不是三角形,我们会将
设置type为VK_RAY_TRACING_SHADER_GROUP_TYPE_PROCEDURAL_HIT_GROUP_KHR,并且必须定义一个相交着色器。

创建着色器组后,我们需要设置管道布局,该布局将描述管道如何访问外部数据:

  VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo;

我们首先添加推送常量范围以允许光线跟踪着色器访问全局统一值:

// 推入常量:我们希望能够更新着色器使用的常量
VkPushConstantRange pushConstant{VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_MISS_BIT_KHR,
                                 0, sizeof(PushConstantRay)};


VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO};
pipelineLayoutCreateInfo.pushConstantRangeCount = 1;
pipelineLayoutCreateInfo.pPushConstantRanges    = &pushConstant;

如前所述,管道使用两个描述符集:

  • set=0 特定于光线跟踪管道(TLAS 和输出图像)
  • set=1 与光栅化(场景数据)共享
// 描述符集:一个特定于光线追踪,一个与光栅化管线共享
std::vector<VkDescriptorSetLayout> rtDescSetLayouts = {m_rtDescSetLayout, m_descSetLayout};
pipelineLayoutCreateInfo.setLayoutCount             = static_cast<uint32_t>(rtDescSetLayouts.size());
pipelineLayoutCreateInfo.pSetLayouts                = rtDescSetLayouts.data();

管道布局信息现已完成,之后我们创建布局本身。

vkCreatePipelineLayout(m_device, &pipelineLayoutCreateInfo, nullptr, &m_rtPipelineLayout);

光线追踪管线的创建与经典图形管线不同。在图形管线中,我们只需要填充一组固定的可编程阶段(顶点、片段等)。光线跟踪管线可以包含任意数量的阶段,具体取决于场景中活动着色器的数量。

我们首先提供将要使用的所有阶段:

// 将着色器阶段和递归深度信息整合到光线追踪管线中
VkRayTracingPipelineCreateInfoKHR rayPipelineInfo{VK_STRUCTURE_TYPE_RAY_TRACING_PIPELINE_CREATE_INFO_KHR};
rayPipelineInfo.stageCount = static_cast<uint32_t>(stages.size());  // Stages are shaders
rayPipelineInfo.pStages    = stages.data();

然后,我们指出如何将着色器组装成组。光线生成或未命中着色器本身就是一个组,但命中组最多可以包含 3 个着色器(求交、任何命中、最近命中)。

//在这个例子中,m_rtShaderGroups.size() == 3:我们有一个raygen组,一个未命中shader组,一个命中组。
rayPipelineInfo.groupCount = static_cast<uint32_t>(m_rtShaderGroups.size());
rayPipelineInfo.pGroups    = m_rtShaderGroups.data();

光线生成和最近命中着色器可以追踪光线,使光线追踪成为一个潜在的递归过程。为了允许底层 RTX 优化管线,我们指定了着色器使用的最大递归深度。对于我们目前拥有的简单着色器,我们将此深度设置为 1,这意味着我们根本不能触发递归(即命中着色器调用TraceRayEXT())。请注意,最好将递归级别保持在尽可能低的水平,而代之以循环公式。

rayPipelineInfo.maxPipelineRayRecursionDepth = 1;  // Ray depth
rayPipelineInfo.layout                       = m_rtPipelineLayout;

vkCreateRayTracingPipelinesKHR(m_device, {}, {}, 1, &rayPipelineInfo, nullptr, &m_rtPipeline);

创建管线后,我们释放着色器模块:

  for(auto& s : stages)
    vkDestroyShaderModule(m_device, s.module, nullptr);
}

管线布局和管线本身也必须在关闭时清理,因此我们将其添加到 destroyResources:

vkDestroyPipeline(m_device, m_rtPipeline, nullptr);
vkDestroyPipelineLayout(m_device, m_rtPipelineLayout, nullptr);

最后,在main函数中,我们在其他光线追踪调用之后调用管道构建:

  helloVk.createRtPipeline();
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值