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

相关文章

springboot使用Scheduling实现动态增删启停定时任务教程

《springboot使用Scheduling实现动态增删启停定时任务教程》:本文主要介绍springboot使用Scheduling实现动态增删启停定时任务教程,具有很好的参考价值,希望对大家有... 目录1、配置定时任务需要的线程池2、创建ScheduledFuture的包装类3、注册定时任务,增加、删

如何为Yarn配置国内源的详细教程

《如何为Yarn配置国内源的详细教程》在使用Yarn进行项目开发时,由于网络原因,直接使用官方源可能会导致下载速度慢或连接失败,配置国内源可以显著提高包的下载速度和稳定性,本文将详细介绍如何为Yarn... 目录一、查询当前使用的镜像源二、设置国内源1. 设置为淘宝镜像源2. 设置为其他国内源三、还原为官方

Maven的使用和配置国内源的保姆级教程

《Maven的使用和配置国内源的保姆级教程》Maven是⼀个项目管理工具,基于POM(ProjectObjectModel,项目对象模型)的概念,Maven可以通过一小段描述信息来管理项目的构建,报告... 目录1. 什么是Maven?2.创建⼀个Maven项目3.Maven 核心功能4.使用Maven H

IDEA自动生成注释模板的配置教程

《IDEA自动生成注释模板的配置教程》本文介绍了如何在IntelliJIDEA中配置类和方法的注释模板,包括自动生成项目名称、包名、日期和时间等内容,以及如何定制参数和返回值的注释格式,需要的朋友可以... 目录项目场景配置方法类注释模板定义类开头的注释步骤类注释效果方法注释模板定义方法开头的注释步骤方法注

Python虚拟环境终极(含PyCharm的使用教程)

《Python虚拟环境终极(含PyCharm的使用教程)》:本文主要介绍Python虚拟环境终极(含PyCharm的使用教程),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,... 目录一、为什么需要虚拟环境?二、虚拟环境创建方式对比三、命令行创建虚拟环境(venv)3.1 基础命令3

使用Node.js制作图片上传服务的详细教程

《使用Node.js制作图片上传服务的详细教程》在现代Web应用开发中,图片上传是一项常见且重要的功能,借助Node.js强大的生态系统,我们可以轻松搭建高效的图片上传服务,本文将深入探讨如何使用No... 目录准备工作搭建 Express 服务器配置 multer 进行图片上传处理图片上传请求完整代码示例

MySQL更新某个字段拼接固定字符串的实现

《MySQL更新某个字段拼接固定字符串的实现》在MySQL中,我们经常需要对数据库中的某个字段进行更新操作,本文就来介绍一下MySQL更新某个字段拼接固定字符串的实现,感兴趣的可以了解一下... 目录1. 查看字段当前值2. 更新字段拼接固定字符串3. 验证更新结果mysql更新某个字段拼接固定字符串 -

python连接本地SQL server详细图文教程

《python连接本地SQLserver详细图文教程》在数据分析领域,经常需要从数据库中获取数据进行分析和处理,下面:本文主要介绍python连接本地SQLserver的相关资料,文中通过代码... 目录一.设置本地账号1.新建用户2.开启双重验证3,开启TCP/IP本地服务二js.python连接实例1.

Python 安装和配置flask, flask_cors的图文教程

《Python安装和配置flask,flask_cors的图文教程》:本文主要介绍Python安装和配置flask,flask_cors的图文教程,本文通过图文并茂的形式给大家介绍的非常详细,... 目录一.python安装:二,配置环境变量,三:检查Python安装和环境变量,四:安装flask和flas

Spring Security基于数据库的ABAC属性权限模型实战开发教程

《SpringSecurity基于数据库的ABAC属性权限模型实战开发教程》:本文主要介绍SpringSecurity基于数据库的ABAC属性权限模型实战开发教程,本文给大家介绍的非常详细,对大... 目录1. 前言2. 权限决策依据RBACABAC综合对比3. 数据库表结构说明4. 实战开始5. MyBA