将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. showdan

    from shodan import Shodan import json api = Shodan("") result = api.search('windows') with ...

  2. Windows核心编程 第十一章 线程池的使用

    第11章 线程池的使用 第8章讲述了如何使用让线程保持用户方式的机制来实现线程同步的方法.用户方式的同步机制的出色之处在于它的同步速度很快.如果关心线程的运行速度,那么应该了解一下用户方式的同步机制是 ...

  3. SpringBoot日志输出定义

    在application.yml配置文件中添加 logging: level: root: INFO #根日志输出级别 com.juyss.dao: DEBUG #自定义包的日志输出级别 file: ...

  4. SSM项目使用Spring提供的测试

    在测试类上添加注解@RunWith(SpringJUnit4ClassRunner.class)和@ContextConfiguration(locations = {"classpath: ...

  5. Gridea博客无法载入CSS样式的解决办法

    今日在使用Gridea客户端更新博客的过程中,推送到远端仓库后内容显示正常,但是无法载入主题样式,就是没有载入CSS样式,折腾了一下午在搞懂问题出在哪里了,下面说一下自己的解决思路. 问题描述 首先, ...

  6. spring boot的ComponentScan和ServletComponentScan注解

    ComponentScan 这个注解可以扫描带@Component的类.众所皆知,@RestController和@Configuration和@Service和@Configuration等都有带C ...

  7. 数据人必读!玩转数据可视化用这个就够了——高德LOCA API 2.0升级来袭!

    引言 "一图胜千言",大数据时代来临,数据与人们生活密切相关.复杂难懂且体量庞大的数据给人的感觉总是冷冰冰的,让人难以获取到重点信息,也找不出规律和特征,数据价值发挥不出来.空间数 ...

  8. Sping AOP

    Sping AOP 1.什么是AOP 面向切面编程(AOP) 是 面向对象编程的补充(OOP) 传统的业务处理代码中,通常会惊醒事务处理.日志处理等操作.虽然可以使用OOP的组合或继承来实现代码重用, ...

  9. 【近取 Key】Alpha - v1.0 版本发布说明

    功能与特性 Alpha 版本虽然为本软件的第一代版本,但已基本覆盖了用户个人使用时的主要功能.除登陆注册与后台管理外,下文将分版块详细介绍面向用户的主要功能特性. 『产品主页』 潜在应用场景 场景 0 ...

  10. OO第1.2次作业·魔鬼的三角函数化简

    多年以后,面对办公室的屏幕,我会回忆起开始肝第二周OO作业的那个遥远的下午.那时的程序是一个一两百行的符号求导,基类与接口在包里一字排开,工整的注释一望到底 谁能想到,接下来的十几个小时我要经历什么样 ...