将HLSL射线追踪到Vulkan

Bringing HLSL Ray Tracing to Vulkan

Vulkan标志

DirectX光线跟踪(DXR)允许您使用光线跟踪而不是传统的光栅化方法渲染图形。这个API是NVIDIA和微软在2018年创建的。

几个月后,NVIDIA发布了其Turing GPU架构,在硬件上提供了本地支持,以加速光线跟踪工作负载。从那以后,光线追踪生态系统一直在稳步发展。多个使用DXR的AAA游戏标题已经公布和发布,以及行业标准的可视化工具。

与DXR一起,NVIDIA发布了NVIDIA VKRay Vulkan供应商扩展,并公开了相同级别的光线跟踪功能。有几个Vulkan游戏使用NVIDIA VKRay,包括Quake2 RTX、JX3(MMO)和Wolfenstein:Youngblood。

Porting DirectX Content to Vulkan

来自Khronos集团的Vulkan API是跨平台的,可以在不同的平台和设备上获得广泛的受众。许多开发人员将内容从DirectX移植到Vulkan,以利用这一更广泛的市场范围。但是,移植标题需要同时移植API调用(到Vulkan)和着色器(到SPIR-V)。

虽然大多数isv可以通过一些合理的努力来移植3D API调用,但用另一种着色语言重写HLSL着色器是一项重要的任务。着色器源代码可能经过多年的发展。在某些情况下,也会动态生成材质球。因此,将HLSL着色器源代码转换为SPIR-V供Vulkan执行的跨平台编译器对开发人员非常有吸引力。

谷歌开发的一个这样的工具是微软开源DirectXCompiler(DXC)的SPIR-V后端。在过去的几年中,这个编译器已经成为将HLSL内容带到Vulkan的常见的、生产就绪的解决方案。Khronos最近在一篇文章中讨论了在Vulkan中使用HLSL的更多背景,HLSL是一种一流的Vulkan着色语言。

现在,结合了HLSL和光线跟踪在Vulkan中的使用,NVIDIA在NVIDIA VKRay扩展下的SPV_NV_ray_Tracing扩展下为DXC的SPIR-V后端添加了光线跟踪支持。我们还为多供应商扩展提供了上游支持,SPV_KHR_ray_tracing。

NVIDIA VKRay example

以下是如何在现有应用程序中使用HLSL着色器,该应用程序是在NVIDIA工程师Martin Karl Lefrançois和Pascal Gautron编写的Vulkan光线跟踪教程中创建的。

以下代码显示了HLSL最近命中着色器,该着色器使用示例应用程序中的单点光源计算阴影:

#include "raycommon.hlsl"

#include "wavefront.hlsl"



struct MyAttrib

{

        float3 attribs;

};



struct Payload

{

   bool isShadowed;

};



[[vk::binding(0,0)]] RaytracingAccelerationStructure topLevelAS;



[[vk::binding(2,1)]] StructuredBuffer<sceneDesc> scnDesc;



[[vk::binding(5,1)]] StructuredBuffer<Vertex> vertices[];



[[vk::binding(6,1)]] StructuredBuffer<uint> indices[];





[[vk::binding(1,1)]] StructuredBuffer<WaveFrontMaterial> materials[];



[[vk::binding(3,1)]] Texture2D textures[];

[[vk::binding(3,1)]] SamplerState samplers[];



[[vk::binding(4,1)]] StructuredBuffer<int> matIndex[];



struct Constants

{

        float4 clearColor;

        float3 lightPosition;

        float lightIntensity;

        int lightType;

};



[[vk::push_constant]] ConstantBuffer<Constants> pushC;



[shader("closesthit")]

void main(inout hitPayload prd, in MyAttrib attr)

{

  // Object of this instance

  uint objId = scnDesc[InstanceIndex()].objId;



  // Indices of the triangle

  int3 ind = int3(indices[objId][3 * PrimitiveIndex() + 0],

                    indices[objId][3
* PrimitiveIndex() + 1],

                    indices[objId][3
* PrimitiveIndex() + 2]);

  // Vertex of the triangle

  Vertex v0 = vertices[objId][ind.x];

  Vertex v1 = vertices[objId][ind.y];

  Vertex v2 = vertices[objId][ind.z];



  const float3 barycentrics = float3(1.0 - attr.attribs.x - 

  attr.attribs.y, attr.attribs.x, attr.attribs.y);



  // Computing the normal at hit position

  float3 normal = v0.nrm * barycentrics.x + v1.nrm * barycentrics.y


  v2.nrm * barycentrics.z;

  // Transforming the normal to world space

  normal = normalize((mul(scnDesc[InstanceIndex()].transfoIT 

           ,float4(normal,
0.0))).xyz);





  // Computing the coordinates of the hit position

  float3 worldPos = v0.pos * barycentrics.x + v1.pos *
barycentrics.y 

                    +
v2.pos * barycentrics.z;

  // Transforming the position to world space

  worldPos = (mul(scnDesc[InstanceIndex()].transfo,
float4(worldPos, 

              1.0))).xyz;



  // Vector toward the light

  float3  L;

  float lightIntensity = pushC.lightIntensity;

  float lightDistance  = 100000.0;



  // Point light

  if(pushC.lightType == 0)

  {

    float3 lDir      = pushC.lightPosition -
worldPos;

    lightDistance  = length(lDir);

    lightIntensity = pushC.lightIntensity / (lightDistance


                     lightDistance);

    L              =
normalize(lDir);

  }

  else  // Directional light

  {

    L = normalize(pushC.lightPosition - float3(0,0,0));

  }



  // Material of the object

  int               matIdx =
matIndex[objId][PrimitiveIndex()];

  WaveFrontMaterial mat    = materials[objId][matIdx];





  // Diffuse

  float3 diffuse = computeDiffuse(mat, L, normal);

  if(mat.textureId >= 0)

  {

    uint txtId = mat.textureId +
scnDesc[InstanceIndex()].txtOffset;

    float2 texCoord =

        v0.texCoord * barycentrics.x +
v1.texCoord * barycentrics.y + 

                  v2.texCoord
* barycentrics.z;

    diffuse *= textures[txtId].SampleLevel(samplers[txtId],
texCoord,

            0).xyz;

  }



  float3  specular    = float3(0,0,0);

  float attenuation = 1;



  // Tracing shadow ray only if the light is visible from the surface

  if(dot(normal, L) > 0)

  {

    float tMin   = 0.001;

    float tMax   = lightDistance;

    float3  origin = WorldRayOrigin() + WorldRayDirection()


        RayTCurrent();

    float3  rayDir = L;

    uint  flags =

        RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH


        RAY_FLAG_FORCE_OPAQUE |

        RAY_FLAG_SKIP_CLOSEST_HIT_SHADER;



    RayDesc desc;

    desc.Origin = origin;

    desc.Direction = rayDir;

    desc.TMin = tMin;

    desc.TMax = tMax;



    Payload shadowPayload;

    shadowPayload.isShadowed = true;

    TraceRay(topLevelAS,

             flags,

             0xFF,

             0,

             0,

             1,

             desc,

             shadowPayload

    );



    if(shadowPayload.isShadowed)

    {

      attenuation = 0.9;

    }

    else

    {

      // Specular

      specular = computeSpecular(mat,
WorldRayDirection(), L, normal);

    }

  }



  prd.hitValue = float3(lightIntensity * attenuation * (diffuse


  specular));

}

Translating
to SPIR-V

以下是转换中几个有趣的部分:

资源绑定

入口点

入口点参数

转换为本义

ShaderBufferRecord(也称为用户SBT数据

Resource
binding

在遮挡阴影的顶部,HLSL中有一个用于光线跟踪的新基本类型声明:

[[vk::binding(0,0)]] RaytracingAccelerationStructure topLevelAS;

DirectX使用全局路径签名作为资源绑定的机制。对于Vulkan,[[vk::binding]]是一个特殊的注释,用于设置资源的绑定点和描述符集位置。此注释将分别转换为SPIR-V绑定和描述符集修饰,生成DXIL时将忽略这些修饰。

您还可以继续使用register(xX,spaceY)语义,该语义将映射到绑定和描述符集装饰。有关注释和映射的完整列表的信息,请参阅HLSL到SPIR-V功能映射手册。

RaytracingAccelerationStructure直接映射到SPIR-V操作码

OpTypeAccelerationStructureNV/OpTypeAcccelerationStructureKHR。

Entry points

着色器入口点类似于以下代码示例:

[shader("closesthit")]

void main(inout hitPayload prd, in MyAttrib attr)

DXR HLSL着色器不使用特定的配置文件进行编译,而是编译为着色器库(lib_6_*profiles)。这允许在单个文件中显示不同光线跟踪阶段的数百个入口点。要指定特定阶段,请使用以下注释:

[shader(“<stage>”)] 

如果<stage>可以是以下任何值,请使用它:

raygeneration, intersection, closesthit, anyhit, miss

这些着色器库被转换为SPIR-V,在单个blob中具有多个入口点。对于上述入口点,SPIR-V代码如下所示:

OpEntryPoint ClosestHitNV %main "main" %gl_InstanceID %gl_PrimitiveID %5 %6 %7

Entry point arguments

void main(inout hitPayload prd, in MyAttrib attr)

DXR HLSL为光线跟踪阶段的每个入口点的参数数量和类型指定特定的规则。例如,在最近的命中着色器中,两个参数都必须是用户定义的结构类型。第一个表示有效负载,第二个表示命中属性。DXR规范概述了一整套规则。

SPIR-V不允许着色器入口点具有参数。在转换过程中,将这些变量添加到全局范围,存储类分别为IncomingRayPayloadNV/IncomingRayPayloadKHR和hittattributenv/hittattributekhr。转移也要注意恰当的输入输出语义。

Translation of intrinsics

系统值内部函数(如InstanceIndex()到SPIR-V内置函数)有一对一的映射。有关映射的完整列表的详细信息,请参阅HLSL到SPIR-V功能映射手册。HLSL中的矩阵intrinsics ObjectToWorld3x4()和WorldToObject3x4()没有到SPIR-V内置的直接映射。对于这些,请使用原始的非转置SPIR-V内置项,并在转换过程中转置结果。

HLSL中的TraceRay()内部函数使用特定的预分离结构类型RayDesc。此类型填充了光线的几何信息,如原点、方向、参数最小值和最大值。optraceenv/OpTraceRayKHR操作需要将这些参数中的每一个作为单独的参数。下面的代码示例在转换期间按如下方式解压缩RayDesc结构。

OpTraceNV %245 %uint_13 %uint_255 %uint_0 %uint_0 %uint_1 %244 %float_0_00100000005 %192 %191 %uint_0
OpTraceRayKHR %245 %uint_13 %uint_255 %uint_0 %uint_0 %uint_1 %244 %float_0_00100000005 %192 %191 %uint_0

TraceRay()是模板化的内部函数,最后一个参数是有效负载。SPIR-V中没有模板。OpTraceNV/OpTraceRayKHR通过提供RayPayloadNV/RayPayloadKHR修饰变量的位置号来绕过此限制。这允许不同的调用使用不同的有效负载,从而模拟模板功能。在转换过程中,RayPayloadNV/RayPayloadKHR在执行copy-in和copy-out数据时生成具有唯一位置号的修饰变量,以保留TraceRay()调用的语义。

ShaderBufferRecord(也称为用户SBT数据)

NVIDIA的光线跟踪VKRay扩展允许使用着色器记录缓冲区块对光线跟踪着色器中的着色器绑定表(SBT)中的用户数据进行只读访问。有关更多信息,请参见Vulkan 1,2规范。在HLSL着色器中无法直接访问SBT数据。

若要公开此功能,请将[[vk::shader_record_nv]]/[[vk::shader_record_ext]]注释添加到ConstantBuffer/cbuffers声明:

struct S { float t; }

[[vk::shader_record_nv]]

ConstantBuffer<S> cbuf;

DXR为SBT中存在的每个着色器的绑定资源引入了本地根签名。我们没有在SPIR-V级别模拟本地根签名并在应用程序上强制执行一些契约,而是提供了对SBT内部用户数据部分的访问。这与支持VK_EXT_descriptor_indexing及其相应的SPIR-V功能RuntimeDescriptorArrayEXT一起,可以实现与本地根签名相同的效果,同时保持灵活性。下面是一个代码示例:

[[vk::binding(0,0)] Texture2D<float4> gMaterials[];

struct Payload { float4 Color; };

struct Attribs { float2 value; };

struct MaterialData { uint matDataIdx; };

[[vk::shader_record_nv]]

ConstantBuffer<MaterialData> cbuf;

void main(inout Payload prd, in Attribs bary)

{

    Texture2D tex = gMaterials[NonUniformResourceIndex(matDataIdx)]

    prd.Color += tex[bary.value];

}

根据我们的经验,这种机制与大多数DXR应用程序使用SBT的方式相当吻合。与模拟本地根签名的其他潜在方法相比,从应用程序方面处理它也更简单。

Generating SPIR-V using the Microsoft DXC compiler

通过运行以下命令,可以将早期的HLSL代码转换为针对KHR扩展的SPIR-V:

dxc.exe -T lib_6_4 raytrace.rchit.hlsl -spirv -Fo raytrace.rchit.spv -fvk-use-scalar-layout

要瞄准NV扩展,请运行以下命令:

dxc.exe -T lib_6_4 raytrace.rchit.hlsl -spirv -Fo raytrace.rchit.spv -fvk-use-scalar-layout -fspv-extension="SPV_NV_ray_tracing"

使用的选项如下:

-T lib_6_4:使用标准配置文件编译光线跟踪着色器。

-SPIR V:在SPIR-V中生成输出。

-Fo<filename>:从<filename>生成输出文件。

差不多了!您可以在源代码中插入生成的SPIR-V blob,并查看它是否按预期运行,如图2所示。如果您比较从HLSL或相应的GLSL生成的SPIR-V,它看起来非常相似。

Conclusion

NVIDIA VKRay扩展具有DXC编译器和SPIR-V后端,通过HLSL在Vulkan中提供与DXR中当前可用的相同级别的光线跟踪功能。现在,您可以使用DXR或NVIDIA VKRay开发光线跟踪应用程序,并使用最小化的着色器重新编写来部署到DirectX或Vulkan api。

我们鼓励您利用这种新的灵活性,并通过将光线跟踪标题带到Vulkan来扩展您的用户群。

References

将HLSL射线追踪到Vulkan的更多相关文章

  1. 视频系列:RTX实时射线追踪(下)

    视频系列:RTX实时射线追踪(下) Key things from part 4 光线有效载荷是从一个着色器传递到另一个着色器的结构. 这一切都发生在RTX的引擎下. 更小的有效载荷要好得多! 新的D ...

  2. 视频系列:RTX实时射线追踪(上)

    视频系列:RTX实时射线追踪(上) Video Series: Practical Real-Time Ray Tracing With RTX RTX在游戏和应用程序中引入了一个令人兴奋的和根本性的 ...

  3. NVIDIA Turing Architecture架构设计(下)

    NVIDIA Turing Architecture架构设计(下) GDDR6 内存子系统 随着显示分辨率不断提高,着色器功能和渲染技术变得更加复杂,内存带宽和大小在 GPU 性能中扮演着更大的角色. ...

  4. NVIDIA Turing Architecture架构设计(上)

    NVIDIA Turing Architecture架构设计(上) 在游戏市场持续增长和对更好的 3D 图形的永不满足的需求的推动下, NVIDIA 已经将 GPU 发展成为许多计算密集型应用的世界领 ...

  5. 卓越精Forsk.Atoll.v3.3.2.10366无线网络

    卓越精Forsk.Atoll.v3.3.2.10366无线网络 Atoll是法国 FORSK 公司开发的,是一个全面的.基于Windows的.支持2G.3G.4G多种技术,用户界面 友好的无线网络规划 ...

  6. C\C++代码优化的27个建议

    1. 记住阿姆达尔定律: funccost是函数func运行时间百分比,funcspeedup是你优化函数的运行的系数. 所以,如果你优化了函数TriangleIntersect执行40%的运行时间, ...

  7. 【转】C\C++代码优化的27个建议

    1. 记住阿姆达尔定律: funccost是函数func运行时间百分比,funcspeedup是你优化函数的运行的系数. 所以,如果你优化了函数TriangleIntersect执行40%的运行时间, ...

  8. <airsim文档学习> Street View Image, Pose, and 3D Cities Dataset

    原文地址:  https://github.com/amir32002/3D_Street_View 说明:个人学习笔记,翻译整理自github/airsim. 简介 该存储库共享包含6DOF相机姿态 ...

  9. [UE4]碰撞机制

    应用于两种情况: 一.射线追踪,LineTrace 1.射线来自某个Trace Channel 2.Trace Channel 默认有两个:Visibility(不是可见的意思.只是Channel名称 ...

随机推荐

  1. 【SpringBoot】Spring Boot

    Spring Boot是由Pribotal团队提供,设计用来简化新Spring应用的初始搭建和开发过程的开源框架. 随着Spring体系越来越庞大,各种配置也是越来越复杂,Spring Boot就是解 ...

  2. hdu4876 深搜+(随机枚举剪枝)

    题意:       给你n个数,让你从选择k个数,然后排成一个环(k个数的顺序随意,但是排成一个环后就不能变了),然后可以在这个环上任意的找连续w个数(w<=k),可以找多次,得到一个值等于当前 ...

  3. Python中Numpy模块的使用

    目录 NumPy ndarray对象 Numpy数据类型 Numpy数组属性 NumPy NumPy(Numerical Python) 是 Python 的一个扩展程序库,支持大量的维度数组与矩阵运 ...

  4. 神经网络与机器学习 笔记—Rosenblatt感知器收敛算法C++实现

    Rosenblatt感知器收敛算法C++实现 算法概述 自己用C++实现了下,测试的例子和模式用的都是双月分类模型,关于双月分类相关看之前的那个笔记: https://blog.csdn.net/u0 ...

  5. 分布式事务与Seate框架(1)——分布式事务理论

    前言 虽然在实际工作中,由于公司与项目规模限制,实际上所谓的微服务分布式事务都不会涉及,更别提单独部署构建Seata集群.但是作为需要不断向前看的我,还是有必要记录下相关的分布式事务理论与Seate框 ...

  6. 初探 Git Submodules

    之前一直想将一个 Git 仓库放到另一个 Git 仓库,有 Maven 多模块项目(Maven Multimodule Project)和 Gradle 多项目构建(Gradle Multiproje ...

  7. 【vue-03】组件化开发 component

    vue组件化思想 组件化是vue的一个重要思想 它提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构建我们的应用. 任何的应用都会被抽象成一颗组件树. 注册组件 组件的使用分成三个步骤:创建 ...

  8. MyBatis Generator(SSM Maven项目)

    mbg.xml 放在项目目录里 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE genera ...

  9. .NET平台系列目录

    本系列主要讲解微软.NET平台发展历程以及.NET框架技术.包含.NET Framework..NET Core.Xamarin..NET Standrad等技术与应用. 1..NET平台系列 .NE ...

  10. MzzTxx——博客目录

    准备阶段 团队介绍 需求分析 技术规格说明书 功能规格说明书 Alpha 阶段任务分配 团队贡献分分配方案 Scrum Meeting Alpha 2021.04.21 Scrum Meeting 0 ...