[译]Vulkan教程(13)图形管道基础之Shader模块

[译]Vulkan教程(13)图形管道基础之Shader模块

Shader modules

Unlike earlier APIs, shader code in Vulkan has to be specified in a bytecode format as opposed to human-readable syntax like GLSL and HLSL. This bytecode format is called SPIR-V and is designed to be used with both Vulkan and OpenCL (both Khronos APIs). It is a format that can be used to write graphics and compute shaders, but we will focus on shaders used in Vulkan's graphics pipelines in this tutorial.

与从前的API不同,Vulkan的shader代码必须用字节码格式来指定-不同于人类可读的语法-例如GLSL和HLSL。这个字节码格式被称为SPIR-V,设计为用于Vulkan和OpenCL(都是Khronos的API)。这个格式可用于写图形和计算shader,但是本教程中我们只关注Vulkan图形管道的shader。

The advantage of using a bytecode format is that the compilers written by GPU vendors to turn shader code into native code are significantly less complex. The past has shown that with human-readable syntax like GLSL, some GPU vendors were rather flexible with their interpretation of the standard. If you happen to write non-trivial shaders with a GPU from one of these vendors, then you'd risk other vendor's drivers rejecting your code due to syntax errors, or worse, your shader running differently because of compiler bugs. With a straightforward bytecode format like SPIR-V that will hopefully be avoided.

使用字节码的优势是,GPU厂商的编译器to转换shader代码为本地代码-复杂度显著降低。过去的经验标明,用人类刻度的语法-如GLSL,有的GPU厂商对标准的解读相当的可扩展(随心所欲?)。如果你碰巧写过复杂的shader-with这些厂商的GPU,那么你得冒险-其他厂商的驱动可能会拒绝你的代码-由于语法错误,甚至更糟,你的shader运行结果会不一样-由于编译器bug。用直截了当的字节码格式-如SPIR-V,这些问题有望避免。

However, that does not mean that we need to write this bytecode by hand. Khronos has released their own vendor-independent compiler that compiles GLSL to SPIR-V. This compiler is designed to verify that your shader code is fully standards compliant and produces one SPIR-V binary that you can ship with your program. You can also include this compiler as a library to produce SPIR-V at runtime, but we won't be doing that in this tutorial. The compiler is already included with the LunarG SDK as glslangValidator.exe, so you don't need to download anything extra.

但是,这不等于说我们需要手动写字节码了。Khronos发布了它们的厂商无关的编译器that将GLSL编译为SPIR-V。这个编译器可以验证你的shader代码是否完全符合标准and产生你需要的SPIR-V二进制代码。你可以将此编译器包含进你的库to在运行时生成SPIR-V,但是本教程中我们不会这样做。这个编译器在LunarG SDK里有as glslangValidator.exe文件,你不需额外下载。

GLSL is a shading language with a C-style syntax. Programs written in it have a main function that is invoked for every object. Instead of using parameters for input and a return value as output, GLSL uses global variables to handle input and output. The language includes many features to aid in graphics programming, like built-in vector and matrix primitives. Functions for operations like cross products, matrix-vector products and reflections around a vector are included. The vector type is called vec with a number indicating the amount of elements. For example, a 3D position would be stored in a vec3. It is possible to access single components through members like .x, but it's also possible to create a new vector from multiple components at the same time. For example, the expression vec3(1.0, 2.0, 3.0).xy would result in vec2. The constructors of vectors can also take combinations of vector objects and scalar values. For example, a vec3 can be constructed with vec3(vec2(1.0, 2.0), 3.0).

GLSL是类C语法的着色语言。它有个main 函数that在每个对象中被调用。Instead of用参数作为输入and用返回值作为输出,GLSL用全局变量处理输入输出问题。它有很多特性来支持图形编程,例如内置的向量和矩阵操作。函数例如叉积、点积、矩阵-向量积and围绕向量反射等都包含在内。向量类型是vec  with一个数字-表示元素数量。例如,一个3D位置可以保存在一个vec3中。可以通过成员如.x来读写单个变量,也可以创建新向量from多个成员。例如,表达式vec3(1.0, 2.0, 3.0).xy 会产生vec2。向量的构造函数可以接收向量和标量的组合。例如一个vec3 可以由vec3(vec2(1.0, 2.0), 3.0)构造出来。

As the previous chapter mentioned, we need to write a vertex shader and a fragment shader to get a triangle on the screen. The next two sections will cover the GLSL code of each of those and after that I'll show you how to produce two SPIR-V binaries and load them into the program.

如前所述,我们要写顶点shader和Fragment shader以在屏幕上绘制三角形。下2节将介绍其GLSL代码,之后我们将生成SPIR-V代码,加载它们到程序中。

Vertex shader 顶点shader

The vertex shader processes each incoming vertex. It takes its attributes, like world position, color, normal and texture coordinates as input. The output is the final position in clip coordinates and the attributes that need to be passed on to the fragment shader, like color and texture coordinates. These values will then be interpolated over the fragments by the rasterizer to produce a smooth gradient.

顶点shader处理每个顶点。他接收顶点的属性,例如世界坐标、颜色、法线和文理坐标-作为输入。输出是clip空间的位置和需要传送到Fragment shader的属性,例如颜色和文理坐标。这些值会被插值到Fragment by光栅器to生成平滑的渐变。

clip coordinate is a four dimensional vector from the vertex shader that is subsequently turned into a normalized device coordinate by dividing the whole vector by its last component. These normalized device coordinates are homogeneous coordinates that map the framebuffer to a [-1, 1] by [-1, 1] coordinate system that looks like the following:

clip坐标是四维向量from顶点shader that接下来被转换为标准设备坐标by用它的最后一个元素除整个向量。这些标准设备坐标是齐次坐标that将帧缓存映射到[-1, 1]by[-1, 1]坐标系统that如下图所示:

 [译]Vulkan教程(13)图形管道基础之Shader模块_第1张图片

 

You should already be familiar with these if you have dabbled in computer graphics before. If you have used OpenGL before, then you'll notice that the sign of the Y coordinates is now flipped. The Z coordinate now uses the same range as it does in Direct3D, from 0 to 1.

你应该已经熟悉这些了if你接触过计算机图形。If你用过OpenGL,你会注意到Y坐标的符号现在被翻转了。Z坐标现在用的范围与在Direct3D中相同,都是从0到1。

For our first triangle we won't be applying any transformations, we'll just specify the positions of the three vertices directly as normalized device coordinates to create the following shape:

For我们的第一个三角形,我们不使用任何变换,我们直接指定3个顶点的位置as标准设备坐标to创建下述形状:

 [译]Vulkan教程(13)图形管道基础之Shader模块_第2张图片

 

We can directly output normalized device coordinates by outputting them as clip coordinates from the vertex shader with the last component set to 1. That way the division to transform clip coordinates to normalized device coordinates will not change anything.

我们可以直接输出标准设备坐标by在顶点shader中将clip坐标的最后元素设置为1。这样,除法to变换clip坐标到标准设备坐标就不会改变任何东西。

Normally these coordinates would be stored in a vertex buffer, but creating a vertex buffer in Vulkan and filling it with data is not trivial. Therefore I've decided to postpone that until after we've had the satisfaction of seeing a triangle pop up on the screen. We're going to do something a little unorthodox in the meanwhile: include the coordinates directly inside the vertex shader. The code looks like this:

一般这些坐标会被保存在顶点buffer中,但是在Vulkan中创建顶点buffer并填入数据是很繁琐的。因此我决定推后它,until我们有了满意的可见三角形出现在屏幕上。同时我们要做一些有点非正统的事:在顶点shader中写入坐标。代码如下:

 1 #version 450
 2  
 3 vec2 positions[3] = vec2[](
 4     vec2(0.0, -0.5),
 5     vec2(0.5, 0.5),
 6     vec2(-0.5, 0.5)
 7 );
 8  
 9 void main() {
10     gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
11 }

 

The main function is invoked for every vertex. The built-in gl_VertexIndex variable contains the index of the current vertex. This is usually an index into the vertex buffer, but in our case it will be an index into a hardcoded array of vertex data. The position of each vertex is accessed from the constant array in the shader and combined with dummy z and w components to produce a position in clip coordinates. The built-in variable gl_Positionfunctions as the output.

函数main 为每个顶点调用一次。内置变量gl_VertexIndex 包含了当前顶点的索引。一般这是一个顶点buffer的索引,但在我们的案例中它是一个应变按摩的顶点数组的索引。每个订单的位置从这个常量数组中读取,结合傻傻的zw元素to产生clip坐标的位置。内置变量gl_Position是输出。

Fragment shader

The triangle that is formed by the positions from the vertex shader fills an area on the screen with fragments. The fragment shader is invoked on these fragments to produce a color and depth for the framebuffer (or framebuffers). A simple fragment shader that outputs the color red for the entire triangle looks like this:

由顶点shader中的位置构成的三角形占据了屏幕上的一块区域,其上每个点都成为一个Fragment。Fragment shader在这些Fragment调用to产生颜色和深度值for帧缓存(1个或多个帧缓存)。一个简单的Fragment shader that输出红色for整个三角形-看起来是这样的:

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

 

The main function is called for every fragment just like the vertex shader main function is called for every vertex. Colors in GLSL are 4-component vectors with the R, G, B and alpha channels within the [0, 1] range. Unlikegl_Position in the vertex shader, there is no built-in variable to output a color for the current fragment. You have to specify your own output variable for each framebuffer where the layout(location = 0) modifier specifies the index of the framebuffer. The color red is written to this outColor variable that is linked to the first (and only) framebuffer at index 0.

main 函数for每个fragment调用一次,就像顶点shader的main 函数for每个顶点调用一次。GLSL中的颜色是4元向量withRGBA通道在[0, 1]范围内。与顶点shader中的gl_Position不同,不存在内建变量to输出颜色到当前Fragment。你必须指定你自己ID输出变量for每个帧缓存where修饰符layout(location = 0)指定了帧缓存的索引。红色写入变量outColor  that链接到第一个(也是唯一一个)帧缓存at索引0

Per-vertex colors 逐顶点的颜色

Making the entire triangle red is not very interesting, wouldn't something like the following look a lot nicer?

让这个三角形为红色,并不有趣,下面这样的图不会更好一点吗?

 [译]Vulkan教程(13)图形管道基础之Shader模块_第3张图片

 

We have to make a couple of changes to both shaders to accomplish this. First off, we need to specify a distinct color for each of the three vertices. The vertex shader should now include an array with colors just like it does for positions:

我们必须对2个shader做出改变才能实现这个。首先,我们需要指定不同的颜色for每个顶点。顶点shader现在包含一个填写了颜色的数组,就像位置数组一样:

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

 

Now we just need to pass these per-vertex colors to the fragment shader so it can output their interpolated values to the framebuffer. Add an output for color to the vertex shader and write to it in the main function:

现在我们只需传递这些逐顶点的颜色到Fragment shader,这样它就可以输出插值的颜色到帧缓存。添加一个输出for颜色to顶点shader,在main 函数中写入它:

layout(location = 0) out vec3 fragColor;
 
void main() {
    gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
    fragColor = colors[gl_VertexIndex];
}

 

Next, we need to add a matching input in the fragment shader:

交流下,我们需要添加一个匹配的输入in Fragment shader:

layout(location = 0) in vec3 fragColor;
 
void main() {
    outColor = vec4(fragColor, 1.0);
}

 

The input variable does not necessarily have to use the same name, they will be linked together using the indexes specified by the location directives. The main function has been modified to output the color along with an alpha value. As shown in the image above, the values for fragColor will be automatically interpolated for the fragments between the three vertices, resulting in a smooth gradient.

输入变量不需要使用相同的名字,它们会被链接到一起using以location 指定的索引。函数main 被修改为输出颜色及其alpha值。如上图所示,变量fragColor  的值会在3个顶点之间被自动插值for Fragment,导致一个平滑的渐变效果。

Compiling the shaders 编译shader

Create a directory called shaders in the root directory of your project and store the vertex shader in a file called shader.vert and the fragment shader in a file called shader.frag in that directory. GLSL shaders don't have an official extension, but these two are commonly used to distinguish them.

创建文件夹shaders in你项目的根文件夹下,保存顶点shader到文件shader.vert ,保存Fragment shader到文件shader.frag 。GLSL shader没有官方扩展名,但是这2个是常用的to区分它们。

The contents of shader.vert should be:

shader.vert 的内容应当是:

 1 #version 450
 2 #extension GL_ARB_separate_shader_objects : enable
 3  
 4 layout(location = 0) out vec3 fragColor;
 5  
 6 vec2 positions[3] = vec2[](
 7     vec2(0.0, -0.5),
 8     vec2(0.5, 0.5),
 9     vec2(-0.5, 0.5)
10 );
11  
12 vec3 colors[3] = vec3[](
13     vec3(1.0, 0.0, 0.0),
14     vec3(0.0, 1.0, 0.0),
15     vec3(0.0, 0.0, 1.0)
16 );
17  
18 void main() {
19     gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
20     fragColor = colors[gl_VertexIndex];
21 }

 

And the contents of shader.frag should be:

shader.frag 的内容应当是:

 1 #version 450
 2 #extension GL_ARB_separate_shader_objects : enable
 3  
 4 layout(location = 0) in vec3 fragColor;
 5  
 6 layout(location = 0) out vec4 outColor;
 7  
 8 void main() {
 9     outColor = vec4(fragColor, 1.0);
10 }

 

We're now going to compile these into SPIR-V bytecode using the glslangValidator program.

现在我们要编译这些into SPIR-V字节码-使用glslangValidator 程序。

Windows

Create a compile.bat file with the following contents:

创建compile.bat 文件,写入如下内容:

C:/VulkanSDK/x.x.x.x/Bin32/glslangValidator.exe -V shader.vert
C:/VulkanSDK/x.x.x.x/Bin32/glslangValidator.exe -V shader.frag
pause

 

Replace the path to glslangValidator.exe with the path to where you installed the Vulkan SDK. Double click the file to run it.

将路径替换为glslangValidator.exe 的位置where你安装了Vulkan SDK。双击文件to运行它。

Linux

Create a compile.sh file with the following contents:

创建compile.sh 文件,写入如下内容:

/home/user/VulkanSDK/x.x.x.x/x86_64/bin/glslangValidator -V shader.vert
/home/user/VulkanSDK/x.x.x.x/x86_64/bin/glslangValidator -V shader.frag

 

Replace the path to glslangValidator with the path to where you installed the Vulkan SDK. Make the script executable with chmod +x compile.sh and run it.

将路径替换为glslangValidator.exe 的位置where你安装了Vulkan SDK。用chmod +x compile.sh 使它可执行,运行它。

End of platform-specific instructions 尾声of平台相关的指导

These two commands invoke the compiler with the -V flag, which tells it to compile the GLSL source files to SPIR-V bytecode. When you run the compile script, you'll see that two SPIR-V binaries are created: vert.spv andfrag.spv. The names are automatically derived from the type of shader, but you can rename them to anything you like. You may get a warning about some missing features when compiling your shaders, but you can safely ignore that.

这2个命令调用编译器with标志-V,which告诉编译器to编译GLSL代码to SPIR-V字节码。当年运行编译脚本,你将看到2个SPIR-V二进制文件:vert.spv 和frag.spv。名字是自动从shader类型继承来的,但是你可以重命名它们to任何你喜欢的名字。你可能收到警告about确实某些特性when编译你的shader,但是你可以安全地忽略它。

If your shader contains a syntax error then the compiler will tell you the line number and problem, as you would expect. Try leaving out a semicolon for example and run the compile script again. Also try running the compiler without any arguments to see what kinds of flags it supports. It can, for example, also output the bytecode into a human-readable format so you can see exactly what your shader is doing and any optimizations that have been applied at this stage.

If你的shader有如法错误,那么编译器会告诉你错误的行数和问题,如你所愿。尝试少写一个分号and再次运行编译脚本。也试试不带参数运行编译器to看看它支持哪些标志。例如,它可能也输出字节码into一个人类刻度的格式,以便你可以看到你的shader在做什么and这一阶段有哪些优化。

Compiling shaders on the commandline is one of the most straightforward options and it's the one that we'll use in this tutorial, but it's also possible to compile shaders directly from your own code. The Vulkan SDK includes libshaderc, which is a library to compile GLSL code to SPIR-V from within your program.

在命令行编译shader是最直观的方式,我们在本教程中就这么用,但是直接从你的代码编译shader也是可以的。Vulkan SDK包含了libshaderc,它是个库to编译GLSL代码到SPIR-V(在你的程序中)。

Loading a shader 加载shader

Now that we have a way of producing SPIR-V shaders, it's time to load them into our program to plug them into the graphics pipeline at some point. We'll first write a simple helper function to load the binary data from the files.

既然我们知道如何生成SPIR-V格式的shader了,是时候加载它们到我们的程序to将其插入图形管道的某处。我们先写一个简单的辅助函数to家族二进制数据from文件。

#include 
 
...
 
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!");
    }
}

 

The readFile function will read all of the bytes from the specified file and return them in a byte array managed by std::vector. We start by opening the file with two flags:

  • ate: Start reading at the end of the file
  • binary: Read the file as binary file (avoid text transformations)

函数readFile 会读所有的字节from指定的文件,返回它们到一个字节数组-由std::vector管理。我们打开文件with两个标志:

  • ate:开始从文件末尾读。
  • binary:将文件视为二进制文件来读取(避免文字转换)。

The advantage of starting to read at the end of the file is that we can use the read position to determine the size of the file and allocate a buffer:

从文件末尾开始读的优势是,我们可以用读取位置to决定文件的大小and申请buffer:

size_t fileSize = (size_t) file.tellg();
std::vector<char> buffer(fileSize);

 

After that, we can seek back to the beginning of the file and read all of the bytes at once:

之后,我们可以找回到文件的开始,一次读入所有字节:

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

 

And finally close the file and return the bytes:

最后关闭文件,返回字节数组:

file.close();
 
return buffer;

 

We'll now call this function from createGraphicsPipeline to load the bytecode of the two shaders:

我们现在调用这个函数from createGraphicsPipeline  to加载2个shader的字节码:

void createGraphicsPipeline() {
    auto vertShaderCode = readFile("shaders/vert.spv");
    auto fragShaderCode = readFile("shaders/frag.spv");
}

 

Make sure that the shaders are loaded correctly by printing the size of the buffers and checking if they match the actual file size in bytes. Note that the code doesn't need to be null terminated since it's binary code and we will later be explicit about its size.

确保shader被正确地加载by打印buffer的大小and检查if它们匹配文件的实际大小(字节)。注意,代码不需是null结尾,因为它是二进制代码and我们稍后会知道其大小。

Creating shader modules 创建shader模块

Before we can pass the code to the pipeline, we have to wrap it in a VkShaderModule object. Let's create a helper function createShaderModule to do that.

我们传递代码到pipeline之前,我们必须封装它为一个VkShaderModule 对象。我们来创建一个辅助函数createShaderModule 做这件事。

VkShaderModule createShaderModule(const std::vector<char>& code) {
 
}

 

The function will take a buffer with the bytecode as parameter and create a VkShaderModule from it.

这个函数接收一个字节码的数组,据此创建一个VkShaderModule 。

Creating a shader module is simple, we only need to specify a pointer to the buffer with the bytecode and the length of it. This information is specified in a VkShaderModuleCreateInfo structure. The one catch is that the size of the bytecode is specified in bytes, but the bytecode pointer is a uint32_t pointer rather than a char pointer. Therefore we will need to cast the pointer with reinterpret_cast as shown below. When you perform a cast like this, you also need to ensure that the data satisfies the alignment requirements of uint32_t. Lucky for us, the data is stored in an std::vector where the default allocator already ensures that the data satisfies the worst case alignment requirements.

创建shader模块很简单,我们只需指明buffer的指针及其商都。这个信息在VkShaderModuleCreateInfo 结构体中指明。要注意的是字节码的大小是按字节算的,但是字节码的指针是uint32_t 类型,而不是char 类型。因此我们需要用reinterpret_cast 转换指针,如下所示。当你做这样的转换时,你也需要确保数据满足uint32_t的布局要求。幸运的是,数据保存在std::vector  where默认的申请器已经确保了数据满足最坏的情况。

VkShaderModuleCreateInfo createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
createInfo.codeSize = code.size();
createInfo.pCode = reinterpret_cast<const uint32_t*>(code.data());

 

The VkShaderModule can then be created with a call to vkCreateShaderModule:

然后VkShaderModule 就可以通过调用vkCreateShaderModule来创建了:

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

 

The parameters are the same as those in previous object creation functions: the logical device, pointer to create info structure, optional pointer to custom allocators and handle output variable. The buffer with the code can be freed immediately after creating the shader module. Don't forget to return the created shader module:

参数与之前的对象创建函数相同:逻辑设备、创建信息结构体、可选申请器函数指针and输出变量的句柄。创建了shader模块后,buffer就可以被释放了。不要忘记返回创建的shader模块:

return shaderModule;

 

Shader modules are just a thin wrapper around the shader bytecode that we've previously loaded from a file and the functions defined in it. The compilation and linking of the SPIR-V bytecode to machine code for execution by the GPU doesn't happen until the graphics pipeline is created. That means that we're allowed to destroy the shader modules again as soon as pipeline creation is finished, which is why we'll make them local variables in the createGraphicsPipeline function instead of class members:

Shader模块只是对shader字节码的简单封装。编译和链接of SPIR-V字节码to机器码for GPU执行不会发生until图形管道被创建。这意味着,一旦管道创建完成了,我们就可以销毁shader模块了,所以我们才让它作为局部变量in createGraphicsPipeline 函数,而不是类的成员。

void createGraphicsPipeline() {
    auto vertShaderCode = readFile("shaders/vert.spv");
    auto fragShaderCode = readFile("shaders/frag.spv");
 
    VkShaderModule vertShaderModule = createShaderModule(vertShaderCode);
    VkShaderModule fragShaderModule = createShaderModule(fragShaderCode);

 

The cleanup should then happen at the end of the function by adding two calls to vkDestroyShaderModule. All of the remaining code in this chapter will be inserted before these lines.

清理工作应该在函数最后发生by添加2个对vkDestroyShaderModule的调用。本章剩下的代码会被插入这些行之前。

    ...
    vkDestroyShaderModule(device, fragShaderModule, nullptr);
    vkDestroyShaderModule(device, vertShaderModule, nullptr);
}

 

Shader stage creation 创建shader阶段

To actually use the shaders we'll need to assign them to a specific pipeline stage through VkPipelineShaderStageCreateInfo structures as part of the actual pipeline creation process.

为了使用这些shader,我们需要将它们赋予一个特定的管道阶段-通过VkPipelineShaderStageCreateInfo 结构体as管道创建过程的一部分。

We'll start by filling in the structure for the vertex shader, again in the createGraphicsPipeline function.

首先我们填充这个结构体for顶点shader,再次在createGraphicsPipeline 函数中。

VkPipelineShaderStageCreateInfo vertShaderStageInfo = {};
vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT;

 

The first step, besides the obligatory sType member, is telling Vulkan in which pipeline stage the shader is going to be used. There is an enum value for each of the programmable stages described in the previous chapter.

第一步,除了必有的sType 成员,要告诉Vulkan,shader要别用到管道的哪个阶段。有个枚举值for每个可编程阶段(之前章节介绍过)。

vertShaderStageInfo.module = vertShaderModule;
vertShaderStageInfo.pName = "main";

 

The next two members specify the shader module containing the code, and the function to invoke, known as the entrypoint. That means that it's possible to combine multiple fragment shaders into a single shader module and use different entry points to differentiate between their behaviors. In this case we'll stick to the standard main, however.

就行了个成员指定shader模块,要调用的函数,即入口点。这意味着,可以组合多个Fragment shader进一个单独的shader模块,使用不同的入口点to区分它们的行为。但本案例中我们仍旧使用标准的main

There is one more (optional) member, pSpecializationInfo, which we won't be using here, but is worth discussing. It allows you to specify values for shader constants. You can use a single shader module where its behavior can be configured at pipeline creation by specifying different values for the constants used in it. This is more efficient than configuring the shader using variables at render time, because the compiler can do optimizations like eliminating if statements that depend on these values. If you don't have any constants like that, then you can set the member to nullptr, which our struct initialization does automatically.

还有个可选成员pSpecializationInfo,我们这里不用它,但是它值得一提。它允许你指定shader常量的值。你可以用一个单独的shader模块where其行为可在管道创建时被配置by为常量指定不同的值。这更高效than在运行时用变量配置shader,因为编译器可以做优化例如依据这些值去掉if 语句。如果你没有那样的常量,那么你可以设置此成员为nullptr,我们的结构体初始化就是自动这么做的。

Modifying the structure to suit the fragment shader is easy:

修改结构体to适应Fragment shader是简单的:

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

 

Finish by defining an array that contains these two structs, which we'll later use to reference them in the actual pipeline creation step.

最后,定义数组that包含这2个结构体,稍候我们用于在管道创建时引用它们。

VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo};

 

That's all there is to describing the programmable stages of the pipeline. In the next chapter we'll look at the fixed-function stages.

描述管道的可编程阶段的内容就这些。下一章我们将学习固定功能阶段。

C++ code / Vertex shader / Fragment shader

  • Previous

 

  • Next

 

转载于:https://www.cnblogs.com/bitzhuwei/p/Vulkan-Tutorial-13-Graphics-Pipeline-Basics-Shader-Modules.html

你可能感兴趣的:([译]Vulkan教程(13)图形管道基础之Shader模块)