将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. hdu 3062 基础的2sat

    题意: Party Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Total Sub ...

  2. 病毒木马查杀实战第025篇:JS下载者脚本木马的分析与防御

    前言 这次我与大家分享的是我所总结的关于JS下载者脚本木马的分析与防御技术.之所以要选择这样的一个题目,是因为在日常的病毒分析工作中,每天都会遇到这类病毒样本,少则几个,多则几十个(当然了,更多的样本 ...

  3. hdu3074 线段树求区间乘积(单点更新)

    题意:       给你n个数,两种操作,(1) 把第b个数改成c (2)算出b-c的乘积,结果对1000000007取余. 思路:       线段树单点更新,简单题目,不多解释,具体看代码. #i ...

  4. Web中的相对路径和绝对路径

    前台路径:浏览器端发起的资源请求路径 后台路径:服务器端发起的资源请求路径

  5. 远程分支git换地址了,本地重新关联

    由于本人把github远程仓库的名字修改了所以做了以下步骤修改 步骤:两步 (1)先把之前关联的git清除掉 git remote rm origin (2)再关联新的地址 git remote ad ...

  6. mac打开class文件

    本来不想写这个东西的.但是这个却费了我一番周折. 我要先声明一点的是,我从来不讲iOS当成一个单独的系统,而是将这个操作系统归位unix内核的系统. 简单来说,我把它当成linux在用. 但是,mac ...

  7. CRM系统实施的原则

    在我们使用CRM系统服务企业和客户之前,需要先系统的实施它.使用CRM系统却没有发挥它应有价值的案例很多,那么我们要怎样才能让CRM的作用发挥到最大,并确保它是成功的?那么今天小编跟您聊一聊,您的企业 ...

  8. C#是怎么跑起来的

    解释流程前,需要了解一些基本的概念. 基本概念解释: CPU :中央处理器,计算机的大脑,内部由数百万至数亿个晶体管组成,是解释和运行最终转换成机器语言(二进制代码)的地方.机器语言是通过CPU内存的 ...

  9. welcome实现首页路由的重定向效果

    welcome实现首页路由的重定向效果 1.创建welcome组件 2.在路由中引入组件并配置子组件 3.在home.vue中添加路由占位符 4.测试

  10. [刷题] PTA 03-树3 Tree Traversals Again

    用栈实现树遍历 1 #include<stdio.h> 2 #include<string.h> 3 #define MAXSIZE 30 4 5 int Pre[MAXSIZ ...