在 Vulkan 中使用 HLSL 着色器与 Vulkan-Hpp

此示例的源代码可以在 Khronos Vulkan 示例 github 存储库 中找到。
这是 API 示例的转码版本,说明了 vulkan.hpp 提供的 vulkan C++ 绑定的用法。

本教程以及随附的示例代码演示了如何在运行时使用 Vulkan-Hpp 在 Vulkan 中使用用高级着色语言 (HLSL) 编写的着色器。

Vulkan 不直接使用人类可读的文本格式的着色器,而是使用 SPIR-V 作为中间表示。这为使用除 GLSL 以外的其他着色器语言提供了选择,只要它们可以针对 Vulkan SPIR-V 环境。其中一种语言是 Microsoft 的 HLSL,它是 DirectX 的着色语言。

有关 HLSL 如何适应 Vulkan 生态系统的详细信息,请参见此Vulkan 指南章节

HLSL 语法

HLSL 比 GLSL 更面向对象,但着色器的总体结构类似。Vulkan 特定的函数使用隐式 vk 命名空间标记

struct VSInput
{
[[vk::location(0)]] float3 Pos : POSITION0;
[[vk::location(1)]] float2 UV : TEXCOORD0;
[[vk::location(2)]] float3 Normal : NORMAL0;
};

struct UBO
{
	float4x4 projection;
	float4x4 model;
	float4 viewPos;
};

cbuffer ubo : register(b0, space0) { UBO ubo; }

struct VSOutput
{
	float4 Pos : SV_POSITION;
[[vk::location(0)]] float2 UV : TEXCOORD0;
};

VSOutput main(VSInput input)
{
	VSOutput output = (VSOutput)0;
	output.UV = input.UV;
	output.Pos = mul(ubo.projection, mul(ubo.model, float4(input.Pos.xyz, 1.0)));
	return output;
}

Glslang

Vulkan 示例使用 Glslang 在运行时将着色器转换为 SPIR-V。Glslang 是参考 GLSL 验证器和转换器,但也支持 HLSL 作为输入语言。

不过,Glslang 中的 HLSL 支持有限,为了获得功能更完整的 HLSL 到 SPIR-V 编译器,您还可以使用 DirectX 着色器编译器

对于本教程中的基本着色器,我们可以使用 Glslang,因为它支持我们需要的所有功能。

将 HLSL 转换为 SPIR-V

使用 Glslang 加载 HLSL 类似于加载 GLSL,但需要不同的参数。以下是与从示例的 HlslShaders::load_hlsl_shader 函数加载 HLSL 不同的相关部分

std::vector<uint32_t> spirvCode;

// Use HLSL parsing rules and semantics (EShMsgReadHlsl)
EShMessages messages = static_cast<EShMessages>(EShMsgReadHlsl | ...);
...

// Language needs to be selected based on the shader stage
EShLanguage language = EShLangVertex;
glslang::TShader shader(language);
...

// Set the source language to HLSL
shader.setEnvInput(glslang::EShSourceHlsl, language, glslang::EShClientVulkan, 1);
...

// Parse the HLSL input
if (!shader.parse(&glslang::DefaultTBuiltInResource, 100, false, messages))
{
	...
}

// Add shader to new program object.
glslang::TProgram program;
program.addShader(&shader);

// Link program.
if (!program.link(messages))
{
	...
}
...

// Translate to SPIRV
glslang::TIntermediate *intermediate = program.getIntermediate(language);
...
glslang::GlslangToSpv(*intermediate, spirvCode, &logger);
...

创建着色器模块

glslang::GlslangToSpv 的调用将生成 SPIR-V 字节码,我们可以使用它来创建 Vulkan 着色器模块

vk::ShaderModuleCreateInfo module_create_info({}, spirvCode);
vk::ShaderModule           shader_module = get_device()->get_handle().createShaderModule(module_create_info);