本文主要参考NVIDIA Vulkan Ray Tracing Tutorial教程,环境配置与程序均可参照此文档执行(个人水平有限,如有错误请参照原文)。
本部分主要展示如何使用相交着色器及不同的材质渲染不同的图元。
首先我们需要明白什么时候使用这个可选的着色器,具体可参照光追渲染管线部分的说明。
一、使用场景
本部分设计使用
- 在 BLAS 中添加 2000000 个轴对齐的边界框
- 场景中添加2种材质
- 每隔一个相交对象创建一个球体或一个立方体,并将使用上述两种材料之一。
因此,我们需要:
- 添加相交着色器 (.rint)
- 添加新的最近命中着色器 (.chit)
- 从VkAccelerationStructureGeometryAabbsDataKHR创建VkAccelerationStructureGeometryKHR。
二、创建数据
在 host_device.h 中,添加我们需要的结构体。首先是定义球体的结构体。并且它也可以用于定义AABB框。此信息将在相交着色器中检索并返回相交点信息。
struct Sphere
{
vec3 center;
float radius;
};
然后我们需要保存所有球体的 AABB 结构体,也用于创建 BLAS ( VK_GEOMETRY_TYPE_AABBS_KHR)。
struct Aabb
{
vec3 minimum;
vec3 maximum;
};
之后添加以下宏定义以区分球体和正方体
#define KIND_SPHERE 0
#define KIND_CUBE 1
以上所有信息都需要保存在缓冲区中,并且可供着色器访问。
std::vector<Sphere> m_spheres; //所有球体
nvvkBuffer m_spheresBuffer; //保存球体的缓冲区
nvvkBuffer m_spheresAabbBuffer; //所有 Aabb 的缓冲区
nvvkBuffer m_spheresMatColorBuffer; //多种材质
nvvkBuffer m_spheresMatIndexBuffer; //定义哪个球体使用哪种材料
最后,定义两个函数:一个创建球体,一个创建 BLAS 的中间数据,类似之前的函数objectToVkGeometryKHR()。
void createSpheres ();
auto sphereToVkGeometryKHR ();
之后将以随机位置和半径创建 2000000 个球体。并且根据球体定义创建 Aabb包围盒,并且两种材料将交替赋值给每个对象。且以上所有创建的信息都将移动到 Vulkan 缓冲区,以便求交和最近命中着色器可访问。
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};
// 球数据
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);
}
// 每个球体的轴对齐包围盒
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);
}
// 两种材质
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);
// 指定给每个球体的材质(shader中会根据i%2判断类型)
for(size_t i = 0; i < m_spheres.size(); i++)
{
matIdx[i] = i % 2;
}
// 创建所有缓冲区
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调试数据
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");
// 添加一个额外的实例来访问材质缓冲区
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);
}
之后不要忘记在destroyResources()中销毁缓冲区
m_alloc.destroy(m_spheresBuffer);
m_alloc.destroy(m_spheresAabbBuffer);
m_alloc.destroy(m_spheresMatColorBuffer);
m_alloc.destroy(m_spheresMatIndexBuffer);
我们需要一个新的底层加速结构 (BLAS) 来存储上述球体或正方体数据。为了提高效率并且由于所有的图元都是静态的,因此我们将他们都添加到单个 BLAS 中。
与原有管线中默认三角形图元相比,我们在求交着色器中改变的是基本图元的 Aabb 数据(参见 Aabb 结构)和几何类型 ( VK_GEOMETRY_TYPE_AABBS_KHR)。
//--------------------------------------------------------------------------------------------------
// 返回用于创建的BLAS光线追踪几何数据,包含所有球体
//
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);
// 设置加速结构需要用到的构建信息
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;
}
最后在main.cpp我们加载 OBJ 模型,我们可以用
//创建示例
helloVk.loadModel(nvh::findFile( " media/scenes/plane.obj " , defaultSearchPaths, true ));
helloVk.createSpheres( 2000000 );
⚠️注意:可能有更多的 OBJ 模型,但由于我们构建 TLAS 的方式,需要在所有这些模型之后添加球体。
场景变大,因此需要设置相机
CameraManip.setLookat (nvmath :: vec3f ( 20 , 20 , 20 ), nvmath :: vec3f ( 0 , 1 , 0 ), nvmath :: vec3f ( 0 , 1 , 0 ));
三、创建加速结构
3.1 BLAS
函数createBottomLevelAS()为每个 OBJ 创建一个 BLAS,以下修改将添加一个新的 BLAS,其中包含所有球体的 Aabb包围盒信息。
void HelloVulkan::createBottomLevelAS()
{
// BLAS - 将每个图元存储在几何体中
std::vector<nvvk::RaytracingBuilderKHR::BlasInput> allBlas;
allBlas.reserve(m_objModel.size());
for(const auto& obj : m_objModel)
{
auto blas = objectToVkGeometryKHR(obj);
// 我们可以在每个 BLAS 中添加多个几何体,目前我们只添加一个
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
同样在 createTopLevelAS() 中,顶部加速结构需要添加对球体 BLAS 的引用。我们将 instanceCustomId 和 blasId 设置为最后一个元素,这就是为什么必须在其他所有元素之后添加球体 BLAS。
之后将 hitGroupId设置为 1 。我们需要为这些基元添加一个新的命中组,因为我们需要像其他几何体一样计算图元属性,且这些自定义图元不像默认三角形图元那样管线会自动提供。
因为我们在创建自定义对象时添加了一个额外的实例,所以循环的元素少了一个。因此,循环现在应该像这样:
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];
...
}
在循环之后和构建 TLAS 之前,我们需要添加以下内容。
//在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);
}
该instanceCustomIndex给我们的最后一个元素m_instances,并在着色器中将能够访问分配给自定义对象的材质。
四、描述符
要访问新创建的包含所有球体的缓冲区,需要对描述符进行一些更改。
添加一个新的枚举到 Binding中
eImplicit = 3 , //所有自定义对象
描述符需要添加一个绑定到自定义对象的缓冲区。
//存储球体 (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);
updateDescriptorSet()写入缓冲区值的函数也需要修改。然后在纹理数组之后写入球体的缓冲区
VkDescriptorBufferInfo dbiSpheres{m_spheresBuffer. 缓冲区,0,VK_WHOLE_SIZE};
writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, eImplicit, &dbiSpheres));
五、新增着色器
相交着色器将被添加到命中组 VK_RAY_TRACING_SHADER_GROUP_TYPE_PROCEDURAL_HIT_GROUP_KHR中。在示例中,我们已经有了三角形的命中组和关联的最近命中着色器。我们将添加一个新的命中组,并将成为 Hit Group ID (1)。
这是一个两个命中组的创建:
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;
//最近命中着色器 + 相交点(命中组 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 相交着色器
每次光线命中场景的 Aabb 时都会调用此着色器。不过需要注意,在相交着色器中无法检索到 Aabb 信息。也不可能获得光线追踪器在 GPU 上计算出的命中点值。
我们唯一可以获取的信息是 :使用gl_PrimitiveID获取 Aabb 中被击中的图元是哪一个。然后,利用缓冲区中存储的信息,我们可以检索球体的几何信息。
在着色器中我们首先声明扩展名并包含公共文件。
#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"
以下是所有球体的拓扑结构,我们可以使用gl_PrimitiveID获取具体哪个。
layout(binding = 3, set = eImplicit, scalar) buffer allSpheres_
{
Sphere allSpheres[];
};
我们将针对入射光线实施两种求交方法。
struct Ray
{
vec3 origin;
vec3 direction;
};
球体交点
// 射线与球相交算法
// 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 交叉点
float hitAabb ( const Aabb aabb, const Ray r)
{
vec3 invDir = 1.0 / r. 方向;
vec3 tbot = invDir * (aabb. minimum - r. origin );
VEC3 TTOP = invDir *(AABB最大。 - R的原点);
vec3 tmin = min (ttop, tbot);
vec3 tmax =最大值(ttop,tbot);
float t0 = max (tmin. x , max (tmin. y , tmin.Ž));
float t1 = min (tmax. x , min (tmax. y , tmax. z ));
返回t1 > max (t0, 0.0 ) ? t0 : - 1.0 ;
}
// 光线-AABB包围盒 求交点
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;
}
如果没有命中,两者都返回-1,否则返回射线到原点的距离。
void main()
{
Ray ray;
ray.origin = gl_WorldRayOriginEXT;
ray.direction = gl_WorldRayDirectionEXT;
并且可以像这样获取有关包含在 Aabb 中的几何体的信息。
//球体数据
Sphere sphere = allSpheres.i[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);
}
使用reportIntersectionEXT函数可以获取相交着色器中获取到的相交信息,:交点与原点的距离和可用于区分原始类型的第二个参数 (hitKind)。
// Report hit point
if(tHit > 0)
reportIntersectionEXT(tHit, hitKind);
}
着色器整体代码如下:
#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 最近命中着色器
新增最近命中着色器如下:
#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));
}
新增命中组中的raytrace2.rchit这个着色器几乎与之前的raytrace.rchit着色器相同,但由于图元是自定义的,因此我们只需要计算被击中的图元的法线。
我们从光线中检索世界位置,并且使用的gl_HitTEXT会在相交着色器中设置。
vec3 worldPos = gl_WorldRayOriginEXT + gl_WorldRayDirectionEXT * gl_HitTEXT;
球体信息的检索方式与raytrace.rint着色器中的检索方式相同。
Sphere instance = allSpheres.i[gl_PrimitiveID];
然后我们像球体一样计算法线。
//计算命中位置的法线
vec3 normal = normalize(worldPos - instance.center);
要判断我们是否与立方体相交而不是球体相交,我们需要使用 gl_HitKindEXT数据(通过reportIntersectionEXT函数的第二个参数中设置)。
所以当这是一个立方体时,我们将法线设置为主轴。
// 如果命中交点返回值为 1,则计算立方体的法线
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));
}