将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. php 二维数组排序详解: array_multisort

      定义和用法 array_multisort() 函数返回一个排序数组.您可以输入一个或多个数组.函数先对第一个数组进行排序,接着是其他数组,如果两个或多个值相同,它将对下一个数组进行排序. 注释: ...

  2. Docker Swarm删除节点

    节点上的主机如果想离开的话,可以自己直接执行docker swarm leave 然后你可以发现,原本跑在自己上面的容器被转移到别的容器上了.此时如果在manager节点上docker node ls ...

  3. JetBrains系列软件用法

    IDEA JSON格式化 IDEA的JSON_Formatter插件,下载地址 安装方式:File->Settings->Plugins,然后选择INstall plugin from d ...

  4. Java中浮点数的坑

    基本数据类型 浮点数存在误差 浮点数有一个需要特别注意的点就是浮点数是有误差的,比如以下这段代码你觉得输出的什么结果: public class Demo { public static void m ...

  5. python爬虫——《瓜子网》的广州二手车市场信息

    由于多线程爬取数据比单线程的效率要高,尤其对于爬取数据量大的情况,效果更好,所以这次采用多线程进行爬取.具体代码和流程如下: import math import re from concurrent ...

  6. 论文笔记:RankIQA

    0.Abstract 本文提出了一种从排名中学习的无参考图像质量评估方法(RankIQA).为了解决IQA数据集大小有限的问题,本文训练了一个孪生网络,通过使用合成的已知相对图像质量排名的数据集来训练 ...

  7. 解密华为云FusionInsight MRS新特性:一架构三湖

    摘要:华为云安全网关产品总监郭冕在"华为云TechWave云原生2.0专题日"上发表<华为云FusionInsight MRS,一个架构实现三种数据湖>的主题演讲,分享 ...

  8. jQuery清空元素和克隆元素

    1.清空 $(function () { $('#btn').click(function () { $('#ul1').html('') $('#ul1').empty() $('#ul1').re ...

  9. Build 2021 :正式发布.NET 6 Preview4

    微软在不断推进.NET 6的可用性,昨晚的Build 2021大会上发布了Preview4, 这是一个很大的版本更新,带来大量的功能,以及接近最终的产品交付质量,不过,这并不意味着可以在生产环境使用了 ...

  10. $(cd "$(dirname "$0")",pwd) 解析

    xx.sh 文件内容如下: #!/bin/bash BIN_FOLDER=$(cd "$(dirname "$0")";pwd) echo $BIN_FOLDE ...