本文主要参考NVIDIA Vulkan Ray Tracing Tutorial教程,环境配置与程序均可参照此文档执行(个人水平有限,如有错误请参照原文)。
一、可调用着色器
可调用着色器可以访问一个可调用的有效荷载,类似光线有效荷载值的使用来处理光追程序。 并且可调用着色器通过从一个允许的着色器阶段调用OpExecuteCallableKHR来执行。
光线追踪允许在光线生成,最近命中,未命中或其他可调用着色器阶段使用可调用着色器。它类似于间接函数调用,无需将这些着色器与可执行程序链接。
1.1 数据存储
数据只能访问从父阶段(上阶段)传入可调用对象的数据。每次只有一个结构传递,并且应该像有效载荷一样定义。
在父阶段(上阶段),使用callableDataEXT存储限定符数据,它可以声明为以下模样:
layout(location = 0) callableDataEXT rayLight cLight;
布局(位置 = 0) callableDataEXT rayLight cLight;
其中rayLight结构体在公共文件中定义为:
struct rayLight
{
vec3 inHitPosition;
float outLightDistance;
vec3 outLightDir;
float outIntensity;
};
之后在传入的可调用着色器中,您必须使用callableDataInEXT存储限定符数据。
layout(location = 0) callableDataInEXT rayLight cLight;
1.2 执行
要执行可调用着色器,父阶段(上阶段)需要调用executeCallableEXT函数
executeCallableEXT(pushC.lightType, 0);
- 第一个参数是 SBT 记录索引
- 第二个参数对应于“位置”索引
二、向 SBT 添加可调用着色器
2.1 创建着色器模块
在 HelloVulkan::createRtPipeline() 中,在添加最近命中着色器之后,我们将立即为每种类型的光添加 3 个可调用着色器。
首先,创建着色器模块
enum StageIndices
{
eRaygen,
eMiss,
eMiss2,
eClosestHit,
eCall0,
eCall1,
eCall2,
eShaderGroupCount
};
...
// Call0
stage.module = nvvk::createShaderModule(m_device, nvh::loadFile("spv/light_point.rcall.spv", true, defaultSearchPaths, true));
stage.stage = VK_SHADER_STAGE_CALLABLE_BIT_KHR;
stages[eCall0] = stage;
// Call1
stage.module = nvvk::createShaderModule(m_device, nvh::loadFile("spv/light_spot.rcall.spv", true, defaultSearchPaths, true));
stage.stage = VK_SHADER_STAGE_CALLABLE_BIT_KHR;
stages[eCall1] = stage;
// Call2
stage.module = nvvk::createShaderModule(m_device, nvh::loadFile("spv/light_inf.rcall.spv", true, defaultSearchPaths, true));
stage.stage = VK_SHADER_STAGE_CALLABLE_BIT_KHR;
stages[eCall2] = stage;
然后是 3 组可调用着色器以及与之相关的阶段。
// 可调用着色器
group.type = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR;
group.closestHitShader = VK_SHADER_UNUSED_KHR;
group.generalShader = eCall0;
m_rtShaderGroups.push_back(group);
group.generalShader = eCall1;
m_rtShaderGroups.push_back(group);
group.generalShader = eCall2;
m_rtShaderGroups.push_back(group);
2.2 着色器源码
light_point.rcall
#version 460 core
#extension GL_EXT_ray_tracing : enable
#extension GL_GOOGLE_include_directive : enable
#include "raycommon.glsl"
layout(location = 0) callableDataInEXT rayLight cLight;
layout(push_constant) uniform Constants
{
vec4 clearColor;
vec3 lightPosition;
float lightIntensity;
vec3 lightDirection;
float lightSpotCutoff;
float lightSpotOuterCutoff;
int lightType;
};
void main()
{
vec3 lDir = lightPosition - cLight.inHitPosition;
cLight.outLightDistance = length(lDir);
cLight.outIntensity = lightIntensity / (cLight.outLightDistance * cLight.outLightDistance);
cLight.outLightDir = normalize(lDir);
}
light_spot.rcall
#version 460 core
#extension GL_EXT_ray_tracing : enable
#extension GL_GOOGLE_include_directive : enable
#include "raycommon.glsl"
layout(location = 0) callableDataInEXT rayLight cLight;
layout(push_constant) uniform Constants
{
vec4 clearColor;
vec3 lightPosition;
float lightIntensity;
vec3 lightDirection;
float lightSpotCutoff;
float lightSpotOuterCutoff;
int lightType;
};
void main()
{
vec3 lDir = lightPosition - cLight.inHitPosition;
cLight.outLightDistance = length(lDir);
cLight.outIntensity = lightIntensity / (cLight.outLightDistance * cLight.outLightDistance);
cLight.outLightDir = normalize(lDir);
float theta = dot(cLight.outLightDir, normalize(-lightDirection));
float epsilon = lightSpotCutoff - lightSpotOuterCutoff;
float spotIntensity = clamp((theta - lightSpotOuterCutoff) / epsilon, 0.0, 1.0);
cLight.outIntensity *= spotIntensity;
}
light_inf.rcall
#version 460 core
#extension GL_EXT_ray_tracing : enable
#extension GL_GOOGLE_include_directive : enable
#include "raycommon.glsl"
layout(location = 0) callableDataInEXT rayLight cLight;
layout(push_constant) uniform Constants
{
vec4 clearColor;
vec3 lightPosition;
float lightIntensity;
vec3 lightDirection;
float lightSpotCutoff;
float lightSpotOuterCutoff;
int lightType;
};
void main()
{
cLight.outLightDistance = 10000000;
cLight.outIntensity = 1.0;
cLight.outLightDir = normalize(-lightDirection);
}
三、着色器绑定表
在本例中,我们将使用nvvk::SBTWrapper来创建光线追踪管线,并为着色绑定表创建缓冲区。
在头文件hello_vulkan.h中,需要添加其头文件并添加一个新成员。
#include "nvvk/sbtwrapper_vk.hpp"
...
nvvk::SBTWrapper m_sbtWrapper;
在 HelloVulkan::initRayTracing() 中,按以下方式对其进行初始化。
m_sbtWrapper.setup(m_device, m_graphicsQueueIndex, &m_alloc, m_rtProperties);
在HelloVulkan::createRtPipeline()中,在创建对vkCreateRayTracingPipelinesKHR()的管道调用之后,立即使用以下命令创建SBT。
m_sbtWrapper.create(m_rtPipeline, rayPipelineInfo);
在 HelloVulkan::raytrace() 中,我们必须告诉可调用着色器从哪里开始。由于它们是在命中着色器之后添加的,因此我们在 SBT 中添加以下内容。
SBT 封装类中可以直接返回我们需要的信息。因此,我们可以直接VkStridedDeviceAddressRegionKHR获取每个组类型的,而不是计算各种偏移量 。
四、调用着色器
在最近命中着色器中,我们现在可以根据光的类型直接调用对应的着色器,而不是使用 if-else 情况。
cLight.inHitPosition = worldPos;
//#define DONT_USE_CALLABLE
#if defined(DONT_USE_CALLABLE)
// Point light
if(pushC.lightType == 0)
{
vec3 lDir = pushC.lightPosition - cLight.inHitPosition;
float lightDistance = length(lDir);
cLight.outIntensity = pushC.lightIntensity / (lightDistance * lightDistance);
cLight.outLightDir = normalize(lDir);
cLight.outLightDistance = lightDistance;
}
else if(pushC.lightType == 1)
{
vec3 lDir = pushC.lightPosition - cLight.inHitPosition;
cLight.outLightDistance = length(lDir);
cLight.outIntensity =
pushC.lightIntensity / (cLight.outLightDistance * cLight.outLightDistance);
cLight.outLightDir = normalize(lDir);
float theta = dot(cLight.outLightDir, normalize(-pushC.lightDirection));
float epsilon = pushC.lightSpotCutoff - pushC.lightSpotOuterCutoff;
float spotIntensity = clamp((theta - pushC.lightSpotOuterCutoff) / epsilon, 0.0, 1.0);
cLight.outIntensity *= spotIntensity;
}
else // Directional light
{
cLight.outLightDir = normalize(-pushC.lightDirection);
cLight.outIntensity = 1.0;
cLight.outLightDistance = 10000000;
}
#else
executeCallableEXT(pushC.lightType, 0);
#endif
对应执行效果如下: