Vulkan shader模块使用

大家好,接下来将为大家介绍Vulkan shader模块使用。

与之前的图像API不同,Vulkan中的着色器代码必须以二进制字节码的格式使用,而不是像GLSLHLSL这样具有比较好的可读性的语法。此字节格式成为SPIR-V,它可以与Vulkan和OpenCL一同使用。这是一种可以编写图形和计算着色器的格式,但我们重点介绍本教程中Vulkan图形流水线使用的着色器。

使用二进制字节码格式的优点之一是 使得GPU厂商编写将着色器代码转换为本地代码的编译器复杂度减少了很多。经验表明使用可读性比较强的语法,比如GLSL一些GPU厂商相当灵活地理解这个标准。这导致一种情况会发生,比如编写好,并在一个厂商的GPU运行的不错的着色器程序,可能在其他的GPU厂商的GPU驱动程序运行异常,可能是语法的问题,或者更糟的是不同GPU厂商编写的编译器差异,导致着色器运行错误。如果直接使用编译好的二进制字节码格式,可以避免这种情况。

但是,并不意味着我们要手写字节码。Khronos发布了与厂商无关的编译器,它将GLSL编译成SPIR-V。该编译器用于验证着色器代码是否符合标准,并生成与Vulkan功能运行的SPRIR-V二进制文件。除此之外还可以将此编译器作为库在运行时编译生成SPRI-V,但在本教程中不会这样操作。编译器glslangValidator.exe包含在LunarG SDK中,因此不需要下载任何额外的内容。

GLSL是具有C风格语法的着色语言。在程序中需要定义编写main函数作为入口。GLSL不会使用输入参数和返回值作为输出,而是使用全局变量来处理输入和输出。该语言包括很多功能简化图形编程,比如内置的基于向量和矩阵的叉积操作函数,矩阵和矢量乘法操作函数。矢量类型为vec,数字表示分量的数量。例如3D位置存储在vec3中。可以通过诸如.x之类的成员访问单个分量,也可以通过多个分量创建一个新的向量。比如,表达式vec3(1.0, 2.0, 3.0).xy截取前两个分量,并赋予新的vec2中。向量的构造函数也可以采用矢量对象和标量值的组合。比如vec3可以用vec3(vec2(1.0, 2.0), 3.0)构造。

如前面提到的一样,我们需要编写一个vertex shader和一个fragment shader绘制三角形在屏幕。下面两个小节会探讨与之相关的GLSL代码,并展示如何生成两个SPIR-V二进制文件,最后加载到程序中。

顶点着色器:

顶点着色器处理每一个顶点数据。它的属性,如世界坐标,颜色,法线和纹理UV坐标作为输入。输出的是最终的clip coordinates 裁剪坐标和需要传递到片元着色器的属性,包括颜色和纹理UV坐标。这些值会在光栅化阶段进行内插值,以产生平滑的过度。

裁剪坐标 clip coordinate 是一个来此顶点着色器的思维向量,随后通过矢量最后一个分量进行整体归一化操作。这些归一化后的设备坐标是 homogeneous coordinates 最终映射到缓冲区范围为[-1, 1]的[-1, 1]坐标系统,如下所示:

如果之前的计算机图形比较熟悉的话,对这部分会比较熟悉。如果你之前使用过OpenGL,你会注意到Y坐标轴是反转的,Z坐标轴的范围与Direct3D是一致的范围,从0到1.

对于第一个三角形,我们不会做任何转换操作,我们将三个顶点的位置指定为归一化设备坐标,创建如下图形:

通常情况下顶点坐标数据是存储在一个顶点缓冲区中,但是在Vulkan中创建一个顶点缓冲区并填充数据的过程并不是直接的。所以我们后置这些步骤,直到我们满意的看到一个三角形出现在屏幕上。同时我们需要做一些非正统的事情:将坐标直接包含在顶点着色器的内部。代码如下所示:

#version 450
#extension GL_ARB_separate_shader_objects : enable

out gl_PerVertex {
    vec4 gl_Position;
};

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

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

main函数的执行应用于每个顶点,内置的gl_VertexIndex变量包含了当前顶点的索引信息。通常是顶点缓冲区的索引,但是在这里我们硬编码到顶点数据的集合中。每个顶点的位置从常量数组中访问,并与zw分量组合使用,以产生裁剪坐标中的有效位置信息。内置的gl_Position变量作为输出。最后Vulkan中使用shader,需要确保GL_ARG_separate_shader_objects扩展开启。

片元着色器:

由顶点着色器的位置数据形成的三角形用片段着色器填充屏幕上的区域中。片段着色器针对一个或者多个framebuffer帧缓冲区的每个片元产生具体的颜色和深度信息。一个简单的片段着色器为完成的三角形输出红色信息的代码如下:

#version 450
#extension GL_ARB_separate_shader_objects : enable

layout(location = 0) out vec4 outColor;

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

fragment sahder中的main函数与vertex shader中的main函数类似,会为每一个片元调用处理。颜色的信息在GLSL中是4个分量组成的矢量,包括R,G,B和Alpha通道,值域收敛在[0, 1]范围内。不像顶点着色器的gl_Position,它没有内置的变量为当前片元输出颜色信息。在这里必须为framebuffer定义输出变量,layout(location = 0)修饰符明确framebuffer的索引。红色信息写进outColor变量中,该变量链接第一个framebuffer中,索引为0

输入color

我们针对两个类型的着色器尝试做一些改变,完成上图的效果。首先,我们需要为每个顶点设置差异化的颜色。顶点着色器应该包含一个颜色数组,就像位置信息的数组一样:

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)
);

现在我们需要把每个顶点的颜色传递到片段着色器中,从而输出经过插值后的颜色信息到framebuffer中。为顶点着色器添增加输出颜色支持,在main函数中定义如下:

layout(location = 0) out vec3 fragColor;

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

下一步,我们需要将片段着色器的输入匹配顶点着色器的输出:

layout(location = 0) in vec3 fragColor;

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

输入的变量不一定要同名,它们将通过location索引指令链接在一起。main函数中修改将要输出的颜色alpha值。就像之前讨论的一样,fragColor将会为三个顶点所属的片元自动进行内插值,形成平滑的颜色过度。

发布了62 篇原创文章 · 获赞 71 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/u010281924/article/details/105397803
今日推荐