Vulkan_ Ray Tracing 11_ Intersection shader

This paper mainly refers to NVIDIA Vulkan Ray Tracing Tutorial Tutorials, environment configurations and programs can be implemented by referring to this document (personal level is limited, please refer to the original text if there are errors).

This section mainly shows how to render different elements with intersection shaders and different materials.
First, we need to know when to use this optional shader. For details, please refer to the description of light tracing rendering pipeline.

1, Usage scenario

This part is designed and used

  • Add 2000000 axis aligned bounding boxes to BLAS
  • Add 2 materials to the scene
  • Create a sphere or cube every other intersecting object, and use one of the above two materials.

Therefore, we need to:

  • Add intersection shader (. rint)
  • Add a new recent hit shader (. chit)
  • Create vkaccelerationstructureometrykhr from vkaccelerationstructureometryaabbsdatkhr.

2, Create data

At host_ In device. H, add the structure we need. The first is to define the structure of the sphere. And it can also be used to define the AABB box. This information retrieves and returns intersection point information in the intersection shader.

struct Sphere
{
  vec3  center;
  float radius;
};

Then we need to save the AABB structure of all spheres, which is also used to create Blas (vk_geometry_type_aabbs_khr).

struct Aabb
{
  vec3 minimum;
  vec3 maximum;
};

Then add the following macro definition to distinguish between sphere and cube

#define KIND_SPHERE 0
#define KIND_CUBE 1

All the above information needs to be saved in the buffer and can be accessed by the shader.

  std::vector<Sphere> m_spheres;                //All spheres
  nvvkBuffer m_spheresBuffer;          //Save buffer for sphere
  nvvkBuffer m_spheresAabbBuffer;      //Buffer for all AABS
  nvvkBuffer m_spheresMatColorBuffer;  //Multiple materials
  nvvkBuffer m_spheresMatIndexBuffer;  //Define which sphere uses which material

Finally, define two functions: one to create a sphere and the other to create intermediate data of BLAS, similar to the previous function objecttovkgeometriykhr().

  void  createSpheres ();
  auto  sphereToVkGeometryKHR ();

After that, 2000000 spheres are created at random positions and radii. The Aabb bounding box is created according to the sphere definition, and the two materials will be assigned to each object alternately. And all the information created above will be moved to the Vulkan buffer so that the intersection and recent hit shaders can be accessed.

void HelloVulkan::createSpheres(uint32_t nbSpheres)
{
  std::random_device                    rd{};
  std::mt19937                          gen{rd()};
  std::normal_distribution<float>       xzd{0.f, 5.f};
  std::normal_distribution<float>       yd{6.f, 3.f};
  std::uniform_real_distribution<float> radd{.05f, .2f};

  // Ball data
  m_spheres.resize(nbSpheres);
  for(uint32_t i = 0; i < nbSpheres; i++)
  {
    Sphere s;
    s.center     = nvmath::vec3f(xzd(gen), yd(gen), xzd(gen));
    s.radius     = radd(gen);
    m_spheres[i] = std::move(s);
  }

  //  Align the bounding box with the axis of each sphere
  std::vector<Aabb> aabbs;
  aabbs.reserve(nbSpheres);
  for(const auto& s : m_spheres)
  {
    Aabb aabb;
    aabb.minimum = s.center - nvmath::vec3f(s.radius);
    aabb.maximum = s.center + nvmath::vec3f(s.radius);
    aabbs.emplace_back(aabb);
  }

  // Two materials
  MaterialObj mat;
  mat.diffuse = nvmath::vec3f(0.2, 1, 0.2);
  std::vector<MaterialObj> materials;
  std::vector<int>         matIdx(nbSpheres);
  materials.emplace_back(mat);
  mat.diffuse = nvmath::vec3f(1, 0.5, 0);
  materials.emplace_back(mat);

  // The material assigned to each sphere (the type is determined by i%2 in the shader)
  for(size_t i = 0; i < m_spheres.size(); i++)
  {
    matIdx[i] = i % 2;
  }

  // Create all buffers
  using vkBU = VkBufferUsageFlagBits;
  nvvk::CommandPool genCmdBuf(m_device, m_graphicsQueueIndex);
  auto              cmdBuf = genCmdBuf.createCommandBuffer();
  m_spheresBuffer          = m_alloc.createBuffer(cmdBuf, m_spheres, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT);
  m_spheresAabbBuffer      = m_alloc.createBuffer(cmdBuf, aabbs,
                                             VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT
                                                 | VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR);
  m_spheresMatIndexBuffer =
      m_alloc.createBuffer(cmdBuf, matIdx, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT);
  m_spheresMatColorBuffer =
      m_alloc.createBuffer(cmdBuf, materials, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT);
  genCmdBuf.submitAndWait(cmdBuf);

  // Nsight debug data
  m_debug.setObjectName(m_spheresBuffer.buffer, "spheres");
  m_debug.setObjectName(m_spheresAabbBuffer.buffer, "spheresAabb");
  m_debug.setObjectName(m_spheresMatColorBuffer.buffer, "spheresMat");
  m_debug.setObjectName(m_spheresMatIndexBuffer.buffer, "spheresMatIdx");

  // Add an additional instance to access the material buffer  
  ObjDesc objDesc{};
  objDesc.materialAddress      = nvvk::getBufferDeviceAddress(m_device, m_spheresMatColorBuffer.buffer);
  objDesc.materialIndexAddress = nvvk::getBufferDeviceAddress(m_device, m_spheresMatIndexBuffer.buffer);
  m_objDesc.emplace_back(objDesc);

  ObjInstance instance{};
  instance.objIndex = static_cast<uint32_t>(m_objModel.size());
  m_instances.emplace_back(instance);
}

After that, don't forget to destroy the buffer in destroryresources()

  m_alloc.destroy(m_spheresBuffer); 
  m_alloc.destroy(m_spheresAabbBuffer); 
  m_alloc.destroy(m_spheresMatColorBuffer); 
  m_alloc.destroy(m_spheresMatIndexBuffer);

We need a new underlying acceleration structure (BLAS) to store the above sphere or cube data. In order to improve efficiency and because all elements are static, we add them to a single BLAS.

Compared with the default triangle entity in the original pipeline, what we change in the intersection shader are the Aabb data (see Aabb structure) and geometry type (VK_GEOMETRY_TYPE_AABBS_KHR) of the basic entity.

//--------------------------------------------------------------------------------------------------
// Returns the BLAS raytrace geometry data used to create, including all spheres
//
auto HelloVulkan::sphereToVkGeometryKHR()
{
  VkDeviceAddress dataAddress = nvvk::getBufferDeviceAddress(m_device, m_spheresAabbBuffer.buffer);  

  VkAccelerationStructureGeometryAabbsDataKHR aabbs{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_AABBS_DATA_KHR};
  aabbs.data.deviceAddress = dataAddress;
  aabbs.stride             = sizeof(Aabb);

  // Build information needed to set up the acceleration structure
  VkAccelerationStructureGeometryKHR asGeom{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR};
  asGeom.geometryType   = VK_GEOMETRY_TYPE_AABBS_KHR;
  asGeom.flags          = VK_GEOMETRY_OPAQUE_BIT_KHR;
  asGeom.geometry.aabbs = aabbs;

  VkAccelerationStructureBuildRangeInfoKHR offset{};
  offset.firstVertex     = 0;
  offset.primitiveCount  = (uint32_t)m_spheres.size();  
  offset.primitiveOffset = 0;
  offset.transformOffset = 0;

  nvvk::RaytracingBuilderKHR::BlasInput input;
  input.asGeometry.emplace_back(asGeom);
  input.asBuildOffsetInfo.emplace_back(offset);
  return input;
}

Finally, in main.cpp, we load the OBJ model, which we can use

  //Create sample
  helloVk.loadModel(nvh::findFile( " media/scenes/plane.obj " , defaultSearchPaths, true )); 
  helloVk.createSpheres( 2000000 );

⚠️ Note: there may be more OBJ models, but due to the way we build TLAS, we need to add spheres after all these models.

The scene becomes larger, so you need to set up the camera

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

3, Create acceleration structure

3.1 BLAS

The function createbotomlevelas() creates a BLAS for each OBJ. The following modifications will add a new BLAS, which contains the Aabb bounding box information of all spheres.

void HelloVulkan::createBottomLevelAS()
{
  // BLAS - stores each entity in geometry
  std::vector<nvvk::RaytracingBuilderKHR::BlasInput> allBlas;
  allBlas.reserve(m_objModel.size());
  for(const auto& obj : m_objModel)
  {
    auto blas = objectToVkGeometryKHR(obj);

    // We can add multiple geometry in each BLAS. At present, we only add one
    allBlas.emplace_back(blas);
  }

  // Spheres
  {
    auto blas = sphereToVkGeometryKHR();
    allBlas.emplace_back(blas);
  }

  m_rtBuilder.buildBlas(allBlas, VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR);
}

3.2 TLAS

Also in createTopLevelAS(), the top acceleration structure needs to add a reference to the sphere BLAS. We set instanceCustomId and blasId as the last element, which is why sphere BLAS must be added after all other elements.

Then set hitGroupId to 1. We need to add a new hit group for these primitives, because we need to calculate the element properties like other geometry, and these custom elements are not automatically provided by the pipeline like the default triangle element.

Because we added an additional instance when creating the custom object, there is one less element in the loop. So the loop should now look like this:

  auto nbObj = static_cast<uint32_t>(m_instances.size()) - 1;
  tlas.reserve(nbObj);
  for(uint32_t i = 0; i < nbObj; i++)
  {
      const auto& inst = m_instances[i];
      ...
  }

After the loop and before building TLAS, we need to add the following.

  //Add all custom objects in BLAS
  {
    VkAccelerationStructureInstanceKHR rayInst{};
    rayInst.transform           = nvvk::toTransformMatrixKHR(nvmath::mat4f(1));  // Position of the instance (identity)
    rayInst.instanceCustomIndex = nbObj;                                         // nbObj == last object == implicit
    rayInst.accelerationStructureReference = m_rtBuilder.getBlasDeviceAddress(static_cast<uint32_t>(m_objModel.size()));
    rayInst.instanceShaderBindingTableRecordOffset = 1;  // We will use the same hit group for all objects
    rayInst.flags                                  = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR;
    rayInst.mask                                   = 0xFF;  //  Only be hit if rayMask & instance.mask != 0
    tlas.emplace_back(rayInst);
  }

The instanceCustomIndex gives us the last element m_instances and will be able to access the materials assigned to custom objects in the shader.

4, Descriptor

To access the newly created buffer containing all spheres, you need to make some changes to the descriptor.

Add a new enumeration to the Binding

  eImplicit = 3 ,   //All custom objects

The descriptor needs to add a buffer bound to the custom object.

  //Storage sphere (binding = 3) 
  m_descSetLayoutBind.addBinding(eImplicit, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1 ,
                                 VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_INTERSECTION_BIT_KHR);

The function that updateDescriptorSet() writes the buffer value also needs to be modified. The sphere's buffer is then written after the texture array

  VkDescriptorBufferInfo dbiSpheres{m_spheresBuffer. Buffer, 0, VK_WHOLE_SIZE};
  writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, eImplicit, &dbiSpheres));

5, New shader

The intersection shader is added to the hit group VK_ RAY_ TRACING_ SHADER_ GROUP_ TYPE_ PROCEDURAL_ HIT_ GROUP_ In KHR. In the example, we already have a hit group of triangles and an associated recent hit shader. We will add a new hit group and will become Hit Group ID (1).

This is the creation of a two hit group:

  enum StageIndices
  {
    eRaygen,
    eMiss,
    eMiss2,
    eClosestHit,
    eClosestHit2,
    eIntersection,
    eShaderGroupCount
  };

  // Closest hit
  stage.module = nvvk::createShaderModule(m_device, nvh::loadFile("spv/raytrace2.rchit.spv", true, defaultSearchPaths, true));
  stage.stage          = VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR;
  stages[eClosestHit2] = stage;
  // Intersection
  stage.module = nvvk::createShaderModule(m_device, nvh::loadFile("spv/raytrace.rint.spv", true, defaultSearchPaths, true));
  stage.stage           = VK_SHADER_STAGE_INTERSECTION_BIT_KHR;
  stages[eIntersection] = stage;

  //Closest hit shader + intersection (hit group 2) 
  group.type = VK_RAY_TRACING_SHADER_GROUP_TYPE_PROCEDURAL_HIT_GROUP_KHR; 
  group.closestHitShader = eClosestHit2; 
  group.intersectionShader = eIntersection; 
  m_rtShaderGroups.push_back(group);

5.1 intersection shader

This shader is called every time a ray hits the scene's Aabb. Note, however, that Aabb information cannot be retrieved in the intersection shader. It is also impossible to obtain the hit point value calculated by the ray tracker on the GPU.

The only information we can get is: using gl_PrimitiveID gets which element is hit in Aabb. Then, using the information stored in the buffer, we can retrieve the geometric information of the sphere.

In the shader, we first declare the extension and include the public file.

#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"

The following is the topology of all spheres. We can use GL_ The primitiveid gets the specific.

layout(binding = 3, set = eImplicit, scalar) buffer allSpheres_
{
  Sphere allSpheres[];
};

We will implement two intersection methods for incident light.

struct Ray
{
  vec3 origin;
  vec3 direction;
};

Sphere intersection

// Ray and sphere intersection algorithm
// http://viclw17.github.io/2018/07/16/raytracing-ray-sphere-intersection/
float hitSphere(const Sphere s, const Ray r)
{
  vec3  oc           = r.origin - s.center;
  float a            = dot(r.direction, r.direction);
  float b            = 2.0 * dot(oc, r.direction);
  float c            = dot(oc, oc) - s.radius * s.radius;
  float discriminant = b * b - 4 * a * c;
  if(discriminant < 0)
  {
    return -1.0;
  }
  else
  {
    return (-b - sqrt(discriminant)) / (2.0 * a);
  }
}

Intersects the axis aligned bounding box

//Ray AABB intersection
float hitAabb ( const Aabb aabb, const Ray r)
{
vec3 invDir = 1.0 / r. direction;
vec3 tbot = invDir * (aabb. minimum - r. origin );
VEC3 TTOP = invDir * (AABB max. - origin of R);
vec3 tmin = min (ttop, tbot);
vec3 tmax = maximum (ttop, tbot);
float t0 = max (tmin. x , max (tmin. y , tmin.Ž));
float t1 = min (tmax. x , min (tmax. y , tmax. z ));
Return T1 > max (T0, 0.0)? t0 : - 1.0 ;
}

// Ray AABB bounding box intersection
float hitAabb(const Aabb aabb, const Ray r)
{
  vec3  invDir = 1.0 / r.direction;
  vec3  tbot   = invDir * (aabb.minimum - r.origin);
  vec3  ttop   = invDir * (aabb.maximum - r.origin);
  vec3  tmin   = min(ttop, tbot);
  vec3  tmax   = max(ttop, tbot);
  float t0     = max(tmin.x, max(tmin.y, tmin.z));
  float t1     = min(tmax.x, min(tmax.y, tmax.z));
  return t1 > max(t0, 0.0) ? t0 : -1.0;
}

If there is no hit, both return - 1, otherwise return the distance of the ray to the origin.

void main()
{
  Ray ray;
  ray.origin    = gl_WorldRayOriginEXT;
  ray.direction = gl_WorldRayDirectionEXT;

And information about the geometry contained in Aabb can be obtained like this.

  //Sphere data
  Sphere sphere = allSpheres.i[gl_PrimitiveID];

Now we just need to know whether the light hits the sphere or the cube.

  float tHit    = -1;
  int   hitKind = gl_PrimitiveID % 2 == 0 ? KIND_SPHERE : KIND_CUBE;
  if(hitKind == KIND_SPHERE)
  {
    // Sphere intersection
    tHit = hitSphere(sphere, ray);
  }
  else
  {
    // AABB intersection
    Aabb aabb;
    aabb.minimum = sphere.center - vec3(sphere.radius);
    aabb.maximum = sphere.center + vec3(sphere.radius);
    tHit         = hitAabb(aabb, ray);
  }

The reportIntersectionEXT function can be used to obtain the intersection information obtained in the intersection shader, including the distance between the intersection and the origin and the second parameter (hitKind) that can be used to distinguish the original type.

  // Report hit point
  if(tHit > 0)
    reportIntersectionEXT(tHit, hitKind);
}

The overall shader code is as follows:

#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"


layout(set = 1, binding = eImplicit, scalar) buffer allSpheres_
{
  Sphere allSpheres[];
};


struct Ray
{
  vec3 origin;
  vec3 direction;
};

// Ray-Sphere intersection
// http://viclw17.github.io/2018/07/16/raytracing-ray-sphere-intersection/
float hitSphere(const Sphere s, const Ray r)
{
  vec3  oc           = r.origin - s.center;
  float a            = dot(r.direction, r.direction);
  float b            = 2.0 * dot(oc, r.direction);
  float c            = dot(oc, oc) - s.radius * s.radius;
  float discriminant = b * b - 4 * a * c;
  if(discriminant < 0)
  {
    return -1.0;
  }
  else
  {
    return (-b - sqrt(discriminant)) / (2.0 * a);
  }
}

// Ray-AABB intersection
float hitAabb(const Aabb aabb, const Ray r)
{
  vec3  invDir = 1.0 / r.direction;
  vec3  tbot   = invDir * (aabb.minimum - r.origin);
  vec3  ttop   = invDir * (aabb.maximum - r.origin);
  vec3  tmin   = min(ttop, tbot);
  vec3  tmax   = max(ttop, tbot);
  float t0     = max(tmin.x, max(tmin.y, tmin.z));
  float t1     = min(tmax.x, min(tmax.y, tmax.z));
  return t1 > max(t0, 0.0) ? t0 : -1.0;
}

void main()
{
  Ray ray;
  ray.origin    = gl_WorldRayOriginEXT;
  ray.direction = gl_WorldRayDirectionEXT;

  // Sphere data
  Sphere sphere = allSpheres[gl_PrimitiveID];

  float tHit    = -1;
  int   hitKind = gl_PrimitiveID % 2 == 0 ? KIND_SPHERE : KIND_CUBE;
  if(hitKind == KIND_SPHERE)
  {
    // Sphere intersection
    tHit = hitSphere(sphere, ray);
  }
  else
  {
    // AABB intersection
    Aabb aabb;
    aabb.minimum = sphere.center - vec3(sphere.radius);
    aabb.maximum = sphere.center + vec3(sphere.radius);
    tHit         = hitAabb(aabb, ray);
  }

  // Report hit point
  if(tHit > 0)
    reportIntersectionEXT(tHit, hitKind);
}

5.2 recent hit shader

New recently hit shaders are as follows:

#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 {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 = 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(set = 1, binding = eImplicit, scalar) buffer allSpheres_ {Sphere i[];} allSpheres;

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

  vec3 worldPos = gl_WorldRayOriginEXT + gl_WorldRayDirectionEXT * gl_HitTEXT;

  Sphere instance = allSpheres.i[gl_PrimitiveID];

  // Computing the normal at hit position
  vec3 worldNrm = normalize(worldPos - instance.center);

  // Computing the normal for a cube
  if(gl_HitKindEXT == KIND_CUBE)  // Aabb
  {
    vec3  absN = abs(worldNrm);
    float maxC = max(max(absN.x, absN.y), absN.z);
    worldNrm   = (maxC == absN.x) ? vec3(sign(worldNrm.x), 0, 0) :
                                  (maxC == absN.y) ? vec3(0, sign(worldNrm.y), 0) : vec3(0, 0, sign(worldNrm.z));
  }

  // 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);
  vec3  specular    = vec3(0);
  float attenuation = 0.3;

  // 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
    {
      attenuation = 1;
      // Specular
      specular = computeSpecular(mat, gl_WorldRayDirectionEXT, L, worldNrm);
    }
  }

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

The newly added raytrace 2.rchit shader in the hit group is almost the same as the previous raytrace.rchit shader, but since the primitive is custom, we only need to calculate the normal of the hit primitive.

We retrieve the world position from the light and use the gl_HitTEXT is set in the intersection shader.

  vec3 worldPos = gl_WorldRayOriginEXT + gl_WorldRayDirectionEXT * gl_HitTEXT;

Sphere information is retrieved in the same way as in the raytrace.rint shader.

  Sphere instance = allSpheres.i[gl_PrimitiveID];

Then we calculate the normals like a sphere.

  //Calculates the normal of the hit location
  vec3 normal = normalize(worldPos - instance.center);

To determine whether we intersect a cube rather than a sphere, we need to use gl_HitKindEXT data (set in the second parameter of the reportIntersectionEXT function).

So when this is a cube, we set the normal to the principal axis.

  // If the hit intersection returns a value of 1, the normal of the cube is calculated
  if(gl_HitKindEXT == KIND_CUBE)  // Aabb
  {
    vec3  absN = abs(normal);
    float maxC = max(max(absN.x, absN.y), absN.z);
    normal     = (maxC == absN.x) ?
                 vec3(sign(normal.x), 0, 0) :
                 (maxC == absN.y) ? vec3(0, sign(normal.y), 0) : vec3(0, 0, sign(normal.z));
  }

Tags: Vulkan

Posted on Sat, 20 Nov 2021 13:12:14 -0500 by alex_bg