Vulkan_Ray Tracing 10_简单路径追踪

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

一、场景优化

1.1 场景数据

之前OBJ文件加载后被分别存储在四个缓冲中:

  • 顶点缓冲区:位置、法线、UV、颜色数组

  • 索引缓冲区:顶点索引,每三个组成一个三角形

  • 材质缓冲区:wavefront 材质结构数据

  • 材质索引缓冲区:每个三角形的材质索引。

因为我们可以加载多个 OBJ文件,所以我们可能会有多组上述结构的缓冲区数组造成缓冲区冗余。

为了兼容 GLTF 场景数据,我们不再将顶点,位置,法线和其他属性放在单独的缓冲区。 对于场景中的所有几何图形,都放在一个位置缓冲区,对于索引和其他属性也是如此。 所以我们需要记录下对于每个几何图形的元素数量和偏移量等信息。

从源教程中,我们不需要以下内容,因此将其删除:

  //delete
  std::vector<ObjModel>    m_objModel;   // Model on host
  std::vector<ObjDesc>     m_objDesc;    // Model description for device access
  std::vector<ObjInstance> m_instances;  // Scene model instances

在host_device.h中我们将添加结构体:PrimMeshInfo,SceneDesc和GltfShadeMaterial。

// 用于检索最近命中着色器中基元信息的结构体
struct PrimMeshInfo
{
  uint indexOffset;
  uint vertexOffset;
  int  materialIndex;
};

// 各缓冲区地址
struct SceneDesc
{
  uint64_t vertexAddress;    //顶点缓冲区地址
  uint64_t normalAddress;    //普通缓冲区地址
  uint64_t uvAddress;        //纹理坐标缓冲区的地址
  uint64_t indexAddress;     //三角形索引缓冲区的地址
  uint64_t materialAddress;  //材质缓冲区的地址 (GltfShadeMaterial) 
  uint64_t primInfoAddress;  //网格图元缓冲区的地址 (PrimMeshInfo) 
};

此外,我们的 GLTF中用于着色的材质信息如下:

struct GltfShadeMaterial
{
  vec4 pbrBaseColorFactor;
  vec3 emissiveFactor;
  int  pbrBaseColorTexture;
};

为了保存创建的用于表示场景的所有缓冲区,我们将它们存储在下面列表中:

 nvh::GltfScene m_gltfScene;
 nvvk::Buffer   m_vertexBuffer;
 nvvk::Buffer   m_normalBuffer;
 nvvk::Buffer   m_uvBuffer;
 nvvk::Buffer   m_indexBuffer;
 nvvk::Buffer   m_materialBuffer;
 nvvk::Buffer   m_primInfo;
 nvvk::Buffer   m_sceneDesc;

1.2 加载 GLTF

为了加载场景,我们将使用TinyGLTF来解析文件。。

我们将加载一个场景而不是加载模型,因此我们将替换loadModel()函数为loadScene()。

在源文件中,加载场景loadScene()将首先使用 TinyGLTF 导入 glTF。

  tinygltf::Model    tmodel;
  tinygltf::TinyGLTF tcontext;
  std::string        warn, error;

  if(!tcontext.LoadASCIIFromFile(&tmodel, &error, &warn, filename))
    assert(!"Error while loading scene");

然后我们使用 gltfScene 帮助程序获取我们需要的信息。

  m_gltfScene.importMaterials(tmodel);
  m_gltfScene.importDrawableNodes(tmodel,
                                  nvh::GltfAttributes::Normal | nvh::GltfAttributes::Texcoord_0);

下一部分是分配缓冲区来保存信息,例如位置、法线、纹理坐标等。

  //在设备上创建缓冲区并复制顶点、索引和材质
  nvvk::CommandPool cmdBufGet (m_device, m_graphicsQueueIndex);
  VkCommandBuffer cmdBuf = cmdBufGet.createCommandBuffer();

  m_vertexBuffer = m_alloc.createBuffer(cmdBuf, m_gltfScene.m_positions,
                                        VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT
                                            | VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR);
  m_indexBuffer = m_alloc.createBuffer(cmdBuf, m_gltfScene.m_indices,
                                       VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT
                                           | VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR);
  m_normalBuffer = m_alloc.createBuffer(cmdBuf, m_gltfScene.m_normals,
                                        VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT
                                            | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT);
  m_uvBuffer = m_alloc.createBuffer(cmdBuf, m_gltfScene.m_texcoords0,
                                    VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT
                                        | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT);

我们只从 glTF 材质中提取几个如下的数据。

 //只复制我们需要的元素所有材质
  std::vector<GltfShadeMaterial> shadeMaterials;
  for(auto& m : m_gltfScene.m_materials)
  {
    shadeMaterials.emplace_back(GltfShadeMaterial{m.baseColorFactor, m.emissiveFactor, m.baseColorTexture});
  }
  m_materialBuffer = m_alloc.createBuffer(cmdBuf, shadeMaterials,
                                          VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT);

为了在最近命中着色器中找到三角形命中的位置以及其他属性,我们将存储该几何体的偏移信息。

  //以下用于查找CHIT中的原始mesh信息
  std::vector<PrimMeshInfo> primLookup;
  for(auto& primMesh : m_gltfScene.m_primMeshes)
  {
    primLookup.push_back({primMesh.firstIndex, primMesh.vertexOffset, primMesh.materialIndex});
  }
  m_rtPrimLookup =
      m_alloc.createBuffer(cmdBuf, primLookup, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT);

最后,我们创建一个缓冲区,保存所有缓冲区的地址

  SceneDesc sceneDesc;
  sceneDesc.vertexAddress   = nvvk::getBufferDeviceAddress(m_device, m_vertexBuffer.buffer);
  sceneDesc.indexAddress    = nvvk::getBufferDeviceAddress(m_device, m_indexBuffer.buffer);
  sceneDesc.normalAddress   = nvvk::getBufferDeviceAddress(m_device, m_normalBuffer.buffer);
  sceneDesc.uvAddress       = nvvk::getBufferDeviceAddress(m_device, m_uvBuffer.buffer);
  sceneDesc.materialAddress = nvvk::getBufferDeviceAddress(m_device, m_materialBuffer.buffer);
  sceneDesc.primInfoAddress = nvvk::getBufferDeviceAddress(m_device, m_primInfo.buffer);
  m_sceneDesc               = m_alloc.createBuffer(cmdBuf, sizeof(SceneDesc), &sceneDesc,
                                     VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT);

在关闭函数之前,我们将创建纹理(默认场景中没有)并提交命令缓冲区。在所有数据复制到 GPU后释放数据。

  //创建所有找到的纹理
  createTextureImages (cmdBuf, tmodel);
  cmdBufGet.submitAndWait(cmdBuf);
  m_alloc.finalizeAndReleaseStaging();

  NAME_VK (m_vertexBuffer.buffer);
  NAME_VK (m_indexBuffer.buffer);
  NAME_VK (m_normalBuffer.buffer);
  NAME_VK (m_uvBuffer.buffer);
  NAME_VK (m_materialBuffer.buffer);
  NAME_VK (m_primInfo.buffer);
  NAME_VK (m_sceneDesc.buffer);
}

⚠️注意:宏NAME_VK可以方便地命名 Vulkan 对象,以便在 Nsight Graphics 中轻松识别它们并了解它的创建位置。

1.3 创建 BLAS

我们将使用primitiveToVkGeometry(const nvh::GltfPrimMesh& prim)取代objectToVkGeometryKHR(),两者功能类似,只是输入不同,此外对于结构体VkAccelerationStructureBuildRangeInfoKHR我们还包括偏移量数据。

// 在创建BLAS时,将GLTF图元转换成光追需要的几何数据
//
auto HelloVulkan::primitiveToGeometry(const nvh::GltfPrimMesh& prim)
{
  // 缓冲区原始地址.
  VkDeviceAddress vertexAddress = nvvk::getBufferDeviceAddress(m_device, m_vertexBuffer.buffer);
  VkDeviceAddress indexAddress  = nvvk::getBufferDeviceAddress(m_device, m_indexBuffer.buffer);

  uint32_t maxPrimitiveCount = prim.indexCount / 3;

  VkAccelerationStructureGeometryTrianglesDataKHR triangles{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR};
  triangles.vertexFormat             = VK_FORMAT_R32G32B32_SFLOAT;  // vec3 vertex position data.
  triangles.vertexData.deviceAddress = vertexAddress;
  triangles.vertexStride             = sizeof(nvmath::vec3f);
  // Describe index data (32-bit unsigned int)
  triangles.indexType               = VK_INDEX_TYPE_UINT32;
  triangles.indexData.deviceAddress = indexAddress;
  //通过将transformData 设置为空设备指针来处理转换。
  triangles.maxVertex = prim.vertexCount;

  //将上述数据标识为包含不透明三角形。
  VkAccelerationStructureGeometryKHR asGeom{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR};
  asGeom.geometryType       = VK_GEOMETRY_TYPE_TRIANGLES_KHR;
  asGeom.flags              = VK_GEOMETRY_NO_DUPLICATE_ANY_HIT_INVOCATION_BIT_KHR;  // For AnyHit
  asGeom.geometry.triangles = triangles;

  VkAccelerationStructureBuildRangeInfoKHR offset;
  offset.firstVertex     = prim.vertexOffset;
  offset.primitiveCount  = prim.indexCount / 3;
  offset.primitiveOffset = prim.firstIndex * sizeof(uint32_t);
  offset.transformOffset = 0;

  //我们的 blas 仅由一种几何图形组成,但可以由多种几何图形组成
  nvvk::RaytracingBuilderKHR::BlasInput input;
  input.asGeometry.emplace_back(asGeom);
  input.asBuildOffsetInfo.emplace_back(offset);

  return input;
}

1.4 创建 TLAS

除了几何的索引存储在primMesh中,与之前创建TLAS几乎没有什么不同、

  for(auto& node : m_gltfScene.m_nodes)
  {
    VkAccelerationStructureInstanceKHR rayInst;
    rayInst.transform                      = nvvk::toTransformMatrixKHR(node.worldMatrix);
    rayInst.instanceCustomIndex            = node.primMesh;  // gl_InstanceCustomIndexEXT: to find which primitive
    rayInst.accelerationStructureReference = m_rtBuilder.getBlasDeviceAddress(node.primMesh);
    rayInst.flags                          = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR;
    rayInst.mask                           = 0xFF;
    rayInst.instanceShaderBindingTableRecordOffset = 0;  // We will use the same hit group for all objects
    tlas.emplace_back(rayInst);
  }

1.5 光栅化更改

光栅渲染很简单。着色器已更改为使用顶点、法线和纹理坐标。对于每个节点,我们将推送该图元正在使用的材质 ID。由于我们已经展平了场景图,我们可以遍历所有可绘制节点。

  std::vector<VkBuffer> vertexBuffers = {m_vertexBuffer.buffer, m_normalBuffer.buffer, m_uvBuffer.buffer};
  vkCmdBindVertexBuffers(cmdBuf, 0, static_cast<uint32_t>(vertexBuffers.size()), vertexBuffers.data(), offsets.data());
  vkCmdBindIndexBuffer(cmdBuf, m_indexBuffer.buffer, 0, VK_INDEX_TYPE_UINT32);

  uint32_t idxNode = 0;
  for(auto& node : m_gltfScene.m_nodes)
  {
    auto& primitive = m_gltfScene.m_primMeshes[node.primMesh];

    m_pcRaster.modelMatrix = node.worldMatrix;
    m_pcRaster.objIndex    = node.primMesh;
    m_pcRaster.materialId  = primitive.materialIndex;
    vkCmdPushConstants(cmdBuf, m_pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0,
                       sizeof(PushConstantRaster), &m_pcRaster);
    vkCmdDrawIndexed(cmdBuf, primitive.indexCount, 1, primitive.firstIndex, primitive.vertexOffset, 0);
  }

1.6 光线追踪更改

在createRtDescriptorSet()中,我们将添加的唯一更改是原始信息缓冲区,用于在击中三角形时检索数据。

  m_rtDescSetLayoutBind.addBinding(ePrimLookup, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1,
                                   VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR);  // Primitive info
// ...
  VkDescriptorBufferInfo primitiveInfoDesc{m_rtPrimLookup.buffer, 0, VK_WHOLE_SIZE};
// ...
  writes.emplace_back(m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, ePrimLookup, &primitiveInfoDesc));

1.7 描述符与渲染管线结构体更改

由于我们使用了不同的缓冲区,并且顶点不再是一个结构体,而是使用了 3 个不同的缓冲区作为位置、法线和纹理坐标。在方法createDescriptorSetLayout(),updateDescriptorSet()和createGraphicsPipeline() 中将相应更改。

具体参照源码

1.8 Shader更改

着色与之前是相同的,并没有使用 glTF PBR 着色模型,但着色器仍需要更改以适应新的传入格式。

光栅化:

//顶点着色器
#version 450
#extension GL_ARB_separate_shader_objects : enable
#extension GL_EXT_scalar_block_layout : enable
#extension GL_GOOGLE_include_directive : enable
#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require

#include "gltf.glsl"
#include "host_device.h"

layout(binding = 0) uniform _GlobalUniforms
{
  GlobalUniforms uni;
};

layout(push_constant) uniform _PushConstantRaster
{
  PushConstantRaster pcRaster;
};

layout(location = 0) in vec3 i_position;
layout(location = 1) in vec3 i_normal;
layout(location = 2) in vec2 i_texCoord;


layout(location = 1) out vec3 o_worldPos;
layout(location = 2) out vec3 o_worldNrm;
layout(location = 3) out vec3 o_viewDir;
layout(location = 4) out vec2 o_texCoord;

out gl_PerVertex
{
  vec4 gl_Position;
};

void main()
{
  vec3 origin = vec3(uni.viewInverse * vec4(0, 0, 0, 1));

  o_worldPos = vec3(pcRaster.modelMatrix * vec4(i_position, 1.0));
  o_viewDir  = vec3(o_worldPos - origin);
  o_texCoord = i_texCoord;
  o_worldNrm = mat3(pcRaster.modelMatrix) * i_normal;

  gl_Position = uni.viewProj * vec4(o_worldPos, 1.0);
}
//片段着色器
#version 450
#extension GL_ARB_separate_shader_objects : enable
#extension GL_EXT_nonuniform_qualifier : enable
#extension GL_GOOGLE_include_directive : enable
#extension GL_EXT_scalar_block_layout : enable

#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require
#extension GL_EXT_buffer_reference2 : require

#include "gltf.glsl"
#include "host_device.h"

layout(push_constant) uniform _PushConstantRaster
{
  PushConstantRaster pcRaster;
};

// clang-format off
// Incoming 
layout(location = 1) in vec3 i_worldPos;
layout(location = 2) in vec3 i_worldNrm;
layout(location = 3) in vec3 i_viewDir;
layout(location = 4) in vec2 i_texCoord;
// Outgoing
layout(location = 0) out vec4 o_color;
// Buffers
layout(buffer_reference, scalar) buffer  GltfMaterial { GltfShadeMaterial m[]; };
layout(set = 0, binding = eSceneDesc ) readonly buffer SceneDesc_ { SceneDesc sceneDesc; } ;
layout(set = 0, binding = eTextures) uniform sampler2D[] textureSamplers;
// clang-format on


void main()
{
  // Material of the object
  GltfMaterial      gltfMat = GltfMaterial(sceneDesc.materialAddress);
  GltfShadeMaterial mat     = gltfMat.m[pcRaster.materialId];

  vec3 N = normalize(i_worldNrm);

  // Vector toward light
  vec3  L;
  float lightIntensity = pcRaster.lightIntensity;
  if(pcRaster.lightType == 0)
  {
    vec3  lDir     = pcRaster.lightPosition - i_worldPos;
    float d        = length(lDir);
    lightIntensity = pcRaster.lightIntensity / (d * d);
    L              = normalize(lDir);
  }
  else
  {
    L = normalize(pcRaster.lightPosition);
  }


  // Diffuse
  vec3 diffuse = computeDiffuse(mat, L, N);
  if(mat.pbrBaseColorTexture > -1)
  {
    uint txtId      = mat.pbrBaseColorTexture;
    vec3 diffuseTxt = texture(textureSamplers[nonuniformEXT(txtId)], i_texCoord).xyz;
    diffuse *= diffuseTxt;
  }

  // Specular
  vec3 specular = computeSpecular(mat, i_viewDir, L, N);

  // Result
  o_color = vec4(lightIntensity * (diffuse + specular), 1);
}

光线追踪:

//光线生成着色器
#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 "sampling.glsl"
#include "host_device.h"

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

layout(set = 0, binding = 0) uniform accelerationStructureEXT topLevelAS;
layout(set = 0, binding = 1, rgba32f) uniform image2D image;

layout(set = 1, binding = 0) 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));
}
//最近命中着色器
#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 "gltf.glsl"
#include "raycommon.glsl"
#include "host_device.h"

hitAttributeEXT vec2 attribs;

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

layout(set = 0, binding = 0 ) uniform accelerationStructureEXT topLevelAS;
layout(set = 0, binding = 2) readonly buffer _InstanceInfo {PrimMeshInfo primInfo[];};

//layout(set = 1, binding = B_MATERIALS) readonly buffer _MaterialBuffer {GltfShadeMaterial materials[];};


layout(buffer_reference, scalar) readonly buffer Vertices  { vec3 v[]; };
layout(buffer_reference, scalar) readonly buffer Indices   { uint i[]; };
layout(buffer_reference, scalar) readonly buffer Normals   { vec3 n[]; };
layout(buffer_reference, scalar) readonly buffer TexCoords { vec2 t[]; };
layout(buffer_reference, scalar) readonly buffer Materials { GltfShadeMaterial m[]; };

layout(set = 1, binding = eSceneDesc ) readonly buffer SceneDesc_ { SceneDesc sceneDesc; };
layout(set = 1, binding = eTextures) uniform sampler2D texturesMap[]; // all textures

// clang-format on

layout(push_constant) uniform Constants
{
  vec4  clearColor;
  vec3  lightPosition;
  float lightIntensity;
  int   lightType;
}
pushC;


void main()
{
  // Retrieve the Primitive mesh buffer information
  PrimMeshInfo pinfo = primInfo[gl_InstanceCustomIndexEXT];

  // Getting the 'first index' for this mesh (offset of the mesh + offset of the triangle)
  uint indexOffset  = pinfo.indexOffset + (3 * gl_PrimitiveID);
  uint vertexOffset = pinfo.vertexOffset;           // Vertex offset as defined in glTF
  uint matIndex     = max(0, pinfo.materialIndex);  // material of primitive mesh

  Materials gltfMat   = Materials(sceneDesc.materialAddress);
  Vertices  vertices  = Vertices(sceneDesc.vertexAddress);
  Indices   indices   = Indices(sceneDesc.indexAddress);
  Normals   normals   = Normals(sceneDesc.normalAddress);
  TexCoords texCoords = TexCoords(sceneDesc.uvAddress);
  Materials materials = Materials(sceneDesc.materialAddress);


  // Getting the 3 indices of the triangle (local)
  ivec3 triangleIndex = ivec3(indices.i[indexOffset + 0], indices.i[indexOffset + 1], indices.i[indexOffset + 2]);
  triangleIndex += ivec3(vertexOffset);  // (global)

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

  // Vertex of the triangle
  const vec3 pos0           = vertices.v[triangleIndex.x];
  const vec3 pos1           = vertices.v[triangleIndex.y];
  const vec3 pos2           = vertices.v[triangleIndex.z];
  const vec3 position       = pos0 * barycentrics.x + pos1 * barycentrics.y + pos2 * barycentrics.z;
  const vec3 world_position = vec3(gl_ObjectToWorldEXT * vec4(position, 1.0));

  // Normal
  const vec3 nrm0         = normals.n[triangleIndex.x];
  const vec3 nrm1         = normals.n[triangleIndex.y];
  const vec3 nrm2         = normals.n[triangleIndex.z];
  vec3       normal       = normalize(nrm0 * barycentrics.x + nrm1 * barycentrics.y + nrm2 * barycentrics.z);
  const vec3 world_normal = normalize(vec3(normal * gl_WorldToObjectEXT));
  const vec3 geom_normal  = normalize(cross(pos1 - pos0, pos2 - pos0));

  // TexCoord
  const vec2 uv0       = texCoords.t[triangleIndex.x];
  const vec2 uv1       = texCoords.t[triangleIndex.y];
  const vec2 uv2       = texCoords.t[triangleIndex.z];
  const vec2 texcoord0 = uv0 * barycentrics.x + uv1 * barycentrics.y + uv2 * barycentrics.z;

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

  // Material of the object
  GltfShadeMaterial mat = materials.m[matIndex];

  // Diffuse
  vec3 diffuse = computeDiffuse(mat, L, world_normal);
  if(mat.pbrBaseColorTexture > -1)
  {
    uint txtId = mat.pbrBaseColorTexture;
    diffuse *= texture(texturesMap[nonuniformEXT(txtId)], texcoord0).xyz;
  }

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

  // Tracing shadow ray only if the light is visible from the surface
  if(dot(world_normal, 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, world_normal);
    }
  }

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

1.8 其他更改

相机位置

  CameraManip.setLookat (nvmath :: vec3f ( 0 , 0 , 15 ), nvmath :: vec3f ( 0 , 0 , 0 ), nvmath :: vec3f ( 0 , 1 , 0 ));

场景模型

  helloVk.loadScene(nvh::findFile( " media/scenes/cornellBox.gltf " , defaultSearchPaths, true ));

灯光位置

  nvmath::vec3f lightPosition{ 0 . f , 45f , 0。f };

二、简单路径追踪

要将这个示例转换为简单的路径跟踪(可科学上网Wikipedia Path Tracing),我们需要更改RayGen和ClosestHit着色器。在此之前,我们需要修改应用程序以发送保存当前渲染帧,从而允许像素叠加。

在hello_vulkan.cpp中添加以下两个函数:

  void updateFrame();
  void resetFrame();
// ------------------------------------------------ -------------------------------------------------- 
//如果相机矩阵发生变化,则重置帧。
//否则,增加帧。
//
void HelloVulkan::updateFrame()
{
  static nvmath::mat4f refCamMatrix;
  static float         refFov{CameraManip.getFov()};

  const auto& m   = CameraManip.getMatrix();
  const auto  fov = CameraManip.getFov();

  if(memcmp(&refCamMatrix.a00, &m.a00, sizeof(nvmath::mat4f)) != 0 || refFov != fov)
  {
    resetFrame();
    refCamMatrix = m;
    refFov       = fov;
  }
  m_pcRay.frame++;
}

void HelloVulkan::resetFrame()
{
  m_pcRay.frame = -1;
}

并在raytrace()函数的开头调用updateFrame()。

并在RtPushConstant结构体末尾添加一个新成员。

2.1 光线生成着色器更改

在光线生成着色器中需要进行一些修改:首先,它将使用时钟作为其随机种子数。

这需要通过添加GL_ARB_shader_clock扩展来完成的。

#extension GL_ARB_shader_clock : enable

随机数的生成在sampling.glsl中,并#include这个文件中。

//sampling.glsl

//使用使用16对的微小加密算法由两个unsigned int值生成一个随机的unsigned int。
//Zafar, Olano, and Curtis, "GPU Random Numbers via the Tiny Encryption Algorithm"  
uint tea(uint val0, uint val1)
{
  uint v0 = val0;
  uint v1 = val1;
  uint s0 = 0;

  for(uint n = 0; n < 16; n++)
  {
    s0 += 0x9e3779b9;
    v0 += ((v1 << 4) + 0xa341316c) ^ (v1 + s0) ^ ((v1 >> 5) + 0xc8013ea4);
    v1 += ((v0 << 4) + 0xad90777d) ^ (v0 + s0) ^ ((v0 >> 5) + 0x7e95761e);
  }

  return v0;
}

//生成一个随机的无符号整数,在[0,2 ^24)给定之前的RNG状态  
//使用Numerical Recipes线性同余生成器  
uint lcg(inout uint prev)
{
  uint LCG_A = 1664525u;
  uint LCG_C = 1013904223u;
  prev       = (LCG_A * prev + LCG_C);
  return prev & 0x00FFFFFF;
}

//生成一个随机浮点数在[0,1)给定之前的RNG状态  float rnd(inout uint prev)
{
  return (float(lcg(prev)) / float(0x01000000));
}


//-------------------------------------------------------------------------------------------------
// Sampling
//-------------------------------------------------------------------------------------------------

// 在+Z方向附近随机采样
vec3 samplingHemisphere(inout uint seed, in vec3 x, in vec3 y, in vec3 z)
{
#define M_PI 3.141592

  float r1 = rnd(seed);
  float r2 = rnd(seed);
  float sq = sqrt(1.0 - r2);

  vec3 direction = vec3(cos(2 * M_PI * r1) * sq, sin(2 * M_PI * r1) * sq, sqrt(r2));
  direction      = direction.x * x + direction.y * y + direction.z * z;

  return direction;
}

//从传入法线返回正切和副法线  
void createCoordinateSystem(in vec3 N, out vec3 Nt, out vec3 Nb)
{
  if(abs(N.x) > abs(N.y))
    Nt = vec3(N.z, 0, -N.x) / sqrt(N.x * N.x + N.z * N.z);
  else
    Nt = vec3(0, -N.z, N.y) / sqrt(N.y * N.y + N.z * N.z);
  Nb = cross(N, Nt);
}

在main()中,我们将像这样初始化随机数:(

  //初始化随机数
  uint seed = tea(gl_LaunchIDEXT.y * gl_LaunchSizeEXT.x + gl_LaunchIDEXT.x, int (clockARB()));

为了累积帧样本,我们不仅会写入图像,还会使用上一帧数据。

  //随着时间的推移进行帧迭代
  if(pcRay.frame > 0)
  {
    float a         = 1.0f / float(pcRay.frame + 1);
    vec3  old_color = imageLoad(image, ivec2(gl_LaunchIDEXT.xy)).xyz;
    imageStore(image, ivec2(gl_LaunchIDEXT.xy), vec4(mix(old_color, hitValue, a), 1.f));
  }
  else
  {
    //第一帧中,取代在缓冲器中的值
    imageStore(image, ivec2(gl_LaunchIDEXT.xy), vec4(hitValue, 1.f));
  }

有效光路载荷hitPayload结构体也需要进行如下修改:

struct hitPayload
{
  vec3 hitValue;
  uint seed;
  uint depth;
};

2.2 最近命中着色器更改

此修改将递归光线的深度depth硬编码为10或命中发光元素为止。

我们从着色器中保留的信息仅需要后续用于计算的命中点状态信息:位置、法线。代码修改如下:

  // https://en.wikipedia.org/wiki/Path_tracing
  // 命中点材质
  GltfMaterial mat       = materials[nonuniformEXT(matIndex)];
  vec3         emittance = mat.emissiveFactor;

  // 随机选一个方向,然后继续执行光追.
  vec3 tangent, bitangent;
  createCoordinateSystem(world_normal, tangent, bitangent);
  vec3 rayOrigin    = world_position;
  vec3 rayDirection = samplingHemisphere(prd.seed, tangent, bitangent, world_normal);

  // 新光线的概率(余弦分布)  
  const float p = 1 / M_PI;

  // 计算这条射线的BRDF(假设Lambertian反射)  
  float cos_theta = dot(rayDirection, world_normal);
  vec3  BRDF      = mat.pbrBaseColorFactor.xyz / M_PI;

  // 递归追踪反射光源
  if(prd.depth < 10)
  {
    prd.depth++;
    float tMin  = 0.001;
    float tMax  = 100000000.0;
    uint  flags = gl_RayFlagsOpaqueEXT;
    traceRayEXT(topLevelAS,    // acceleration structure
                flags,         // rayFlags
                0xFF,          // cullMask
                0,             // sbtRecordOffset
                0,             // sbtRecordStride
                0,             // missIndex
                rayOrigin,     // ray origin
                tMin,          // ray min range
                rayDirection,  // ray direction
                tMax,          // ray max range
                0              // payload (location = 0)
    );
  }
  vec3 incoming = prd.hitValue;

  // 应用渲染方程
  prd.hitValue = emittance + (BRDF * incoming * cos_theta / p);

⚠️ 注意:我们不像光栅化器那样实现点光源。因此,只有自发光几何体会发射能量来照亮场景。

2.3 未命中着色器更改

为了避免来自环境的能量贡献。我们修改未命中着色器

void main()
{
  if(prd.depth == 0)
    prd.hitValue = clearColor.xyz * 0.8;
  else
    prd.hitValue = vec3(0.01);  // 来自环境的微小贡献
  prd.depth = 100;              // Ending trace
}

三、优化路径追踪

上面的实现是在最近命中着色器中递归进行路径追踪,这不是最优的。如 反射 教程中所述,最好是在光线生成着色器中打破递归性.

因此可进行以下更改提高帧率(可使渲染速度提高 3 倍)。

为了能够做到这一点,我们需要扩展射线payload结构以将数据从Closest Hit传输到RayGen中,即射线原点和方向以及 BRDF 权重三个数据。因此修改hitPayload结构体:

struct hitPayload
{
  vec3 hitValue;
  uint seed;
  uint depth;
  vec3 rayOrigin;
  vec3 rayDirection;
  vec3 weight;
};

3.1 最近命中着色器更改

我们不再需要追踪光线,所以在追踪一条新光线之前,我们可以将信息存储payload值,并在递归代码之前返回。

  prd.rayOrigin    = rayOrigin;
  prd.rayDirection = rayDirection;
  prd.hitValue     = emittance;
  prd.weight       = BRDF * cos_theta / p;
  return;

3.1 光线生成着色器更改

光线生成将执行光线追踪循环。

首先,初始化payload和局部变量来计算光线循环叠加。

  prd.rayOrigin    = origin.xyz;
  prd.rayDirection = direction.xyz;
  prd.weight       = vec3(0);

  vec3 curWeight = vec3(1);
  vec3 hitValue  = vec3(0);

现在光线追踪函数的循环将如下所示。

⚠️ 注意:递归深度现在是硬编码,也可以通过push constant传递.

  for(; prd.depth < 10; prd.depth++)
  {
    traceRayEXT(topLevelAS,        // acceleration structure
                rayFlags,          // rayFlags
                0xFF,              // cullMask
                0,                 // sbtRecordOffset
                0,                 // sbtRecordStride
                0,                 // missIndex
                prd.rayOrigin,     // ray origin
                tMin,              // ray min range
                prd.rayDirection,  // ray direction
                tMax,              // ray max range
                0                  // payload (location = 0)
    );

    hitValue += prd.hitValue * curWeight;
    curWeight *= prd.weight;
  }

最后不要忘记在imageStore中使用hitValue数据,完整的光线生成着色器如下:

#version 460
#extension GL_EXT_ray_tracing : require
#extension GL_GOOGLE_include_directive : enable
#extension GL_ARB_shader_clock : enable
#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require


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

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

layout(set = 0, binding = 0) uniform accelerationStructureEXT topLevelAS;
layout(set = 0, binding = 1, rgba32f) uniform image2D image;

layout(set = 1, binding = 0) uniform _GlobalUniforms { GlobalUniforms uni; };
layout(push_constant) uniform _PushConstantRay { PushConstantRay pcRay; };
// clang-format on

void main()
{
  // Initialize the random number
  uint seed = tea(gl_LaunchIDEXT.y * gl_LaunchSizeEXT.x + gl_LaunchIDEXT.x, int(clockARB()));

  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;

  prd.hitValue     = vec3(0);
  prd.seed         = seed;
  prd.depth        = 0;
  prd.rayOrigin    = origin.xyz;
  prd.rayDirection = direction.xyz;
  prd.weight       = vec3(0);

  vec3 curWeight = vec3(1);
  vec3 hitValue  = vec3(0);

  for(; prd.depth < 10; prd.depth++)
  {
    traceRayEXT(topLevelAS,        // acceleration structure
                rayFlags,          // rayFlags
                0xFF,              // cullMask
                0,                 // sbtRecordOffset
                0,                 // sbtRecordStride
                0,                 // missIndex
                prd.rayOrigin,     // ray origin
                tMin,              // ray min range
                prd.rayDirection,  // ray direction
                tMax,              // ray max range
                0                  // payload (location = 0)
    );

    hitValue += prd.hitValue * curWeight;
    curWeight *= prd.weight;
  }

  // Do accumulation over time
  if(pcRay.frame > 0)
  {
    float a         = 1.0f / float(pcRay.frame + 1);
    vec3  old_color = imageLoad(image, ivec2(gl_LaunchIDEXT.xy)).xyz;
    imageStore(image, ivec2(gl_LaunchIDEXT.xy), vec4(mix(old_color, hitValue, a), 1.f));
  }
  else
  {
    // First frame, replace the value in the buffer
    imageStore(image, ivec2(gl_LaunchIDEXT.xy), vec4(hitValue, 1.f));
  }
}

运行结果如下图:
在这里插入图片描述

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值