将HLSL射线追踪到Vulkan
将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
- VK_NV_ray_tracing
- VK_KHR_ray_tracing
- GLSL_NV_ray_tracing
- GLSL_EXT_ray_tracing
- DXC documentation for NV_ray tracing, KHR_ray_tracing
- HLSL shaders for the tutorial
将HLSL射线追踪到Vulkan的更多相关文章
- 视频系列:RTX实时射线追踪(下)
视频系列:RTX实时射线追踪(下) Key things from part 4 光线有效载荷是从一个着色器传递到另一个着色器的结构. 这一切都发生在RTX的引擎下. 更小的有效载荷要好得多! 新的D ...
- 视频系列:RTX实时射线追踪(上)
视频系列:RTX实时射线追踪(上) Video Series: Practical Real-Time Ray Tracing With RTX RTX在游戏和应用程序中引入了一个令人兴奋的和根本性的 ...
- NVIDIA Turing Architecture架构设计(下)
NVIDIA Turing Architecture架构设计(下) GDDR6 内存子系统 随着显示分辨率不断提高,着色器功能和渲染技术变得更加复杂,内存带宽和大小在 GPU 性能中扮演着更大的角色. ...
- NVIDIA Turing Architecture架构设计(上)
NVIDIA Turing Architecture架构设计(上) 在游戏市场持续增长和对更好的 3D 图形的永不满足的需求的推动下, NVIDIA 已经将 GPU 发展成为许多计算密集型应用的世界领 ...
- 卓越精Forsk.Atoll.v3.3.2.10366无线网络
卓越精Forsk.Atoll.v3.3.2.10366无线网络 Atoll是法国 FORSK 公司开发的,是一个全面的.基于Windows的.支持2G.3G.4G多种技术,用户界面 友好的无线网络规划 ...
- C\C++代码优化的27个建议
1. 记住阿姆达尔定律: funccost是函数func运行时间百分比,funcspeedup是你优化函数的运行的系数. 所以,如果你优化了函数TriangleIntersect执行40%的运行时间, ...
- 【转】C\C++代码优化的27个建议
1. 记住阿姆达尔定律: funccost是函数func运行时间百分比,funcspeedup是你优化函数的运行的系数. 所以,如果你优化了函数TriangleIntersect执行40%的运行时间, ...
- <airsim文档学习> Street View Image, Pose, and 3D Cities Dataset
原文地址: https://github.com/amir32002/3D_Street_View 说明:个人学习笔记,翻译整理自github/airsim. 简介 该存储库共享包含6DOF相机姿态 ...
- [UE4]碰撞机制
应用于两种情况: 一.射线追踪,LineTrace 1.射线来自某个Trace Channel 2.Trace Channel 默认有两个:Visibility(不是可见的意思.只是Channel名称 ...
随机推荐
- 洛谷P1085 不高兴的津津
题目描述 津津上初中了.妈妈认为津津应该更加用功学习,所以津津除了上学之外,还要参加妈妈为她报名的各科复习班.另外每周妈妈还会送她去学习朗诵.舞蹈和钢琴.但是津津如果一天上课超过八个小时就会不高兴,而 ...
- hdu1542 线段树扫描线求矩形面积的并
题意: 给你n个正方形,求出他们的所占面积有多大,重叠的部分只能算一次. 思路: 自己的第一道线段树扫描线题目,至于扫描线,最近会写一个总结,现在就不直接在这里写了,说下我的方 ...
- Python爬虫之使用正则表达式抓取数据
目录 匹配标签 匹配title标签 a标签 table标签 匹配标签里面的属性 匹配a标签里面的URL 匹配img标签里的 src 相关文章:Linux中的正则表达式 Python中的正则表达式 实例 ...
- Cauchy-Binet 公式的应用
Binet-Cauchy 公式 我们知道,方阵的行列式不是方阵的线性函数,即对 \(\forall \lambda\in F,A,B\in F^{n\times n}\),有 \(det(A+B)\n ...
- Python电子书分享
下载链接:链接:https://pan.baidu.com/s/1v004zaBfsEIF60oSgVq6sA 密码:i3aa 应用篇 下载链接:链接:https://pan.baidu.com/s/ ...
- 第二章 FreeBSD之开机关机命令
立即关机,但是不关闭电源 # shutdown -h now 立即关机,并且关闭电源 # shutdown -p now 重启命令 # shutdown -r now
- Getting Started and Beyond|云原生应用负载均衡选型指南
作者 冉昕,腾讯云服务网格TCM产品经理,现负责云原生流量接入网关与应用通信可观测性等产品特性策划与设计工作. 刘旭,腾讯云高级工程师,专注容器云原生领域,有多年大规模 Kubernetes 集群管理 ...
- .NET 反射(Reflection)
这节讲一下.NET 中的一个技术:反射(Reflection). 反射是一种很重要的技术,它可以在程序运行时,动态的获取类的实例,并调用实例中的任何方法.它就像一面镜子,映射出一个类的所有细节. ...
- 攻防世界 WriteUp
附:|>>>攻防世界-WEB-新手练习区<<<| WriteUp目录 01.|>>>baby_web<<<| 02.|>& ...
- Keepalived+nginx高可用
这种方法会把Keepalived进程结束掉,在教育机构学习到的方法,我个人对这种方法不认可. 参考: https://www.cnblogs.com/gshelldon/p/14504236.html ...