操作系统: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的更多相关文章

  1. [译]Vulkan教程(12)图形管道基础之入门

    [译]Vulkan教程(12)图形管道基础之入门 Introduction 入门 Over the course of the next few chapters we'll be setting u ...

  2. [译]The Python Tutorial#12. Virtual Environments and Packages

    [译]The Python Tutorial#Virtual Environments and Packages 12.1 Introduction Python应用经常使用不属于标准库的包和模块.应 ...

  3. Part 12 DateTime functions in SQL Server

    DateTime functions in SQL Server IsDate, Day, Month, Year and DateName DateTime functions in SQL Ser ...

  4. Vulkan Tutorial 开发环境搭建之Windows

    操作系统:Windows8.1 显卡:Nivida GTX965M 开发工具:Visual Studio 2017 相信很多人在开始学习Vulkan开发的起始阶段都会在开发环境的配置上下一些功夫,那么 ...

  5. Vulkan Tutorial 02 编写Vulkan应用程序框架原型

    操作系统:Windows8.1 显卡:Nivida GTX965M 开发工具:Visual Studio 2017 General structure 在上一节中,我们创建了一个正确配置.可运行的的V ...

  6. Vulkan Tutorial 05 物理设备与队列簇

    操作系统:Windows8.1 显卡:Nivida GTX965M 开发工具:Visual Studio 2017 Selecting a physical device 通过VkInstance初始 ...

  7. Vulkan Tutorial 05 逻辑设备与队列

    操作系统:Windows8.1 显卡:Nivida GTX965M 开发工具:Visual Studio 2017 Introduction 在选择要使用的物理设备之后,我们需要设置一个逻辑设备用于交 ...

  8. Vulkan Tutorial 07 Window surface

    操作系统:Windows8.1 显卡:Nivida GTX965M 开发工具:Visual Studio 2017 到目前为止,我们了解到Vulkan是一个与平台特性无关联的API集合.它不能直接与窗 ...

  9. Vulkan Tutorial 08 交换链

    操作系统:Windows8.1 显卡:Nivida GTX965M 开发工具:Visual Studio 2017 在这一章节,我们了解一下将渲染图像提交到屏幕的基本机制.这种机制成为交换链,并且需要 ...

随机推荐

  1. Android系统--输入系统(八)Reader线程_使用EventHub读取事件

    Android系统--输入系统(八)Reader线程_使用EventHub读取事件 1. Reader线程工作流程 获得事件 size_t count = mEventHub->getEvent ...

  2. [Day02] int, str功能学习

    第二天任务: int的常用功能,参照该篇博客里面列举的常用功能 http://13683137989.blog.51cto.com/9636221/1911393 str的常用功能(重要) ,参照该篇 ...

  3. hibernate 对象三态(瞬态、持久态、脱管态)之我见

    刚开始学习hibernate时,对其对象的三种状态理解的模模糊糊,一直停留在一知半解的状态,前两天又回顾了一下,顿时醒悟,原来三种状态理解起来是很容易的. 先看一下对Hibernate对象状态的解释: ...

  4. JQuery简单动画效果的发生顺序和animate方法

    (1)在同一组元素上的效果 当在一个.animate()方法中以多个属性的方式应用时,是同时发生的. 当以方法连缀的形式应用时,是按顺序发生的(排队效果)---除非queue选项值为false. (2 ...

  5. jquery通过ajax向后台发送(checkbox)数组,并在后台接收,(发送的数据是checkedbox)

    版权声明:本文为博主原创文章,未经博主允许不得转载. $(document).ready(function(){ var flag = 1; $("#delBtn").click( ...

  6. CoreCRM 开发实录 —— 前后端分离的重构

    虽然2月初就回来了,可 CoreCRM 一直到5月才开始恢复开发,期间是各种生活中的意外和不方便. 1. 为什么要重构 首先是一件很值得高兴的事情:CoreCRM 有了第一位 contributor! ...

  7. httpd配置ResponseHeader

    今天遇到一个问题:我把项目编译后的静态文件发布到开发机上,开发机使用httpd启的静态文件服务,页面的访问是在特制的壳浏览器里面,我更新了代码后,发现页面被缓存了,找到壳的RD联调了一下,发现我的主页 ...

  8. centos6.5软件安装:RPM,SRPM与yum功能

    鸟哥的linxu私房菜读书笔记 前言: Linux上软件的安装可以以原始码的方式来安装软件,也就是利用厂商释出的 Tarball 来进行软件的安装.不过,你应该很容易发现,那就是每次安装软件都需要侦测 ...

  9. 关于listener监听器的一些记录

    实现ServletContextListener后,需要实现2个方法,一个是contextInitialized,这个方法会在context被创建的时候执行,这个方法有一个参数为ServletCont ...

  10. 写给Android App开发人员看的Android底层知识(3)

    (七)App启动流程第2篇 书接上文,App启动一共有七个阶段,上篇文章篇幅所限,我们只看了第一阶段,接下来讲剩余的六个阶段,仍然是拿斗鱼App举例子. 简单回顾一下第一阶段的流程,就是Launche ...