VulkanTutorial(8·Shader modules)

Shader modules

与早期的API不同,Vulkan中的着色器代码必须以字节码格式指定,而不是人类可读的语法,如GLSL和HLSL。这种字节码格式称为SPIR-V它是一种可用于编写图形和计算着色器的格式

使用像SPIR-V这样简单的字节码格式,不会面临其他供应商的驱动程序因语法错误而拒绝您的代码的风险,

SPIR-V二进制文件,并不意味着我们需要手工编写,Khronos已经发布了他们自己的独立于供应商的glslangValidator.exe编译器,该编译器将GLSL编译为SPIR-V

但我们将使用Google的glslc.exe,glslc的优点是它使用与GCC和Clang等知名编译器相同的参数格式,并且这两个工具都已包含在Vulkan SDK中,因此您无需下载任何额外的内容。

GLSL是一种具有C风格语法的着色语言。用它编写的程序有一个main函数,GLSL使用全局变量来处理输入和输出,而不是使用参数作为输入和返回值作为输出,该语言包括内置的矢量和矩阵处理功能

Vertex shader

顶点着色器处理每个传入的顶点。它将其属性,如position, color, normal and texture coordinates作为输入。输出是clip coordinate裁剪坐标中的最终位置以及需要传递给片段着色器的属性,这些值然后将被光栅化器在片段上插值以产生 smooth平滑的梯度

clip coordinate裁剪坐标(单位立方体)是来自顶点着色器的四维向量,随后通过将整个向量除以其最后一个分量w来将其转换为规范化的设备坐标(单位正方形)->屏幕坐标(长方形)

左边时帧缓冲坐标(像素坐标),右边时标准化设备坐标,它和opengl 不同,y坐标是相反的,z坐标的范围仅是0---1

对于我们的第一个三角形,我们将直接指定三个顶点的位置作为归一化的设备坐标

通常情况下,这些坐标或者其他顶点属性将存储在顶点缓冲区中,但在Vulkan中创建顶点缓冲区并填充数据并非易事

文件

在vs中新建.vert和.frag的后缀名文件(可以任意取)

首先指明#version的glsl版本

创建元素数量为3的vec2二维向量类型的位置数组,和颜色数组

每个顶点都会调用main()函数,内置的gl_VertexIndex变量包含当前顶点的索引,我们通过positions[gl_VertexIndex]去从数组访问顶点的位置,

并传入w分量,通过内置变量gl_Position用作vertex shader的输出

需要通过out将顶点颜色属性传给fragment shader,并在fragment shader中in匹配输入(不必同名只要location索引一致就行)

cpp 复制代码
#version 450

layout(location = 0) out vec3 fragColor;

vec2 positions[3] = vec2[](
    vec2(0.0, -0.5),
    vec2(0.5, 0.5),
    vec2(-0.5, 0.5)
);

vec3 colors[3] = vec3[](
    vec3(1.0, 0.0, 0.0),
    vec3(0.0, 1.0, 0.0),
    vec3(0.0, 0.0, 1.0)
);

void main() {
    gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
    fragColor = colors[gl_VertexIndex];
}

fragment shader

用fragment 填充屏幕上的区域,在这些fragment上调用fragment shader以产生frambuffer的color and depth

main函数会为每个片段调用,就像顶点着色器main函数会为每个顶点调用一样

没有内置变量来输出当前计算的颜色(GLSL中的颜色是4分量向量),必须为每个frambuffer指定自己的输出变量,比如这里的outColor

其中layout(location = 0)布局修饰符 指定frambuffer的索引

fragColor的值将自动 为三个顶点之间的片段插值,从而产生平滑的颜色渐变

cpp 复制代码
#version 450

layout(location = 0) in vec3 fragColor;

layout(location = 0) out vec4 outColor;

void main() {
    outColor = vec4(fragColor, 1.0);
}

编译为SPIR_V字节码

我们现在将使用glslc程序将这些代码编译成SPIR-V字节码

首先创建一个bat文件(批处理文件),并将它放在你的shader文件夹下,

在其中写入,保存后,双击运行,至此我们的目录下就编译完成了spv文件

这两个命令告诉glslc编译器读取GLSL源文件,并使用-o(输出)标志输出SPIR-V字节码文件

如果着色器包含语法错误,那么编译器将告诉您行号和问题

在命令行上编译着色器是最简单的选项之一,但是也可以用Vulkan SDK的libshaderc,这是一个用于从程序中将GLSL代码编译为SPIR-V的库

cpp 复制代码
....../glslc.exe .......vert -o vert.spv
....../glslc.exe .......frag -o frag.spv
pause

加载SPIR-V到程序中

在是时候将它们加载到我们的程序中,以便在某个时候将它们插入graphics pipeline

编写一个简单的readFile函数来从文件中加载二进制数据

对于输入流对象,我们指定两个标志

  • ate:从文件末尾开始阅读(我们可以使用读取位置来确定文件的tellg()大小并分配缓冲区)
  • binary:将文件读取为二进制文件(避免文本转换)

将读取的内容返回到由std::vector管理的字节数组中

cpp 复制代码
static std::vector<char> readFile(const std::string& filename) {
    std::ifstream file(filename, std::ios::ate | std::ios::binary);//文件输入流

    if (!file.is_open()) {
        throw std::runtime_error("failed to open file!");
    }

    size_t fileSize = (size_t)file.tellg();
    std::vector<char> buffer(fileSize);//读取到的字节数组缓冲

    file.seekg(0);
    file.read(buffer.data(), fileSize);

    file.close();

    return buffer;
}

在createGraphicsPipeline调用函数来加载两个着色器的字节码、

创建Shader modules

在将SPIR-V的字节码传递到pipeline之前,我们必须将其包装在VkShaderModule对象中

同样遵守info和create,需要指定字节大小,和指向缓冲区的指针code.data(),

但字节码指针是uint32_t指针而不是char指针。因此,我们需要使用reinterpret_cast来强制转换指针

在createGraphicsPipeline调用,

Shader modules就像一个包装器,包装了char code,将SPIR-V字节码编译并链接到由GPU执行的机器码在图形管道创建之前不会发生,

一旦pipeline创建完成,我们就可以vkDestroyShaderModule Shader modules,在createGraphicsPipeline所有指令后写

cpp 复制代码
VkShaderModule createShaderModule(const std::vector<char>& code) {
    VkShaderModuleCreateInfo createInfo{};
    createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
    createInfo.codeSize = code.size();
    createInfo.pCode = reinterpret_cast<const uint32_t*>(code.data());

    VkShaderModule shaderModule;
    if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) {
        throw std::runtime_error("failed to create shader module!");
    }

    return shaderModule;
}

VkPipelineShaderStageCreateInfo

要实际使用着色器,我们需要通过VkPipelineShaderStagetInfo结构将它们分配给特定的管道阶段

第一步是告诉Vulkan着色器将在哪个管道阶段使用,每个可编程阶段都有一个枚举值,比如VK_SHADER_STAGE_VERTEX_BIT和VK_SHADER_STAGE_FRAGMENT_BIT

module是Shader modules

pname指明要调用的函数(入口点)

cpp 复制代码
VkPipelineShaderStageCreateInfo vertShaderStageInfo{};
vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT;
vertShaderStageInfo.module = vertShaderModule;
vertShaderStageInfo.pName = "main";

VkPipelineShaderStageCreateInfo fragShaderStageInfo{};
fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
fragShaderStageInfo.module = fragShaderModule;
fragShaderStageInfo.pName = "main";
相关推荐
Winston Wood20 小时前
一个简单的例子,说明Matrix类的妙用
android·前端·图像处理·图形渲染
云渲染图科普匠4 天前
3d室内设计效果图渲染平台哪个好?瑞云快图怎么样?
3d·图形渲染·3dsmax
先知demons4 天前
antvG6如何实现节点动画、连线动画、切换节点图标
前端·javascript·vue.js·图形渲染·canva可画
3DCAT实时渲染云5 天前
边缘计算技术的优势与挑战
实时互动·边缘计算·图形渲染
大耳猫8 天前
Android OpenGL天空盒
android·kotlin·android studio·图形渲染
大耳猫10 天前
Android OpenGL触控反馈
android·kotlin·android studio·图形渲染
木市门11 天前
【GAMES101笔记速查——Lecture 16 Ray Tracing4】
图像处理·笔记·图形渲染
蒋灵瑜的笔记本11 天前
【OpenGL】创建窗口/绘制图形
开发语言·c++·图形渲染·opengl
boker_han12 天前
浅析Android中的Choreographer工作原理
android·图形渲染