ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Vulkan_Ray Tracing 12_AnyHit Shader

2021-11-26 23:02:12  阅读:307  来源: 互联网

标签:12 Hit Tracing VK Shader uint 命中 着色器 raytrace


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

与最近命中着色器一样,任何命中着色器都在光线和几何体之间的交点上运行。但是,任何命中着色器都将在沿射线与几何体的所有命中点上执行。然后将在离相机最近的且被设置的相交点上调用最近命中着色器。

任何命中着色器可用于丢弃相交点,但也可用于简单的透明度。在此示例中,我们将展示添加此着色器并创建透明效果。

在这里插入图片描述

一、任何命中着色器(Any Hit Shader)

创建一个新的着色器文件raytrace.rahit。

此着色器以 raytrace.chit 开头,但使用的信息较少。

#version 460
#extension GL_EXT_ray_tracing : require
#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 "random.glsl"
#include "raycommon.glsl"
#include "wavefront.glsl"

// clang-format off
layout(location = 0) rayPayloadInEXT hitPayload prd;
layout(buffer_reference, scalar) buffer Vertices {Vertex v[]; }; // Positions of an object
layout(buffer_reference, scalar) buffer Indices {uint 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 = 1, binding = eObjDescs, scalar) buffer ObjDesc_ { ObjDesc i[]; } objDesc;
// clang-format on

⚠️注意: 您也可以在Vulkan_Ray Tracing 10_简单路径追踪找到。

//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);
}


对于 any hit 着色器,我们需要知道我们击中的是哪种材质,以及该材质是否支持透明度。如果它是不透明的,我们就简单地返回,这意味着命中将被接受。

void main()
{
  // 对象数据
  ObjDesc    objResource = objDesc.i[gl_InstanceCustomIndexEXT];
  MatIndices matIndices  = MatIndices(objResource.materialIndexAddress);
  Materials  materials   = Materials(objResource.materialAddress);

  // 对象的材质
  int               matIdx = matIndices.i[gl_PrimitiveID];
  WaveFrontMaterial mat    = materials.m[matIdx];

  if (mat.illum != 4)
    return;

现在我们将应用透明度:

  if (mat.dissolve == 0.0 )
       ignoreIntersectionEXT ();
  else  if (rnd(prd.seed) > mat.dissolve)
      ignoreIntersectionEXT ();
}

如上所述,我们使用随机数生成器来确定光线是击中还是忽略了对象。如果我们累加了足够多的光线,最终的结果就会收敛到我们想要的样子。

有效光路载荷

随机数seed也需要在射线有效光路载荷中传递。

所以在 raycommon.glsl 中,添加随机数:

struct hitPayload
{
  vec3 hitValue;
  uint seed;
};

二、添加任何命中着色器

任何命中着色器将成为命中着色器组的一部分。目前,命中着色器组仅包含最近命中着色器。

在createRtPipeline()中,在加载后最近命中着色器后,加载任何命中着色器

  enum StageIndices
  {
    ...
    eAnyHit,
    eShaderGroupCount
  };

  // Hit Group - Any Hit
  stage.module = nvvk::createShaderModule(m_device, nvh::loadFile("spv/raytrace.rahit.spv", true, defaultSearchPaths, true));
  stage.stage     = VK_SHADER_STAGE_ANY_HIT_BIT_KHR;
  stages[eAnyHit] = stage;

Any Hit 与 Closest Hit 位于相同的 Hit 组中,因此我们需要添加 Any Hit 索引并将其添加进命中组中。

  // 最近的命中着色器
  // Payload 0
  group.type             = VK_RAY_TRACING_SHADER_GROUP_TYPE_TRIANGLES_HIT_GROUP_KHR;
  group.generalShader    = VK_SHADER_UNUSED_KHR;
  group.closestHitShader = eClosestHit;
  group.anyHitShader     = eAnyHit;
  m_rtShaderGroups.push_back(group);

2.1 将缓冲区的访问权限授予 Any Hit 着色器

在 createDescriptorSetLayout() 中,我们需要允许 Any Hit 着色器访问场景描述缓冲区

  // Obj descriptions
  m_descSetLayoutBind.addBinding(eObjDescs, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1,
                                 VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT
                                     | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR);

2.2 不透明标识

在示例中,在创建VkAccelerationStructureGeometryKHR对象时,我们将它们的标志设置为VK_GEOMETRY_OPAQUE_BIT_KHR。然而,这避免了调用任何命中着色器。

我们可以删除所有标志,但可能会出现另一个问题:同一个三角形可能多次调用 any hit 着色器。要让任何命中着色器处理每个三角形只有一次命中,需要设置VK_GEOMETRY_NO_DUPLICATE_ANY_HIT_INVOCATION_BIT_KHR标志:

asGeom.flags = VK_GEOMETRY_NO_DUPLICATE_ANY_HIT_INVOCATION_BIT_KHR;  //避免多次击中;

三、其他着色器修改

3.1 光线生成着色器

首先,seed需要在任何命中着色器中可用,这也是我们将其添加到 hitPayload 结构体中的原因。

prd.seed = tea(gl_LaunchIDEXT.y * gl_LaunchSizeEXT.x + gl_LaunchIDEXT.x, pushC.frame);

为了优化,TraceRayEXT调用使用了gl_RayFlagsOpaqueEXT标志。但这将跳过任何命中着色器,因此将其更改为

uint   rayFlags = gl_RayFlagsNoneEXT;

3.2 最近命中着色器

同样,在最近命中着色器中,将标志更改为gl_RayFlagsSkipClosestHitShaderEXT,因为我们要启用任何命中和未命中着色器,但我们仍然不处理阴影光线的最近命中着色器。此操作也将启用透明阴影。

uint  flags = gl_RayFlagsSkipClosestHitShaderEXT;

3.3 场景和模型更新

场景

main()中改为以下场景:

  helloVk.loadModel(nvh::findFile( " media/scenes/wuson.obj " , defaultSearchPaths, true ));
  helloVk.loadModel(nvh::findFile( " media/scenes/sphere.obj " , defaultSearchPaths, true ),
                     nvmath::scale_mat4 (nvmath::vec3f( 1 . 5f ))
                        * nvmath::translation_mat4(nvmath::vec3f( 0 . 0f , 1 . 0f , 0 . 0f )));
  helloVk.loadModel(nvh::findFile( " media/scenes/plane.obj " , defaultSearchPaths, true ));

OBJ材质

默认情况下,所有对象都是不透明的,您需要更改材质描述。

编辑材质文件media/scenes/wuson.mtl和media/scenes/sphere.mtl的前几行,并使用新的照明模型(4)的0.5的透明值:

newmtl  default
illum 4
d 0.5
...

帧累加见之前章节的设置详述。

四、光追管线优化

上面的代码可运行,但有潜在BUG。原因是:阴影射线在最近命中着色器中执行traceRayEXT的调用时使用有效载荷 1,并且当与对象相交时,任何命中着色器将使用有效载荷 0 执行。此处,程序添加了填充并且没有任何问题,但不应该这样处理。

每次traceRayEXT调用时都应该具有与光线跟踪调用不同有效光路一样多的命中组。对于其他示例,没问题,因为我们使用了gl_RayFlagsSkipClosestHitShaderNV标志,并且不会调用最近的命中着色器(有效负载 0),并且命中组中没有任何命中或相交着色器。但在本例中,将跳过最近命中着色器,但不会跳过任何命中着色器。

为了解决这个问题,我们需要添加另一个命中组。

这是当前 SBT绑定表 。
在这里插入图片描述

并且我们需要将以下内容添加到光线追踪管线中,即之前 Hit Group 以及使用适当负载的新 AnyHit。

在这里插入图片描述

4.1 新着色器

创建两个新文件raytrace_0.ahit和raytrace_1.ahit,并重命名raytrace.ahit为raytrace_ahit.glsl

在raytrace_0.ahit添加以下代码

#version 460
#extension GL_GOOGLE_include_directive : enable

#define PAYLOAD_0
#include "raytrace_rahit.glsl"

并在raytrace_1.ahit中,替换PAYLOAD_0为PAYLOAD_1

然后在raytrace_ahit.glsl删除#version 460 并添加以下代码,以便我们有正确的布局。

#ifdef PAYLOAD_0
layout(location = 0) rayPayloadInNV hitPayload prd;
#elif defined(PAYLOAD_1)
layout(location = 1) rayPayloadInNV shadowPayload prd;
#endif

4.2 新的有效光路载荷

我们不能简单地为阴影射线有效光路载荷设置一个布尔值。我们还需要为随机函数添加seed种子 。

在raycommon.glsl文件中,添加以下结构

struct shadowPayload
{
  bool isHit;
  uint seed;
};

阴影有效光路荷载的作用是在最近命中和阴影未命中着色器中。首先,让我们修改raytraceShadow.rmiss:

#version 460
#extension GL_NV_ray_tracing : require
#extension GL_GOOGLE_include_directive : enable

#include "raycommon.glsl"

layout(location = 1) rayPayloadInNV shadowPayload prd;

void main()
{
  prd.isHit = false;
}

最近命中着色器raytrace.rchit需要改变payload的使用,还要调用traceRayEXT

将有效光路载荷替换为

layout(location = 1) rayPayloadNV shadowPayload prdShadow;

然后就在调用之前traceRayEXT,将值初始化为

prdShadow.isHit = true ;
prdShadow.seed = prd.seed;

在光线追踪之后,将种子值设置回主要光路荷载中

prd.seed = prdShadow.seed; 

并检查阴影射线是否击中了物体

if(prdShadow.isHit) 

4.3 执行traceRayEXT

当我们调用 时traceRayEXT,由于我们使用的是有效载荷 1(最后一个参数),因此我们还需要跟踪来命中替代命中组,即使用有效载荷 1 的组。为此,我们需要将 sbtRecordOffset 设置为 1

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

4.4 光线追踪管线

最后一步是添加新的 Hit Group。在createRtPipeline()中我们需要加载新的 any hit 着色器并创建一个新的 Hit Group。

换"shaders/raytrace.rahit.spv"为"shaders/raytrace_0.rahit.spv"

加载新的着色器模块,代码如下:

  enum StageIndices
  {
    eRaygen,
    eMiss,
    eMiss2,
    eClosestHit,
    eAnyHit,
    eAnyHit2,
    eShaderGroupCount
  };

  // Hit Group - Any Hit
  stage.module = nvvk::createShaderModule(m_device, nvh::loadFile("spv/raytrace_0.rahit.spv", true, defaultSearchPaths, true));
  stage.stage     = VK_SHADER_STAGE_ANY_HIT_BIT_KHR;
  stages[eAnyHit] = stage;
  //
  stage.module = nvvk::createShaderModule(m_device, nvh::loadFile("spv/raytrace_1.rahit.spv", true, defaultSearchPaths, true));
  stage.stage     = VK_SHADER_STAGE_ANY_HIT_BIT_KHR;
  stages[eAnyHit2] = stage;

在创建第一个 Hit Group 后,创建一个新的 Hit Group,其中仅添加使用 payload 1 的 any hit。因为我们在光追调用中需要跳过最近命中着色器,所以我们可以在命中组中忽略它。

  // Payload 1
  group.type             = VK_RAY_TRACING_SHADER_GROUP_TYPE_TRIANGLES_HIT_GROUP_KHR;
  group.generalShader    = VK_SHADER_UNUSED_KHR;
  group.closestHitShader = VK_SHADER_UNUSED_KHR;
  group.anyHitShader     = eAnyHit2;
  m_rtShaderGroups.push_back(group);

标签:12,Hit,Tracing,VK,Shader,uint,命中,着色器,raytrace
来源: https://blog.csdn.net/qq_35312463/article/details/121521875

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有