探索 Vulkan 音视频技术(7):光线追踪示例

这个系列文章我们来介绍一位海外工程师如何探索 Vulkan 音视频技术,对于想要开始学习音视频技术的朋友,这些文章是份不错的入门资料,本篇介绍 Vulkan 光线追踪示例。

——来自公众号“关键帧Keyframe”的分享

光线追踪代表了渲染技术的范式转变,超越了传统的光栅化,转而模拟光的物理行为。SaschaWillems/Vulkan 仓库提供了全面的光线追踪示例集合,展示了使用 Vulkan 的 KHR_ray_tracing 扩展的硬件加速光线追踪的全部潜力。这些示例既可作为教育资源,也可作为开发者的实际实现参考,帮助开发者将光线追踪集成到自己的应用程序中。

Vulkan 的光线追踪扩展为现代 GPU 的光线追踪功能提供了低级别、显式的 API。与传统的光栅化不同,光线追踪通过模拟光在场景中的交互路径工作,从而实现更真实的光照、阴影、反射和折射。该仓库的光线追踪示例展示了如何有效地设置和使用这些功能,从基础实现到高级技术。

Vulkan 中的光线追踪实现依赖于几个关键组件:加速结构用于高效的光线-场景相交计算,着色器绑定表用于组织光线追踪着色器,以及专门的管线阶段用于光线生成、相交和着色。这些示例将带你了解每个组件,展示它们如何协同工作以创建令人惊叹的视觉效果。

1、核心光线追踪基础设施

该仓库通过其基础库组件为光线追踪开发提供了坚实的基础。base/VulkanRaytracingSample.h 和 base/VulkanRaytracingSample.cpp 中的 VulkanRaytracingSample 类作为所有光线追踪示例的基类,提供了加速结构创建、着色器绑定表管理和光线追踪管线设置的通用功能。

这个基础实现处理了光线追踪扩展的复杂初始化,包括:

  • 设备扩展加载,用于 VK_KHR_acceleration_structure 和 VK_KHR_ray_tracing_pipeline
  • 光线追踪特定 Vulkan 命令的函数指针检索
  • 光线追踪数据结构的通用缓冲区创建模式

通过构建这个基础,每个示例都可以专注于其特定的光线追踪技术,而无需重复样板代码。

2、基础光线追踪示例

raytracingbasic 示例提供了理解 Vulkan 中光线追踪的完美起点。它演示了从场景设置到最终图像生成的完整工作流程,使用光线追踪技术渲染一个简单的三角形。

2.1、加速结构

光线追踪性能的核心是加速结构(AS),它优化了光线-场景相交过程。该示例演示了两种类型:

  1. 底层加速结构(BLAS):包含实际的几何数据(顶点、三角形)。在 createBottomLevelAccelerationStructure() 中,示例为单个三角形创建了一个 BLAS:
// 为单个三角形设置顶点
struct Vertex {
    float pos[3];
};
std::vector<Vertex> vertices = {
    { {  1.0f,  1.0f, 0.0f } },
    { { -1.0f,  1.0f, 0.0f } },
    { {  0.0f, -1.0f, 0.0f } }
};
  1. 顶层加速结构(TLAS):包含底层结构的实例。createTopLevelAccelerationStructure() 函数展示了如何创建一个引用 BLAS 的 TLAS:
VkAccelerationStructureInstanceKHR instance{};
instance.transform = transformMatrix;
instance.instanceCustomIndex = 0;
instance.mask = 0xFF;
instance.accelerationStructureReference = bottomLevelAS.deviceAddress;

2.2、着色器绑定表

光线追踪使用与传统图形管线不同的着色器模型。光线追踪使用着色器绑定表(SBT)来组织着色器程序,而不是固定管线。createShaderBindingTable() 函数演示了如何创建这些表:

该示例使用的 SBT 布局简单但有效:

  • 光线生成着色器:从相机创建初始光线
  • 未命中着色器:处理未击中任何几何体的光线
  • 最近命中着色器:处理与几何体相交的光线

2.3、光线追踪管线

createRayTracingPipeline() 函数展示了如何设置光线追踪管线。与图形管线不同,光线追踪管线没有固定阶段,而是将着色器组织成组:

// 光线生成组
{
    shaderStages.push_back(loadShader(getShadersPath() + "raytracingbasic/raygen.rgen.spv", VK_SHADER_STAGE_RAYGEN_BIT_KHR));
    VkRayTracingShaderGroupCreateInfoKHR shaderGroup{};
    shaderGroup.type = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR;
    shaderGroup.generalShader = static_cast<uint32_t>(shaderStages.size()) - 1;
    shaderGroups.push_back(shaderGroup);
}

2.4、光线调度

实际的光线追踪发生在 buildCommandBuffers() 函数中,其中 vkCmdTraceRaysKHR 调度光线追踪工作:

vkCmdTraceRaysKHR(
    drawCmdBuffers[i],
    &raygenShaderSbtEntry,
    &missShaderSbtEntry,
    &hitShaderSbtEntry,
    &callableShaderSbtEntry,
    width,
    height,
    1);

这个命令每个像素发射一条光线,通过光线-场景相交创建最终图像。

3、高级光线追踪示例

该仓库包含了众多在基础之上构建的高级光线追踪示例:

3.1、光线查询集成

rayquery 示例演示了如何使用 VK_KHR_ray_query 扩展,该扩展允许在传统图形管线内进行光线追踪操作。这种混合方法非常适合将选择性光线追踪效果添加到现有的基于光栅化的应用程序中。

3.2、反射和阴影

raytracingreflections 和 raytracingshadows 示例展示了如何使用光线追踪实现真实的反射和阴影。与传统光栅化方法相比,这些技术显著提高了视觉质量。

3.3、glTF 场景集成

raytracinggltf 示例演示了完整 glTF 场景的光线追踪,展示了如何处理具有材质和纹理的复杂模型。由于 glTF 已成为网络上 3D 资产的标准格式,这个示例对于实际应用特别有价值。

3.4、可调用着色器

raytracingcallable 示例介绍了可调用着色器,它允许在光线追踪期间进行任意函数调用。这使得在光线追踪管线内可以进行复杂的着色计算和程序化内容生成。

4、性能考虑

光线追踪在计算上可能很昂贵,但该仓库包含了几个演示优化技术的示例:

4.1、着色器绑定表优化

raytracingsbtdata 示例展示了如何有效地组织着色器绑定表以最小化内存使用并提高缓存性能。正确的 SBT 组织对光线追踪性能至关重要。

4.2、位置获取优化

raytracingpositionfetch 示例演示了在光线追踪期间高效获取顶点位置的技术,减少了内存带宽需求并提高了性能。

4.3、纹理处理

raytracingtextures 示例展示了如何在光线追踪着色器中正确处理纹理,包括纹理坐标生成和采样。

5、着色器开发

光线追踪示例包括了各种 GLSL 着色器,演示了不同的光线追踪技术:

5.1、光线生成着色器

光线生成着色器(.rgen)从相机创建初始光线。它们通常为每个像素计算光线方向并将光线发射到场景中:

void main()
{
    vec2 uv = vec2(gl_LaunchIDEXT.xy) / vec2(gl_LaunchSizeEXT.xy - 1);
    vec3 origin = ubo.viewInverse[3].xyz;
    vec3 target = ubo.projInverse * vec4(uv * 2.0 - 1.0, 1.0, 1.0);
    vec3 direction = normalize(vec3(ubo.viewInverse * vec4(target, 0.0)));

    traceRayEXT(topLevelAS, 0, 0xFF, 0, 0, 0, origin, 0.0, direction, 1000.0, 0);
}

5.2、命中着色器

命中着色器(.rchit)处理光线-几何体相交。它们计算光照、材质,并且可以生成次级光线用于反射和折射:

void main()
{
    vec3 worldPos = vec3(gl_WorldRayOriginEXT) + gl_WorldRayDirectionEXT * gl_HitTEXT;
    vec3 normal = normalize(gl_ObjectRayDirectionEXT);

    // 简单的漫反射着色
    vec3 lightDir = normalize(vec3(1.0, 1.0, 1.0));
    float diffuse = max(dot(normal, lightDir), 0.0);

    imageStore(outputImage, ivec2(gl_LaunchIDEXT.xy), vec4(vec3(diffuse), 1.0));
}

5.3、未命中着色器

未命中着色器(.rmiss)处理未击中任何几何体的光线,通常实现环境光照或天空盒:

void main()
{
    vec3 dir = normalize(gl_WorldRayDirectionEXT);
    vec3 color = vec3(0.2, 0.3, 0.8) * max(dir.y, 0.0);
    imageStore(outputImage, ivec2(gl_LaunchIDEXT.xy), vec4(color, 1.0));
}

6、光线追踪入门

要开始处理光线追踪示例,请按照以下步骤操作:

  1. 确保硬件支持:光线追踪需要支持 Vulkan 光线追踪扩展的 GPU。在继续之前检查 GPU 的功能。
  2. 构建仓库:按照仓库 README 中的构建说明进行操作,确保在构建配置中启用光线追踪支持。
  3. 从基础示例开始:从 raytracingbasic 示例开始理解基本概念,然后再学习更高级的技术。

学习和提升音视频开发技术,推荐你加入我们的知识星球:【关键帧的音视频开发圈】

探索 Vulkan 音视频技术(7):光线追踪示例

版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。

(0)

相关推荐

发表回复

登录后才能评论