Vulkan教程 - 09 固定管线

2024-08-21 19:58
文章标签 教程 固定 09 vulkan 管线

本文主要是介绍Vulkan教程 - 09 固定管线,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

固定管线

        老式图形API为多数图形管线提供了默认状态。而在Vulkan中你必须明确所有的东西,从视口大小到混合函数。本章我们会填充所有的结构体来配置这些固定管线操作。

        VkPipelineVertexInputStateCreateInfo结构体描述了将要传给顶点着色器的顶点数据的格式,它主要通过以下两种方式描述:

        绑定:数据间的距离以及数据是否是逐顶点或者逐实例的;

        属性描述:传给顶点着色器的属性的种类,从哪个绑定加载以及从哪个偏移处开始。

        因为我们在顶点着色器中进行硬编码,我们会填充该结构体来明确暂时没有顶点数据要加载,等顶点缓冲章节再回来看:

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

        pVertexBindingDescriptions和pVertexAttributeDescriptions成员指向一组结构体,描述了前面提到的加载顶点数据的细节。上面这段代码添加到createGraphicsPipeline的shaderStages数组后面。

        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:每个三角形的第二和第三个顶点作为下个三角形的头两个顶点。

        通常顶点会根据索引从顶点缓冲中依次加载,但是有了元素缓冲,你可以明确自己要用的顶点。这就允许你进行顶点重用之类的优化了。如果你设置了primitiveRestartEnable成员为真,就可以在_STRIP拓扑模式中使用特殊索引如0xFFFF或0xFFFFFFFF打破线和三角形。

        我们整个教程就是打算画个三角形,所以我们会用下面的数据构建结构体:

VkPipelineInputAssemblyStateCreateInfo inputAssembly = {};
inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
inputAssembly.primitiveRestartEnable = VK_FALSE;

        视口大致描述了输出要渲染到的帧缓冲区域。这基本上都是(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;

        记住交换链的大小和它的图像可能会和窗口的宽高不一样。交换链图像将会被用作后续的帧缓冲,所以就保持他们的大小。

        minDepth和maxDepth的值表明了帧缓冲要用的深度值范围。这些值必须在[0.0f, 1.0f]之间,但是minDepth可能比maxDepth要高。

        虽然视口定义了从图像到帧缓冲的变换,裁剪矩形定义了像素实际上存储到什么区域。任何在裁剪矩形外边的像素都会被光栅器丢弃。说是变换器,实际上它们更像是一个过滤器:

        本教程我们就想要简单绘制整个帧缓冲,所以我们会指定一个能将其完全覆盖的裁剪矩形:

VkRect2D scissor = {};
scissor.offset = { 0, 0 };
scissor.extent = swapChainExtent;

        现在本视口和裁剪矩形需要用VkPipelineViewportStateCreateInfo结构体绑定到一个视口状态。某些显卡可以使用多个视口和裁剪矩形,所以它的成员会引用一组视口和裁剪矩形。多重使用则要求启用一个GPU特性(参考逻辑设备创建):

VkPipelineViewportStateCreateInfo viewportState = {};
viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
viewportState.viewportCount = 1;
viewportState.pViewports = &viewport;
viewportState.scissorCount = 1;
viewportState.pScissors = &scissor;

        光栅器使用来自顶点着色器的顶点构建的几何对象转换成供片段着色器着色的片段。它也会进行深度测试,面剔除和裁剪测试,可以配置输出充满整个多边形或者仅仅是边(网格线渲染)的片段。这些都通过VkPipelineRasterizationStateCreateInfo结构体创建:

VkPipelineRasterizationStateCreateInfo rasterizer = {};
rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
rasterizer.depthClampEnable = VK_FALSE;

        如果depthClampEnable设置为真,那么在近处或者远处平面外的片段会被被截断到它们上面而不是被丢弃。这对于一些特殊情况比较有用,比如阴影映射图。用这个的话需要启用一个GPU特性:

rasterizer.rasterizerDiscardEnable = VK_FALSE;

        这里如果设置为真了,那么几何图形就不会传递到光栅器阶段。这样就基本禁用了到帧缓冲的任何输出:

        polygonMode决定了如何为几何图形生成片段:

rasterizer.polygonMode = VK_POLYGON_MODE_FILL;

        这里有以下几种模式可选:

        VK_POLYGON_MODE_FILL:用片段填充多边形区域;

        VK_POLYGON_MODE_LINE:多边形的边画作线;

        VK_POLYGON_MODE_POINT:多边形的顶点画作点。

        使用任何非填充模式需要启用一个GPU特性:

rasterizer.lineWidth = 1.0f;

        lineWidth成员就比较直白了,它描述的是根据片段数量来说的线的宽度。支持的最大线宽度取决于硬件,任何比1.0f大的宽度需要启用GPU的wideLines特性。

        剔除模式cullMode变量决定了面剔除要用的类型。你可以禁用面剔除,剔除前面,剔除后面或者全剔除。frontFace变量明确了考虑面是否为前面的时候的顶点顺序,可以顺时针或者逆时针:

rasterizer.cullMode = VK_CULL_MODE_BACK_BIT;
rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE;

        光栅器可以通过添加一个常量或者根据片段斜率偏离来改变深度值,这有时候用于阴影映射,我们这里不用,所以就置为假即可:

rasterizer.depthBiasEnable = VK_FALSE;
rasterizer.depthBiasConstantFactor = 0.0f;  // optional
rasterizer.depthBiasClamp = 0.0f;  // optional
rasterizer.depthBiasSlopeFactor = 0.0f;  // optional

        多重采样通过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;
multisampling.alphaToOneEnable = VK_FALSE;

        当前我们先禁用了,以后再来看多重采样。

        如果你要用深度或者模板缓冲,你也需要使用VkPipelineDepthStencilStateCreateInfo配置深度和模板测试。我们现在并没有用,所以就传个nullptr即可,以后回来看深度缓冲。

        片段着色器返回一个颜色后,它需要与已经在帧缓冲中的颜色进行组合。该变换就是颜色混合,有两种方式处理:

        将新的和旧的混合产生最终颜色;

        用按位操作组合新旧颜色值。

        有两种结构体来配置颜色混合。第一种是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;
colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO;
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设置为假的情况下,来自片段着色器的新颜色会不经修改地进行传递。否则,执行这两个混合操作来计算新的颜色。最终颜色和colorWriteMask进行按位与操作,确定通过哪些通道传输。

        最常用的颜色混合方式是实现alpha混合,就是想新颜色基于旧颜色的透明度进行混合。那么最终颜色就是这么计算的:

finalColor.rgb = newAlpha * newColor + (1 - 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枚举中找到所有可能的操作。

        第二个结构体引用所有帧缓冲的结构体数组并允许你设置混合常数作为前面提到的计算中的混合因子。

        如果你想要使用第二种混合方法(按位结合),那么你应该设置logicOpEnable为真,按位运算类型接着在logicOp中指明。注意这会自动禁用第一种方式,如同你对每个附着的帧缓冲设置了blendEnable为假一样。这个模式中也会使用colorWriteMask,以确定实际要影响帧缓冲的哪一个通道。也可以两种模式都不用,像我们这里做的一样,这样片段颜色就会不加修改的写入帧缓冲中。

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

        我们在前面结构体中指定的一些状态可以不用重建管线就进行修改。例如视口大小,线宽度和混合常数。如果你想这么做,那么先要像这样填充一个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 = 2;
dynamicState.pDynamicStates = dynamicStates;

        这会导致这些值的配置被忽略,然后在绘制的时候再指定这些数据。我们以后再回来看这部分内容,没有动态状态的时候可以将其替换成nullptr。

        你可以在着色器中使用统一的值,就是一些全局变量,和动态状态变量类似,可以在绘制的时候进行修改来改变其行为而不用重新创建它们。它们通常用于传递变换矩阵给顶点着色器,或者在片段着色器中创建材质采样。

        这些统一的值需要在管线创建的过程中通过创建一个VkPipelineLayout对象来指明。虽然以后的章节中才会用到,但是我们还是要创建一个空的管线布局。

        首先创建一个类成员来存储该对象,因为我们以后会在其他方法中引用它:

VkPipelineLayout pipelineLayout;

        然后在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!");
}

        该结构体也指明了push constants,这是另一个传递动态值给着色器的方法。管线布局的引用会贯穿整个程序生命周期,所以在最后的cleanup中销毁:

vkDestroyPipelineLayout(device, pipelineLayout, nullptr);

        这行代码就放在cleanup最开始的地方。

        这就是固定管线状态的所有内容了。但是还有一个对象要创建,就是渲染通道,之后才能创建图形管线,这就是后面的内容了。

这篇关于Vulkan教程 - 09 固定管线的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1094132

相关文章

Window Server创建2台服务器的故障转移群集的图文教程

《WindowServer创建2台服务器的故障转移群集的图文教程》本文主要介绍了在WindowsServer系统上创建一个包含两台成员服务器的故障转移群集,文中通过图文示例介绍的非常详细,对大家的... 目录一、 准备条件二、在ServerB安装故障转移群集三、在ServerC安装故障转移群集,操作与Ser

windos server2022的配置故障转移服务的图文教程

《windosserver2022的配置故障转移服务的图文教程》本文主要介绍了windosserver2022的配置故障转移服务的图文教程,以确保服务和应用程序的连续性和可用性,文中通过图文介绍的非... 目录准备环境:步骤故障转移群集是 Windows Server 2022 中提供的一种功能,用于在多个

龙蜥操作系统Anolis OS-23.x安装配置图解教程(保姆级)

《龙蜥操作系统AnolisOS-23.x安装配置图解教程(保姆级)》:本文主要介绍了安装和配置AnolisOS23.2系统,包括分区、软件选择、设置root密码、网络配置、主机名设置和禁用SELinux的步骤,详细内容请阅读本文,希望能对你有所帮助... ‌AnolisOS‌是由阿里云推出的开源操作系统,旨

PyTorch使用教程之Tensor包详解

《PyTorch使用教程之Tensor包详解》这篇文章介绍了PyTorch中的张量(Tensor)数据结构,包括张量的数据类型、初始化、常用操作、属性等,张量是PyTorch框架中的核心数据结构,支持... 目录1、张量Tensor2、数据类型3、初始化(构造张量)4、常用操作5、常用属性5.1 存储(st

Java操作PDF文件实现签订电子合同详细教程

《Java操作PDF文件实现签订电子合同详细教程》:本文主要介绍如何在PDF中加入电子签章与电子签名的过程,包括编写Word文件、生成PDF、为PDF格式做表单、为表单赋值、生成文档以及上传到OB... 目录前言:先看效果:1.编写word文件1.2然后生成PDF格式进行保存1.3我这里是将文件保存到本地后

windows系统下shutdown重启关机命令超详细教程

《windows系统下shutdown重启关机命令超详细教程》shutdown命令是一个强大的工具,允许你通过命令行快速完成关机、重启或注销操作,本文将为你详细解析shutdown命令的使用方法,并提... 目录一、shutdown 命令简介二、shutdown 命令的基本用法三、远程关机与重启四、实际应用

python库fire使用教程

《python库fire使用教程》本文主要介绍了python库fire使用教程,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录1.简介2. fire安装3. fire使用示例1.简介目前python命令行解析库用过的有:ar

LinuxMint怎么安装? Linux Mint22下载安装图文教程

《LinuxMint怎么安装?LinuxMint22下载安装图文教程》LinuxMint22发布以后,有很多新功能,很多朋友想要下载并安装,该怎么操作呢?下面我们就来看看详细安装指南... linux Mint 是一款基于 Ubuntu 的流行发行版,凭借其现代、精致、易于使用的特性,深受小伙伴们所喜爱。对

Python在固定文件夹批量创建固定后缀的文件(方法详解)

《Python在固定文件夹批量创建固定后缀的文件(方法详解)》文章讲述了如何使用Python批量创建后缀为.md的文件夹,生成100个,代码中需要修改的路径、前缀和后缀名,并提供了注意事项和代码示例,... 目录1. python需求的任务2. Python代码的实现3. 代码修改的位置4. 运行结果5.

使用Nginx来共享文件的详细教程

《使用Nginx来共享文件的详细教程》有时我们想共享电脑上的某些文件,一个比较方便的做法是,开一个HTTP服务,指向文件所在的目录,这次我们用nginx来实现这个需求,本文将通过代码示例一步步教你使用... 在本教程中,我们将向您展示如何使用开源 Web 服务器 Nginx 设置文件共享服务器步骤 0 —