将HLSL射线追踪到Vulkan
將HLSL射線追蹤到Vulkan
Bringing HLSL Ray Tracing to Vulkan
Vulkan標(biāo)志
DirectX光線跟蹤(DXR)允許您使用光線跟蹤而不是傳統(tǒng)的光柵化方法渲染圖形。這個API是NVIDIA和微軟在2018年創(chuàng)建的。
幾個月后,NVIDIA發(fā)布了其Turing GPU架構(gòu),在硬件上提供了本地支持,以加速光線跟蹤工作負(fù)載。從那以后,光線追蹤生態(tài)系統(tǒng)一直在穩(wěn)步發(fā)展。多個使用DXR的AAA游戲標(biāo)題已經(jīng)公布和發(fā)布,以及行業(yè)標(biāo)準(zhǔn)的可視化工具。
與DXR一起,NVIDIA發(fā)布了NVIDIA VKRay Vulkan供應(yīng)商擴(kuò)展,并公開了相同級別的光線跟蹤功能。有幾個Vulkan游戲使用NVIDIA VKRay,包括Quake2 RTX、JX3(MMO)和Wolfenstein:Youngblood。
Porting DirectX Content to Vulkan
來自Khronos集團(tuán)的Vulkan API是跨平臺的,可以在不同的平臺和設(shè)備上獲得廣泛的受眾。許多開發(fā)人員將內(nèi)容從DirectX移植到Vulkan,以利用這一更廣泛的市場范圍。但是,移植標(biāo)題需要同時移植API調(diào)用(到Vulkan)和著色器(到SPIR-V)。
雖然大多數(shù)isv可以通過一些合理的努力來移植3D API調(diào)用,但用另一種著色語言重寫HLSL著色器是一項重要的任務(wù)。著色器源代碼可能經(jīng)過多年的發(fā)展。在某些情況下,也會動態(tài)生成材質(zhì)球。因此,將HLSL著色器源代碼轉(zhuǎn)換為SPIR-V供Vulkan執(zhí)行的跨平臺編譯器對開發(fā)人員非常有吸引力。
谷歌開發(fā)的一個這樣的工具是微軟開源DirectXCompiler(DXC)的SPIR-V后端。在過去的幾年中,這個編譯器已經(jīng)成為將HLSL內(nèi)容帶到Vulkan的常見的、生產(chǎn)就緒的解決方案。Khronos最近在一篇文章中討論了在Vulkan中使用HLSL的更多背景,HLSL是一種一流的Vulkan著色語言。
現(xiàn)在,結(jié)合了HLSL和光線跟蹤在Vulkan中的使用,NVIDIA在NVIDIA VKRay擴(kuò)展下的SPV_NV_ray_Tracing擴(kuò)展下為DXC的SPIR-V后端添加了光線跟蹤支持。我們還為多供應(yīng)商擴(kuò)展提供了上游支持,SPV_KHR_ray_tracing。
NVIDIA VKRay example
以下是如何在現(xiàn)有應(yīng)用程序中使用HLSL著色器,該應(yīng)用程序是在NVIDIA工程師Martin Karl Lefran?ois和Pascal Gautron編寫的Vulkan光線跟蹤教程中創(chuàng)建的。
以下代碼顯示了HLSL最近命中著色器,該著色器使用示例應(yīng)用程序中的單點光源計算陰影:
#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 scnDesc;
[[vk::binding(5,1)]] StructuredBuffer vertices[];
[[vk::binding(6,1)]] StructuredBuffer indices[];
[[vk::binding(1,1)]] StructuredBuffer materials[];
[[vk::binding(3,1)]] Texture2D textures[];
[[vk::binding(3,1)]] SamplerState samplers[];
[[vk::binding(4,1)]] StructuredBuffer matIndex[];
struct Constants
{
float4 clearColor;float3 lightPosition;float lightIntensity;int lightType;
};
[[vk::push_constant]] ConstantBuffer 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{// Specularspecular = computeSpecular(mat,
WorldRayDirection(), L, normal);
}
}
prd.hitValue = float3(lightIntensity * attenuation * (diffuse
+
specular));
}
Translating to SPIR-V
以下是轉(zhuǎn)換中幾個有趣的部分:
資源綁定
入口點
入口點參數(shù)
轉(zhuǎn)換為本義
ShaderBufferRecord(也稱為用戶SBT數(shù)據(jù)
Resource binding
在遮擋陰影的頂部,HLSL中有一個用于光線跟蹤的新基本類型聲明:
[[vk::binding(0,0)]] RaytracingAccelerationStructure topLevelAS;
DirectX使用全局路徑簽名作為資源綁定的機(jī)制。對于Vulkan,[[vk::binding]]是一個特殊的注釋,用于設(shè)置資源的綁定點和描述符集位置。此注釋將分別轉(zhuǎn)換為SPIR-V綁定和描述符集修飾,生成DXIL時將忽略這些修飾。
您還可以繼續(xù)使用register(xX,spaceY)語義,該語義將映射到綁定和描述符集裝飾。有關(guān)注釋和映射的完整列表的信息,請參閱HLSL到SPIR-V功能映射手冊。
RaytracingAccelerationStructure直接映射到SPIR-V操作碼
OpTypeAccelerationStructureNV/OpTypeAcccelerationStructureKHR。
Entry points
著色器入口點類似于以下代碼示例:
[shader(“closesthit”)]
void main(inout hitPayload prd, in MyAttrib attr)
DXR HLSL著色器不使用特定的配置文件進(jìn)行編譯,而是編譯為著色器庫(lib_6_*profiles)。這允許在單個文件中顯示不同光線跟蹤階段的數(shù)百個入口點。要指定特定階段,請使用以下注釋:
[shader(“”)]
如果可以是以下任何值,請使用它:
raygeneration, intersection, closesthit, anyhit, miss
這些著色器庫被轉(zhuǎn)換為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為光線跟蹤階段的每個入口點的參數(shù)數(shù)量和類型指定特定的規(guī)則。例如,在最近的命中著色器中,兩個參數(shù)都必須是用戶定義的結(jié)構(gòu)類型。第一個表示有效負(fù)載,第二個表示命中屬性。DXR規(guī)范概述了一整套規(guī)則。
SPIR-V不允許著色器入口點具有參數(shù)。在轉(zhuǎn)換過程中,將這些變量添加到全局范圍,存儲類分別為IncomingRayPayloadNV/IncomingRayPayloadKHR和hittattributenv/hittattributekhr。轉(zhuǎn)移也要注意恰當(dāng)?shù)妮斎胼敵稣Z義。
Translation of intrinsics
系統(tǒng)值內(nèi)部函數(shù)(如InstanceIndex()到SPIR-V內(nèi)置函數(shù))有一對一的映射。有關(guān)映射的完整列表的詳細(xì)信息,請參閱HLSL到SPIR-V功能映射手冊。HLSL中的矩陣intrinsics ObjectToWorld3x4()和WorldToObject3x4()沒有到SPIR-V內(nèi)置的直接映射。對于這些,請使用原始的非轉(zhuǎn)置SPIR-V內(nèi)置項,并在轉(zhuǎn)換過程中轉(zhuǎn)置結(jié)果。
HLSL中的TraceRay()內(nèi)部函數(shù)使用特定的預(yù)分離結(jié)構(gòu)類型RayDesc。此類型填充了光線的幾何信息,如原點、方向、參數(shù)最小值和最大值。optraceenv/OpTraceRayKHR操作需要將這些參數(shù)中的每一個作為單獨的參數(shù)。下面的代碼示例在轉(zhuǎn)換期間按如下方式解壓縮RayDesc結(jié)構(gòu)。
OpTraceNV %245 %uint_13 %uint_255 %uint_0 %uint_0 %uint_1 %244 %float_0_00100000005 %192 %191 %uint_0OpTraceRayKHR %245 %uint_13 %uint_255 %uint_0 %uint_0 %uint_1 %244 %float_0_00100000005 %192 %191 %uint_0
TraceRay()是模板化的內(nèi)部函數(shù),最后一個參數(shù)是有效負(fù)載。SPIR-V中沒有模板。OpTraceNV/OpTraceRayKHR通過提供RayPayloadNV/RayPayloadKHR修飾變量的位置號來繞過此限制。這允許不同的調(diào)用使用不同的有效負(fù)載,從而模擬模板功能。在轉(zhuǎn)換過程中,RayPayloadNV/RayPayloadKHR在執(zhí)行copy-in和copy-out數(shù)據(jù)時生成具有唯一位置號的修飾變量,以保留TraceRay()調(diào)用的語義。
ShaderBufferRecord(也稱為用戶SBT數(shù)據(jù))
NVIDIA的光線跟蹤VKRay擴(kuò)展允許使用著色器記錄緩沖區(qū)塊對光線跟蹤著色器中的著色器綁定表(SBT)中的用戶數(shù)據(jù)進(jìn)行只讀訪問。有關(guān)更多信息,請參見Vulkan 1,2規(guī)范。在HLSL著色器中無法直接訪問SBT數(shù)據(jù)。
若要公開此功能,請將[[vk::shader_record_nv]]/[[vk::shader_record_ext]]注釋添加到ConstantBuffer/cbuffers聲明:
struct S { float t; }
[[vk::shader_record_nv]]
ConstantBuffer cbuf;
DXR為SBT中存在的每個著色器的綁定資源引入了本地根簽名。我們沒有在SPIR-V級別模擬本地根簽名并在應(yīng)用程序上強(qiáng)制執(zhí)行一些契約,而是提供了對SBT內(nèi)部用戶數(shù)據(jù)部分的訪問。這與支持VK_EXT_descriptor_indexing及其相應(yīng)的SPIR-V功能RuntimeDescriptorArrayEXT一起,可以實現(xiàn)與本地根簽名相同的效果,同時保持靈活性。下面是一個代碼示例:
[[vk::binding(0,0)] Texture2D gMaterials[];
struct Payload { float4 Color; };
struct Attribs { float2 value; };
struct MaterialData { uint matDataIdx; };
[[vk::shader_record_nv]]
ConstantBuffer cbuf;
void main(inout Payload prd, in Attribs bary)
{
Texture2D tex = gMaterials[NonUniformResourceIndex(matDataIdx)]prd.Color += tex[bary.value];
}
根據(jù)我們的經(jīng)驗,這種機(jī)制與大多數(shù)DXR應(yīng)用程序使用SBT的方式相當(dāng)吻合。與模擬本地根簽名的其他潛在方法相比,從應(yīng)用程序方面處理它也更簡單。
Generating SPIR-V using the Microsoft DXC compiler
通過運(yùn)行以下命令,可以將早期的HLSL代碼轉(zhuǎn)換為針對KHR擴(kuò)展的SPIR-V:
dxc.exe -T lib_6_4 raytrace.rchit.hlsl -spirv -Fo raytrace.rchit.spv -fvk-use-scalar-layout
要瞄準(zhǔn)NV擴(kuò)展,請運(yùn)行以下命令:
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:使用標(biāo)準(zhǔn)配置文件編譯光線跟蹤著色器。
-SPIR V:在SPIR-V中生成輸出。
-Fo:從生成輸出文件。
差不多了!您可以在源代碼中插入生成的SPIR-V blob,并查看它是否按預(yù)期運(yùn)行,如圖2所示。如果您比較從HLSL或相應(yīng)的GLSL生成的SPIR-V,它看起來非常相似。
Conclusion
NVIDIA VKRay擴(kuò)展具有DXC編譯器和SPIR-V后端,通過HLSL在Vulkan中提供與DXR中當(dāng)前可用的相同級別的光線跟蹤功能。現(xiàn)在,您可以使用DXR或NVIDIA VKRay開發(fā)光線跟蹤應(yīng)用程序,并使用最小化的著色器重新編寫來部署到DirectX或Vulkan api。
我們鼓勵您利用這種新的靈活性,并通過將光線跟蹤標(biāo)題帶到Vulkan來擴(kuò)展您的用戶群。
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
總結(jié)
以上是生活随笔為你收集整理的将HLSL射线追踪到Vulkan的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 10分钟内基于gpu的目标检测
- 下一篇: 低层级GPU虚拟内存管理引论