Vulkan Tutorial 12 Fixed functions
操作系统:Windows8.1
显卡:Nivida GTX965M
开发工具:Visual Studio 2017
早起的图形API在图形渲染管线的许多阶段提供了默认的状态。在Vulkan中,从viewport的大小到混色函数,需要凡事做到亲历亲为。在本章节中我们会填充有关固有功能操作的所有结构体。
Vertex input
VkPipelineVertexInputStateCreateInfo 结构体描述了顶点数据的格式,该结构体数据传递到vertex shader中。它以两种方式进行描述:
- Bindings:根据数据的间隙,确定数据是每个顶点或者是每个instance(instancing)
- Attribute 描述:描述将要进行绑定及加载属性的顶点着色器中的相关属性类型。
因为我们将顶点数据硬编码到vertex shader中,所以我们将要填充的结构体没有顶点数据去加载。我们将会在vertex buffer章节中回来操作。
VkPipelineVertexInputStateCreateInfo vertexInputInfo = {};
vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
vertexInputInfo.vertexBindingDescriptionCount = ;
vertexInputInfo.pVertexBindingDescriptions = nullptr; // Optional
vertexInputInfo.vertexAttributeDescriptionCount = ;
vertexInputInfo.pVertexAttributeDescriptions = nullptr; // Optional
pVertexBindingDescriptions和pVertexAttributeDescriptions成员指向结构体数组,用于进一步描述加载的顶点数据信息。在createGraphicsPipeline函数中的shaderStages数组后添加该结构体。
Input assembly
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: 每个但教训的第二个、第三个顶点都作为下一个三角形的前两个顶点
正常情况下,顶点数据按照缓冲区中的序列作为索引,但是也可以通过element buffer缓冲区自行指定顶点数据的索引。通过复用顶点数据提升性能。如果设置primitiveRestartEnable成员为VK_TRUE,可以通过0xFFFF或者0xFFFFFFFF作为特殊索引来分解线和三角形在_STRIP模式下的图元拓扑结构。
通过本教程绘制三角形,所以我们坚持按照如下格式填充数据结构:
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
viewport用于描述framebuffer作为渲染输出结果目标区域。它的数值在本教程中总是设置在(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;
记得交换链和它的images图像大小WIDTH和HEIGHT会根据不同的窗体而不同。交换链图像将会在帧缓冲区framebuffers使用,所以我们应该坚持它们的大小。
minDepth和maxDepth数值指定framebuffer中深度的范围。这些数值必须收敛在[0.0f, 1.0f]区间冲,但是minDepth可能会大于maxDepth。如果你不做任何指定,建议使用标准的数值0.0f和1.0f。
viewports定义了image图像到framebuffer帧缓冲区的转换关系,裁剪矩形定义了哪些区域的像素被存储。任何在裁剪巨型外的像素都会在光栅化阶段丢弃。它们的功能更像过滤器而不是定义转换关系。这个区别如下图所示。需要注意的是,对于图像比viewport尺寸大的情形,左侧的裁剪矩形只是众多可能的一个表现。
在本教程中我们需要将图像绘制到完整的帧缓冲区framebuffer中,所以我们定义裁剪矩形覆盖到整体图像:
VkRect2D scissor = {};
scissor.offset = {, };
scissor.extent = swapChainExtent;
viewport和裁剪矩形需要借助VkPipelineViewportStateCreateInfo结构体联合使用。可以使用多viewports和裁剪矩形在一些图形卡,通过数组引用。使用该特性需要GPU支持该功能,具体看逻辑设备的创建。
VkPipelineViewportStateCreateInfo viewportState = {};
viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
viewportState.viewportCount = ;
viewportState.pViewports = &viewport;
viewportState.scissorCount = ;
viewportState.pScissors = &scissor;
Rasterizer
光栅化通过顶点着色器及具体的几何算法将顶点进行塑形,并将图形传递到片段着色器进行着色工作。它也会执行深度测试depth testing、面裁切face culling和裁剪测试,它可以对输出的片元进行配置,决定是否输出整个图元拓扑或者是边框(线框渲染)。所有的配置通过VkPipelineRasterizationStateCreateInfo结构体定义。
VkPipelineRasterizationStateCreateInfo rasterizer = {};
rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
rasterizer.depthClampEnable = VK_FALSE;
它的depthClampEnable设置为VK_TRUE,超过远近裁剪面的片元会进行收敛,而不是丢弃它们。它在特殊的情况下比较有用,像阴影贴图。使用该功能需要得到GPU的支持。
rasterizer.rasterizerDiscardEnable = VK_FALSE;
如果rasterizerDiscardEnable设置为VK_TRUE,那么几何图元永远不会传递到光栅化阶段。这是基本的禁止任何输出到framebuffer帧缓冲区的方法。
rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
polygonMode决定几何产生图片的内容。下列有效模式:
- VK_POLYGON_MODE_FILL: 多边形区域填充
- VK_POLYGON_MODE_LINE: 多边形边缘线框绘制
- VK_POLYGON_MODE_POINT: 多边形顶点作为描点绘制
使用任何模式填充需要开启GPU功能。
rasterizer.lineWidth = 1.0f;
lineWidth成员是直接填充的,根据片元的数量描述线的宽度。最大的线宽支持取决于硬件,任何大于1.0的线宽需要开启GPU的wideLines特性支持。
rasterizer.cullMode = VK_CULL_MODE_BACK_BIT;
rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE;
cullMode变量用于决定面裁剪的类型方式。可以禁止culling,裁剪front faces,cull back faces 或者全部。frontFace用于描述作为front-facing面的顶点的顺序,可以是顺时针也可以是逆时针。
rasterizer.depthBiasEnable = VK_FALSE;
rasterizer.depthBiasConstantFactor = 0.0f; // Optional
rasterizer.depthBiasClamp = 0.0f; // Optional
rasterizer.depthBiasSlopeFactor = 0.0f; // Optional
光栅化可以通过添加常量或者基于片元的斜率来更改深度值。一些时候对于阴影贴图是有用的,但是我们不会在章节中使用,设置depthBiasEnable为VK_FALSE。
Multisampling
VkPipelineMultisampleStateCreateInfo结构体用于配置多重采样。所谓多重采样是抗锯齿anti-aliasing的一种实现。它通过组合多个多边形的片段着色器结果,光栅化到同一个像素。这主要发生在边缘,这也是最引人注目的锯齿出现的地方。如果只有一个多边形映射到像素是不需要多次运行片段着色器进行采样的,相比高分辨率来说,它会花费较低的开销。开启该功能需要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
在本教程中我们不会使用多重采样,但是可以随意的尝试,具体的参数请参阅规范。
Depth and stencil testing
如果使用depth 或者 stencil缓冲区,需要使用VkPipelineDepthStencilStateCreateInfo配置。我们现在不需要使用,所以简单的传递nullptr,关于这部分会专门在深度缓冲区章节中讨论。
Color blending
片段着色器输出具体的颜色,它需要与帧缓冲区framebuffer中已经存在的颜色进行混合。这个转换的过程成为混色,它有两种方式:
- 将old和new颜色进行混合产出一个最终的颜色
- 使用按位操作混合old和new颜色的值
有两个结构体用于配置颜色混合。第一个结构体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
这种针对每个帧缓冲区配置混色的方式,使用如下伪代码进行说明:
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;
如果blendEnable设置为VK_FALSE,那么从片段着色器输出的新颜色不会发生变化,否则两个混色操作会计算新的颜色。所得到的结果与colorWriteMask进行AND运算,以确定实际传递的通道。
大多数的情况下使用混色用于实现alpha blending,新的颜色与旧的颜色进行混合会基于它们的opacity透明通道。finalColor作为最终的输出:
finalColor.rgb = newAlpha * newColor + ( - newAlpha) * oldColor;
finalColor.a = newAlpha.a;
可以通过一下参数完成:
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;
可以在规范中找到所有有关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 = ;
colorBlending.pAttachments = &colorBlendAttachment;
colorBlending.blendConstants[] = 0.0f; // Optional
colorBlending.blendConstants[] = 0.0f; // Optional
colorBlending.blendConstants[] = 0.0f; // Optional
colorBlending.blendConstants[] = 0.0f; // Optional
如果需要使用第二种方式设置混合操作(bitwise combination), 需要设置logicOpEnable为VK_TURE。二进制位操作在logicOp字段中指定。在第一种方式中会自动禁止,等同于为每一个附加的帧缓冲区framebuffer关闭混合操作,blendEnable为VK_FALSE。colorWriteMask掩码会用确定帧缓冲区中具体哪个通道的颜色受到影响。它也可以在两种方式下禁止,截至目前,片段缓冲区向帧缓冲区中输出的颜色不会进行任何变化。
Dynamic state
之前创建的一些结构体的状态可以在运行时动态修改,而不必重新创建。比如viewport的大小,line width和blend constants。如果需要进行这样的操作,需要填充VkPipelineDynamicStateCreateInfo结构体:
VkDynamicState dynamicStates[] = {
VK_DYNAMIC_STATE_VIEWPORT,
VK_DYNAMIC_STATE_LINE_WIDTH
}; VkPipelineDynamicStateCreateInfo dynamicState = {};
dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
dynamicState.dynamicStateCount = ;
dynamicState.pDynamicStates = dynamicStates;
在绘制的过程中指定这些数据,这会导致忽略之前的相关数值。我们会在后续的章节中回过头来讨论。如果没有任何需要动态修改的数值清设置为nullptr。
Pipeline layout
可以在着色器中使用uniform,它是类似与动态状态变量的全局变量,可以在绘画时修改,可以更改着色器的行为而无需重新创建它们。它们通常用于将变换矩阵传递到顶点着色器或者在片段着色器冲创建纹理采样器。
这些uniform数值需要在管线创建过程中,通过VkPipelineLayout对象指定。即使在后续内容中用到,我们也仍然需要创建一个空的pipeline layout。
创建类成员变量持有该对象,因为我们在后续章节中的函数中引用它:
VkPipelineLayout pipelineLayout;
在createGraphicsPipeline函数中创建对象:
VkPipelineLayoutCreateInfo pipelineLayoutInfo = {};
pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pipelineLayoutInfo.setLayoutCount = ; // Optional
pipelineLayoutInfo.pSetLayouts = nullptr; // Optional
pipelineLayoutInfo.pushConstantRangeCount = ; // Optional
pipelineLayoutInfo.pPushConstantRanges = ; // Optional if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) {
throw std::runtime_error("failed to create pipeline layout!");
}
该结构体还指定了push常量,这是将动态值传递给着色器的拎一个方式。pipeline layout可以在整个程序的生命周期内引用,所以它在程序退出的时候进行销毁。
void cleanup() {
vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
...
}
Conclusion
这就是所有有关fixed-function的内容,看起来有很多的工作去做,值得庆幸的是我们几乎了解了所有有关渲染管线的内容。这个过程减少了因为不了解某些组件的默认状态,而造成运行时碰到未知行为的可能性。
然而,在我们可以最终创建图形管线之前,还有一个对象需要创建,它就是render pass。
项目代码 GitHub地址。
Vulkan Tutorial 12 Fixed functions的更多相关文章
- [译]Vulkan教程(12)图形管道基础之入门
[译]Vulkan教程(12)图形管道基础之入门 Introduction 入门 Over the course of the next few chapters we'll be setting u ...
- [译]The Python Tutorial#12. Virtual Environments and Packages
[译]The Python Tutorial#Virtual Environments and Packages 12.1 Introduction Python应用经常使用不属于标准库的包和模块.应 ...
- Part 12 DateTime functions in SQL Server
DateTime functions in SQL Server IsDate, Day, Month, Year and DateName DateTime functions in SQL Ser ...
- Vulkan Tutorial 开发环境搭建之Windows
操作系统:Windows8.1 显卡:Nivida GTX965M 开发工具:Visual Studio 2017 相信很多人在开始学习Vulkan开发的起始阶段都会在开发环境的配置上下一些功夫,那么 ...
- Vulkan Tutorial 02 编写Vulkan应用程序框架原型
操作系统:Windows8.1 显卡:Nivida GTX965M 开发工具:Visual Studio 2017 General structure 在上一节中,我们创建了一个正确配置.可运行的的V ...
- Vulkan Tutorial 05 物理设备与队列簇
操作系统:Windows8.1 显卡:Nivida GTX965M 开发工具:Visual Studio 2017 Selecting a physical device 通过VkInstance初始 ...
- Vulkan Tutorial 05 逻辑设备与队列
操作系统:Windows8.1 显卡:Nivida GTX965M 开发工具:Visual Studio 2017 Introduction 在选择要使用的物理设备之后,我们需要设置一个逻辑设备用于交 ...
- Vulkan Tutorial 07 Window surface
操作系统:Windows8.1 显卡:Nivida GTX965M 开发工具:Visual Studio 2017 到目前为止,我们了解到Vulkan是一个与平台特性无关联的API集合.它不能直接与窗 ...
- Vulkan Tutorial 08 交换链
操作系统:Windows8.1 显卡:Nivida GTX965M 开发工具:Visual Studio 2017 在这一章节,我们了解一下将渲染图像提交到屏幕的基本机制.这种机制成为交换链,并且需要 ...
随机推荐
- GPIO的配置过程
今天看到一篇很好的博文,,看这里:http://www.cnblogs.com/crazyxu/archive/2011/10/14/2212337.html 下面总结一下,加深一下理解. 要使用GP ...
- Linux系统操作指令汇总
1.系统配置 arch 显示机器的处理器架构(1) uname -m 显示机器的处理器架构(2) uname -r 显示正在使用的内核版本 dmidecode -q 显示硬件系统部件 - (SMBIO ...
- JQuery简单动画效果的发生顺序和animate方法
(1)在同一组元素上的效果 当在一个.animate()方法中以多个属性的方式应用时,是同时发生的. 当以方法连缀的形式应用时,是按顺序发生的(排队效果)---除非queue选项值为false. (2 ...
- crontab的两种配置方式
废话不多说,直接上菜了 第一种:在/etc/crontab下设置,指定用户名的 1.vim命令进入/etc/crontab 2.在最后一行加上 59 23 * * * root /root/cat ...
- java文件的读写操作
java文件的读写操作主要是对输入流和输出流的操作,由于流的分类很多,所以概念很容易模糊,基于此,对于流的读写操作做一个小结. 1.根据数据的流向来分: 输出流:是用来写数据的,是由程序(内存)--- ...
- 微信小程序之购物车功能
前言 以往的购物车,基本都是通过大量的 DOM 操作来实现.微信小程序其实跟 vue.js 的用法非常像,接下来就看看小程序可以怎样实现购物车功能. 需求 先来弄清楚购物车的需求. 单选.全选和取消, ...
- POJ 1007
DNA Sorting Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 83069 Accepted: 33428 Descrip ...
- 关于爬楼梯的lintcode代码
讲真的,这个我只会用递归去做,但是lintcode上面超时,所以只有在网上找了个动态规划的,虽然这个程序懂了,但是我觉得还是挺不容易的真正弄懂的话-- class Solution {public: ...
- jquery与js实现全选功能的区别---2017-05-12
一.jquery常用的事件 click(),dbclick() focus(),blur() change() keydown(),keypress(),keyup() mousedown(),mou ...
- redis的主从复制
一.主从复制概述 二.主从复制的过程: