Vulkan官方英文原文:https://vulkan-tutorial.com/Drawing_a_triangle/Graphics_pipeline_basics/Fixed_functions
对应的Vulkan技术规格说明书版本: Vulkan 1.3.2
The older graphics APIs provided default state for most of the stages of the graphics pipeline. In Vulkan you have to be explicit about most pipeline states as it'll be baked into an immutable pipeline state object. In this chapter we'll fill in all of the structures to configure these fixed-function operations.
以前的图形API为图形管线的大多数阶段提供了默认状态。在Vulkan中你必须明确大多数管线状态,因为它将被烘焙到一个不可变的图形管线状态对象中。这个章节我们将填充所有信息结构去配置固定功能的相关操作。
Dynamic state
动态状态
While most of the pipeline state needs to be baked into the pipeline state, a limited amount of the state can actually be changed without recreating the pipeline at draw time. Examples are the size of the viewport, line width and blend constants. If you want to use dynamic state and keep these properties out, then you'll have to fill in a VkPipelineDynamicStateCreateInfo structure like this:
虽然大部分图形管线状态需要被烘焙到管线状态中,但实际上可以更改有限数量的状态,而无需在绘制时重新创建图形管线。有几个相关的例子:视口大小,线宽,混合常量。假如你想应用动态状态并且保留这些属性,那你必须填充一个VkPipelineDynamicStateCreateInfo 结构体,如下所示:
std::vector<VkDynamicState> dynamicStates = {
VK_DYNAMIC_STATE_VIEWPORT,
VK_DYNAMIC_STATE_SCISSOR
};
VkPipelineDynamicStateCreateInfo dynamicState{};
dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
dynamicState.dynamicStateCount = static_cast<uint32_t>(dynamicStates.size());
dynamicState.pDynamicStates = dynamicStates.data();
This will cause the configuration of these values to be ignored and you will be able (and required) to specify the data at drawing time. This results in a more flexible setup and is very common for things like viewport and scissor state, which would result in a more complex setup when being baked into the pipeline state.
这将导致这些值的配置被忽略,以及你能够在绘制过程中指定(并且需要设置)相关的数据。这会让设置更灵活并且对于视口和裁剪状态是很常见的事情,当烘焙到管线状态的时候这也会让设置变得更复杂。
关于Dynamic state更多信息请见:
https://blog.csdn.net/vily_lei/article/details/128995011
Vertex input
顶点输入
The VkPipelineVertexInputStateCreateInfo structure describes the format of the vertex data that will be passed to the vertex shader. It describes this in roughly two ways:
Bindings: spacing between data and whether the data is per-vertex or per-instance (see instancing)
Attribute descriptions: type of the attributes passed to the vertex shader, which binding to load them from and at which offset
这个 VkPipelineVertexInputStateCreateInfo 顶点输入创建信息结构体描述将要传送到顶点shader的数据格式。大致用两种方式描述:
Bindings(绑定):数据之间的间距以及确定数据是每个顶点还是每个实例的
Attribute descriptions(属性描述):将要传入顶点shader的属性类型,绑定从哪个偏移量加载它们
Because we're hard coding the vertex data directly in the vertex shader, we'll fill in this structure to specify that there is no vertex data to load for now. We'll get back to it in the vertex buffer chapter.
由于我们是直接在顶点shader中硬编码顶点数据,因此我们填充此结构来指定没有顶点数据需要载入。我们将在顶点缓冲章节回顾它。
VkPipelineVertexInputStateCreateInfo vertexInputInfo{};
vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
vertexInputInfo.vertexBindingDescriptionCount = 0;
vertexInputInfo.pVertexBindingDescriptions = nullptr; // Optional
vertexInputInfo.vertexAttributeDescriptionCount = 0;
vertexInputInfo.pVertexAttributeDescriptions = nullptr; // Optional
The pVertexBindingDescriptions and pVertexAttributeDescriptions members point to an array of structs that describe the aforementioned details for loading vertex data. Add this structure to the createGraphicsPipeline function right after the shaderStages array.
这两个 pVertexBindingDescriptions and pVertexAttributeDescriptions 成员指向一个上述加载顶点数据的细节信息结构体数组。将这个结构添加到 createGraphicsPipeline 函数中的shaderStages 数组之后。
Input assembly
输入装配
The VkPipelineInputAssemblyStateCreateInfo struct describes two things: what kind of geometry will be drawn from the vertices and if primitive restart should be enabled. The former is specified in the topology member and can have values like:
VK_PRIMITIVE_TOPOLOGY_POINT_LIST: points from vertices
VK_PRIMITIVE_TOPOLOGY_LINE_LIST: line from every 2 vertices without reuse
VK_PRIMITIVE_TOPOLOGY_LINE_STRIP: the end vertex of every line is used as start vertex for the next line
VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST: triangle from every 3 vertices without reuse
VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP: the second and third vertex of every triangle are used as first two vertices of the next triangle
这个 VkPipelineInputAssemblyStateCreateInfo 结构体描述了两件事情: 顶点数据以什么类型的几何图元拓扑进行绘制 和 是否启用顶点索重新开始图元。前者在 topology 成员变量中指定,取值如下:
VK_PRIMITIVE_TOPOLOGY_POINT_LIST:顶点到点。
VK_PRIMITIVE_TOPOLOGY_LINE_LIST:两点成线,且顶点不共用。
VK_PRIMITIVE_TOPOLOGY_LINE_STRIP:每个线段(两点成线)的终点是下一个线段的起点。
VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST:三个顶点构成一个三角形,且顶点不共用。
VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP:每个三角形的第二个和第三个顶点作为下一个三角形的前两个顶点。
Normally, the vertices are loaded from the vertex buffer by index in sequential order, but with an element buffer you can specify the indices to use yourself. This allows you to perform optimizations like reusing vertices. If you set the primitiveRestartEnable member to VK_TRUE, then it's possible to break up lines and triangles in the _STRIP topology modes by using a special index of 0xFFFF or 0xFFFFFFFF.
一般情况下,顶点是通过序列顺序中的索引从顶点缓冲区载入的,但是用 element buffer 你能自己指定顶点索引数据。这可以实现优化例如复用顶点数据。加入你设置 primitiveRestartEnable 成员变量的值为 VK_TRUE,则可以通过0XFFFF或0xFFFFFFFF作为特殊的索引在_STRIP 拓扑模式下分解线或者三角形的拓扑图元结构。
We intend to draw triangles throughout this tutorial, so we'll stick to the following data for the structure:
我们打算通过这个教程绘制三角形,因此我们在这个结构体上继续用以下数据:
VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
inputAssembly.primitiveRestartEnable = VK_FALSE;
Viewports and scissors
视口和裁剪
A viewport basically describes the region of the framebuffer that the output will be rendered to. This will almost always be (0, 0) to (width, height) and in this tutorial that will also be the case.
一个视口基本上描述了帧缓冲区渲染输出的区域。它几乎总是从(0,0)坐标到(width,height)坐标,并且本教程也是如此。
VkViewport viewport{};
viewport.x = 0.0f;
viewport.y = 0.0f;
viewport.width = (float) swapChainExtent.width;
viewport.height = (float) swapChainExtent.height;
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;
Remember that the size of the swap chain and its images may differ from the WIDTH and HEIGHT of the window. The swap chain images will be used as framebuffers later on, so we should stick to their size.
The minDepth and maxDepth values specify the range of depth values to use for the framebuffer. These values must be within the [0.0f, 1.0f] range, but minDepth may be higher than maxDepth. If you aren't doing anything special, then you should stick to the standard values of 0.0f and 1.0f.
While viewports define the transformation from the image to the framebuffer, scissor rectangles define in which regions pixels will actually be stored. Any pixels outside the scissor rectangles will be discarded by the rasterizer. They function like a filter rather than a transformation. The difference is illustrated below. Note that the left scissor rectangle is just one of the many possibilities that would result in that image, as long as it's larger than the viewport.
记下当前 swap chain (图像)尺寸,而且他的图像(尺寸)可能和窗口 WIDTH 和 HEIGHT 有所不同。swap chain 图像后续将被用作帧缓冲区,因此我们应该保持他们的尺寸。
这两个 minDepth 和maxDepth 值指定了帧缓冲区的深度值取值范围。取值必须是[0.0f, 1.0f] 范围内,但是 minDepth 或许比 maxDepth 大。假如你不做任何特别的操作,你应该使用标准值0.0f 或1.0f。
So if we wanted to draw to the entire framebuffer, we would specify a scissor rectangle that covers it entirely:
因此假如我们想绘制整个帧缓冲区,我们应该指定一个裁剪区域来整个覆盖帧缓冲区:
VkRect2D scissor{};
scissor.offset = {0, 0};
scissor.extent = swapChainExtent;
Viewport(s) and scissor rectangle(s) can either be specified as a static part of the pipeline or as a dynamic state set in the command buffer. While the former is more in line with the other states it's often convenient to make viewport and scissor state dynamic as it gives you a lot more flexibility. This is very common and all implementations can handle this dynamic state without a performance penalty.
视口和裁剪区矩形既能作为图形管线静态部分也可以作为命令缓冲区的动态状态。
When opting for dynamic viewport(s) and scissor rectangle(s) you need to enable the respective dynamic states for the pipeline:
当我们挑选动态的视口和裁剪矩形的时候,你需为图形管线启用相应的动态状态:
std::vector<VkDynamicState> dynamicStates = {
VK_DYNAMIC_STATE_VIEWPORT,
VK_DYNAMIC_STATE_SCISSOR
};
VkPipelineDynamicStateCreateInfo dynamicState{};
dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
dynamicState.dynamicStateCount = static_cast<uint32_t>(dynamicStates.size());
dynamicState.pDynamicStates = dynamicStates.data();
And then you only need to specify their count at pipeline creation time:
然后您只需要在图形管线创建时指定它们的数量:
VkPipelineViewportStateCreateInfo viewportState{};
viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
viewportState.viewportCount = 1;
viewportState.scissorCount = 1;
The actual viewport(s) and scissor rectangle(s) will then later be set up at drawing time.
With dynamic state it's even possible to specify different viewports and or scissor rectangles within a single command buffer.
Without dynamic state, the viewport and scissor rectangle need to be set in the pipeline using the VkPipelineViewportStateCreateInfo struct. This makes the viewport and scissor rectangle for this pipeline immutable. Any changes required to these values would require a new pipeline to be created with the new values.
实际的视口和裁剪矩形稍后将在绘制时设置。
基于动态状态,甚至能在单个命令缓冲区中指定不同的视口和裁剪矩形。
没有动态状态,视口和裁剪矩形需要使用 VkPipelineViewportStateCreateInfo 创建信息结构体在图形管线中被设定。这样做会让视口和裁剪矩形对于图像管线来讲是不可变的。这些值的任何变动都将需要用新值创建新图形管线。
VkPipelineViewportStateCreateInfo viewportState{};
viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
viewportState.viewportCount = 1;
viewportState.pViewports = &viewport;
viewportState.scissorCount = 1;
viewportState.pScissors = &scissor;
Independent of how you set them, it's is possible to use multiple viewports and scissor rectangles on some graphics cards, so the structure members reference an array of them. Using multiple requires enabling a GPU feature (see logical device creation).
独立于你如何设置他们,可以在一些图形卡中使用多个视口和裁剪矩形,因此(对应的) VkPipelineViewportStateCreateInfo 结构体成员变量(pViewports 和 pScissors)指向了一个他们的数数组。
Rasterizer
光栅化
The rasterizer takes the geometry that is shaped by the vertices from the vertex shader and turns it into fragments to be colored by the fragment shader. It also performs depth testing, face culling and the scissor test, and it can be configured to output fragments that fill entire polygons or just the edges (wireframe rendering). All this is configured using the VkPipelineRasterizationStateCreateInfo structure.
光栅化通过顶点着色器中的计算过程,将对应的顶点数据构造为几何体数据,接着将它转换为片元,这些片元用于片段着色器中着色。这个过程也执行了深度测试,面剔除以及裁剪测试,而光栅化过程按照输出的片元是填充多边形区域还是对应的边(线框渲染)来配置。所有的相关情况都使用 VkPipelineRasterizationStateCreateInfo 结构体配置。
VkPipelineRasterizationStateCreateInfo rasterizer{};
rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
rasterizer.depthClampEnable = VK_FALSE;
If depthClampEnable is set to VK_TRUE, then fragments that are beyond the near and far planes are clamped to them as opposed to discarding them. This is useful in some special cases like shadow maps. Using this requires enabling a GPU feature.
如果将 depthClampEnable 设置为 VK_TRUE,那么在近平面和远平面之外的片元将被限制在近平面和远平面之间,而不是丢弃他们。这对于像shadow map这种特殊的应用场景是有用的。不过这需要启用对一个的GPU特性才能使用。
rasterizer.rasterizerDiscardEnable = VK_FALSE;
If rasterizerDiscardEnable is set to VK_TRUE, then geometry never passes through the rasterizer stage. This basically disables any output to the framebuffer.
如果 rasterizerDiscardEnable 设置为 VK_TRUE,那么几何体数据用于不会通过光栅阶段。这就基本上禁用了任何输出到帧缓冲区的操作。
rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
The polygonMode determines how fragments are generated for geometry. The following modes are available:
成员变量polygonMode 决定了图元如何构造几何体数据。可选模式的值如下:
VK_POLYGON_MODE_FILL: 用片元填充多边形区域。
VK_POLYGON_MODE_LINE: 多边形的边作为线来绘制。
VK_POLYGON_MODE_POINT: 多边形的顶点作为点来绘制。
Using any mode other than fill requires enabling a GPU feature.
使用除了填充模式(VK_POLYGON_MODE_FILL)之外的任何模式都需要开启相应的GPU特性。
rasterizer.lineWidth = 1.0f;
The lineWidth member is straightforward, it describes the thickness of lines in terms of number of fragments. The maximum line width that is supported depends on the hardware and any line thicker than 1.0f requires you to enable the wideLines GPU feature.
这个lineWidth 成员变量的含义是显而易见,它跟进片元的数量描述线的粗细。线宽的最大值依赖于硬件支持,任何比 1.0f 更细的线需要启用 wideLines 这个GPU特性。
rasterizer.cullMode = VK_CULL_MODE_BACK_BIT;
rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE;
The cullMode variable determines the type of face culling to use. You can disable culling, cull the front faces, cull the back faces or both. The frontFace variable specifies the vertex order for faces to be considered front-facing and can be clockwise or counterclockwise.
成员变量cullMode 决定(几何体)面剔除类型。你可以禁用剔除,剔除前向的面,剔除后向的面,或者两者都剔除。成员变量 frontFace 指定了面上的顶点绕序,这个绕序确定面的朝向(front-facing,确定正的一面或反的一面),可以是顺时针顺序或逆时针顺序。
rasterizer.depthBiasEnable = VK_FALSE;
rasterizer.depthBiasConstantFactor = 0.0f; // Optional
rasterizer.depthBiasClamp = 0.0f; // Optional
rasterizer.depthBiasSlopeFactor = 0.0f; // Optional
The rasterizer can alter the depth values by adding a constant value or biasing them based on a fragment's slope. This is sometimes used for shadow mapping, but we won't be using it. Just set depthBiasEnable to VK_FALSE.
光栅化能通过设置常量值来调整深度值,或者根据根据片元斜率来微调深度值。有时候对于阴影贴图技术是有用的,但是我们不会使用他。只需将 depthBiasEnable 设置为 VK_FALSE。
Multisampling
多重采样
The VkPipelineMultisampleStateCreateInfo struct configures multisampling, which is one of the ways to perform anti-aliasing. It works by combining the fragment shader results of multiple polygons that rasterize to the same pixel. This mainly occurs along edges, which is also where the most noticeable aliasing artifacts occur. Because it doesn't need to run the fragment shader multiple times if only one polygon maps to a pixel, it is significantly less expensive than simply rendering to a higher resolution and then downscaling. Enabling it requires enabling a GPU feature.
这个 VkPipelineMultisampleStateCreateInfo 结构体配置多重采样过程,这是一个反走样方法。它的工作方式是将光栅化到同一个像素的多个多边形的片段着色器计算结果合并到一起。这主要沿着边发生,边也是主要产生明显走样的地方。因为如果只是一个多边形映射到像素的话,不需要多次运行片段着色器,相较于绘制到高分辨率然后再下采样来说,它显著地减少开销。开启它需要开启一个GPU特性。
VkPipelineMultisampleStateCreateInfo multisampling{};
multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
multisampling.sampleShadingEnable = VK_FALSE;
multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
multisampling.minSampleShading = 1.0f; // Optional
multisampling.pSampleMask = nullptr; // Optional
multisampling.alphaToCoverageEnable = VK_FALSE; // Optional
multisampling.alphaToOneEnable = VK_FALSE; // Optional
We'll revisit multisampling in later chapter, for now let's keep it disabled.
我们会在后续章节重新讨论多处采用技术,现在先禁用这个特性。
Depth and stencil testing
深度和模板测试
If you are using a depth and/or stencil buffer, then you also need to configure the depth and stencil tests using VkPipelineDepthStencilStateCreateInfo. We don't have one right now, so we can simply pass a nullptr instead of a pointer to such a struct. We'll get back to it in the depth buffering chapter.
假如你正在使用深度以及模板缓冲区,那么你必须用 VkPipelineDepthStencilStateCreateInfo 结构体来配置深度和模板测试。我们现在不处理这些,因此我们简单地通过 nullptr 来代替指向此结构体的指针。我们在深度缓冲区章节再回头来看。
Color blending
颜色混合
After a fragment shader has returned a color, it needs to be combined with the color that is already in the framebuffer. This transformation is known as color blending and there are two ways to do it:
Mix the old and new value to produce a final color
Combine the old and new value using a bitwise operation
There are two types of structs to configure color blending. The first struct, VkPipelineColorBlendAttachmentState contains the configuration per attached framebuffer and the second struct, VkPipelineColorBlendStateCreateInfo contains the global color blending settings. In our case we only have one framebuffer:
片段着色器返回一个颜色后,它需要和帧缓冲区中已经存在的颜色合并。这种变换过程称之为颜色混合,有如下两种方法:
混合旧的和新的颜色值,产生一个最终颜色值。
用位操作合并旧的和新的颜色值
有两类结构体用来配置颜色混合操作。第一个结构体 VkPipelineColorBlendAttachmentState 包含了每个附加到帧缓冲区的配置,而第二个结构体VkPipelineColorBlendStateCreateInfo 包含了目标颜色混合设置。在我们的事情情况下,只有一个帧缓冲区:
VkPipelineColorBlendAttachmentState colorBlendAttachment{};
colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
colorBlendAttachment.blendEnable = VK_FALSE;
colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE; // Optional
colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO; // Optional
colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD; // Optional
colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; // Optional
colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; // Optional
colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD; // Optional
This per-framebuffer struct allows you to configure the first way of color blending. The operations that will be performed are best demonstrated using the following pseudocode:
这种针对每个帧缓冲区的结构体允许你配置首选颜色混合方式。使用如下伪代码来最好的演示将被运行的操作:
if (blendEnable) {
finalColor.rgb = (srcColorBlendFactor * newColor.rgb) <colorBlendOp> (dstColorBlendFactor * oldColor.rgb);
finalColor.a = (srcAlphaBlendFactor * newColor.a) <alphaBlendOp> (dstAlphaBlendFactor * oldColor.a);
} else {
finalColor = newColor;
}
finalColor = finalColor & colorWriteMask;
If blendEnable is set to VK_FALSE, then the new color from the fragment shader is passed through unmodified. Otherwise, the two mixing operations are performed to compute a new color. The resulting color is AND'd with the colorWriteMask to determine which channels are actually passed through.
如果blendEnable 成员变量设置为 VK_FALSE,那么来自片段着色器的新颜色直接通过而不被修改。否则,两个混合操作被执行来计算出一个新颜色值。所得到的的结果颜色和colorWriteMask 变量值进行与运算来决定哪些通道实际上通过。
The most common way to use color blending is to implement alpha blending, where we want the new color to be blended with the old color based on its opacity. The finalColor should then be computed as follows:
使用颜色混合最常见的用途是实现alpha混合,这里我们想基于颜色的不透明度将新的颜色与旧颜色混合。这个 finalColor 就应该按下面的实现形式计算出来:
finalColor.rgb = newAlpha * newColor + (1 - newAlpha) * oldColor;
finalColor.a = newAlpha.a;
This can be accomplished with the following parameters:
用如下的参数配置就能实现:
colorBlendAttachment.blendEnable = VK_TRUE;
colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;
You can find all of the possible operations in the VkBlendFactor and VkBlendOp enumerations in the specification.
The second structure references the array of structures for all of the framebuffers and allows you to set blend constants that you can use as blend factors in the aforementioned calculations.
你可以在技术规格说明书里面找到所有VkBlendFactor 和 VkBlendOp 枚举的可能操作。
第二个结构体持有包含所有帧缓冲区的数组结构,并且允许你设置在上述计算中你能用做混合因子的混合常量。
VkPipelineColorBlendStateCreateInfo colorBlending{};
colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
colorBlending.logicOpEnable = VK_FALSE;
colorBlending.logicOp = VK_LOGIC_OP_COPY; // Optional
colorBlending.attachmentCount = 1;
colorBlending.pAttachments = &colorBlendAttachment;
colorBlending.blendConstants[0] = 0.0f; // Optional
colorBlending.blendConstants[1] = 0.0f; // Optional
colorBlending.blendConstants[2] = 0.0f; // Optional
colorBlending.blendConstants[3] = 0.0f; // Optional
If you want to use the second method of blending (bitwise combination), then you should set logicOpEnable to VK_TRUE. The bitwise operation can then be specified in the logicOp field. Note that this will automatically disable the first method, as if you had set blendEnable to VK_FALSE for every attached framebuffer! The colorWriteMask will also be used in this mode to determine which channels in the framebuffer will actually be affected. It is also possible to disable both modes, as we've done here, in which case the fragment colors will be written to the framebuffer unmodified.
假如你想使用第二种混合方法(位运算组合),那么你应该设置logicOpEnable为VK_TRUE。然后可以在 logicOp字段指定此位操作。注意,这将自动禁用第一种方法,就好像你将每个附加的帧缓冲区的blendEnable 变量设为VK_FALSE。colorWriteMask变量也将在此模式下被用作去决定帧缓冲区中的哪些通道实际被影响了。也可以禁用两种方法,就像我们这里做的这样,这样一来,片段颜色将直接写入帧缓冲区。
Pipeline layout
管线布局
You can use uniform values in shaders, which are globals similar to dynamic state variables that can be changed at drawing time to alter the behavior of your shaders without having to recreate them. They are commonly used to pass the transformation matrix to the vertex shader, or to create texture samplers in the fragment shader.
These uniform values need to be specified during pipeline creation by creating a VkPipelineLayout object. Even though we won't be using them until a future chapter, we are still required to create an empty pipeline layout.
Create a class member to hold this object, because we'll refer to it from other functions at a later point in time:
你能在shader中使用 uniform 值,这是类似于动态状态变量的全局变量,这些值能在绘制的时候被改变,以便调整你的shader的相关行为而不需要重新创建这些shader。他们通常用于将空间变换矩阵传入顶点shader,或在片段shader中创建纹理采样器。
这些 uniform 值通过创建一个 VkPipelineLayout 对象在管线创建期间指定。在以后相关章节前,尽管我们不使用他们,我们仍然需要创建一个管线布局对象。
VkPipelineLayout pipelineLayout;
And then create the object in the createGraphicsPipeline function:
在createGraphicsPipeline 函数中创建此对象:
VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pipelineLayoutInfo.setLayoutCount = 0; // Optional
pipelineLayoutInfo.pSetLayouts = nullptr; // Optional
pipelineLayoutInfo.pushConstantRangeCount = 0; // Optional
pipelineLayoutInfo.pPushConstantRanges = nullptr; // Optionalif (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) {
throw std::runtime_error("failed to create pipeline layout!");
}
The structure also specifies push constants, which are another way of passing dynamic values to shaders that we may get into in a future chapter. The pipeline layout will be referenced throughout the program's lifetime, so it should be destroyed at the end:
这个结构体也指定 push 常量,这是另外一种将值动态传入shader的方法,我们后续章节介绍。关系布局对象将在程序生命周期中被持有,因此需要再程序结束的时候销毁:
void cleanup() {
vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
...
}
Conclusion
结论
That's it for all of the fixed-function state! It's a lot of work to set all of this up from scratch, but the advantage is that we're now nearly fully aware of everything that is going on in the graphics pipeline! This reduces the chance of running into unexpected behavior because the default state of certain components is not what you expect.
这就是所有固定固定功能的相关状态!从头开始设置所有这些状态需要做很多工作,但是好处是我们现在几乎明白了图形管线中发生的每件事。这减少了遇到意外行为的机会,因为某些组件的默认状态不是你所期望的。
There is however one more object to create before we can finally create the graphics pipeline and that is a render pass.
但是,我们最终创建出图形管线之前还有一个对象要创建,它就是渲染通道(render pass)。