初识Vulkan渲染管线

目前参考《Vulkan规范》和《Vulkan开发实战详解》对渲染管线有了一个初步的认识。现结合中英文的渲染管线图进行笔记整理。中英文的渲染管线图分别如下所示:

 

绘制命令送入设备队列执行后,Vulkan将原始的物体顶点坐标数据、顶点颜色数据最终转化为屏幕中画面。上面中文管线结构图中有两个应用程序入口,左侧为简单示例入口,右侧为使用GPU进行高性能通用计算时的计算管线。

1.绘制:命令进入Vulkan图形渲染管线的位置。

       2.输入装配:读取顶点缓冲和索引缓冲中的数据,包含程序将要绘制物体的顶点信息数据(顶点位置坐标、顶点颜色、顶点法向量等),之后对数据分组进行组装。

       3.顶点着色器(Vertex Shader:可编程的处理单元,执行顶点的变换、完成光照与材质的运用及计算等相关操作,操作对象为每个顶点。工作流程:将原始的顶点坐标数据&其他属性值传送到顶点着色器中,再经由自己开发的顶点着色器处理后产生顶点纹理坐标、颜色、位置等属性值,传递到下一阶段。

       4.细分控制&细分求值着色器(Tessellation Control&Evaluation Shader:曲面细分时近代GPU提供的一项高级特性,可以再采用较少原始顶点数据的情况下绘制出如同采用海量数据描述的光滑曲面。曲面细分工作由细分控制着色器&细分求值着色器协同完成。具体过程:

--细分控制着色器负责确定执行细分的各项控制参数(边的切分数量、内部切分数量等)。

       --细分控制着色器计算完成之后,管线将执行细分图元生成固定功能。根据细分控制着色器中确定的各项控制参数生成细分后的各个图元。

       --细分求值着色器负责各个图元中计算每个顶点的各项属性数据(顶点位置、纹理坐标、法向量)

       5.几何着色器(Geometry Shader:近代GPU提供的高级特性,可以对图元进行处理。输入为一个图元,输出为一个或多个图元,图元类型可以不同。例如输入三角形,输出三角形的三条边和法线共四根线。可以使得再不重新组织绘制用原始数据的情况下,可以使用不同的模式进行绘制呈现。

       6.图元装配:第一个任务:把顶点着色器、细分求值着色器或几何着色器产生的结果顶点分组,根据绘制方式和顶点连接关系将顶点组成图元以供光栅化;第二个任务:组装完成后进行剪裁,若图元完全位于视景图和裁剪平面内部,则将完整的图元传递下去;若完全位于视景体或者自定义裁剪平面外部,则丢弃该图元;若一部分位于内部,一部分位于外部,则需要剪裁该图元。

       7.光栅化:将投影后的连续图元分解为一个一个离散化小单元的操作为光栅化,这些小单元成为片元。每个片元都对应帧缓冲中的一个像素,不能直接称之为像素是因为还需要深度信息进行遮挡处理才能最终成像。

       8.片元前操作:在片元着色器执行之前进行的预处理工作,根据程序设置情况,提出不需要处理的片元,提高后继片元着色器处理阶段的工作效率。

       9.片元着色器(Fragment Shader:用于处理光栅化阶段生成,并经过片元前操作处理的片元值及其相关数据的可编程处理单元。可以执行纹理的采样、颜色汇总操作,每个片元执行一次。

       10.片元后操作:片元着色器完成了对输入片元的处理后,还需要对片元进行一些特定的片元后操作:深度测试、模板测试。深度测试:指将输入片元的深度值与帧缓冲中存储的对应位置的深度值进行比较,将浅片元送入下一阶段,覆盖帧缓冲中原有片元/与帧缓冲中原有片元进行混合,或者丢弃片元。模板测试:将绘制区域限定在任意形状的指定范围内,一半用于镜像、睡眠倒影绘制场合。

       11.颜色混合:接受片元着色器和片元后操作结果,对每个片元执行一次。若开启混合,则根据混合因子、目标混合因子将片元颜色值与帧缓冲中对应位置的片元颜色值进行混合,否则送入的片元颜色值将覆盖帧缓冲中对应位置片元的颜色值。

       12.帧缓冲:Vulkan中的物体绘制时预先在帧缓冲中进行绘制,之后再绘制在屏幕上。帧缓冲是由一套附件组成的,组要包括颜色附件、深度附件、模板附件、输入附件。帧缓冲的操作会影响管线的最后几个阶段:深度和模板测试、混合、逻辑操作、多采样。

       --颜色附件:存储每个片元的颜色值,RGBA(透明)表示。应用程序中看到的即是颜色附件中的值。

       --深度附件:存储每个片元的深度值,深度值失值以特定的内部格式表示的从片元处到观察点(摄像机)的距离。

       --模板附件:存储每个片元的模板值,供模板测试使用。(非常灵活、复杂)

       --输入附件:一般在包含多个子渲染通道的渲染中使用。

 

下面是一个简单的使用Vulkan API编写渲染管线的示例代码: ```c++ #include <vulkan/vulkan.h> #include <vector> #include <iostream> int main() { // 初始化Vulkan实例 VkInstance instance; VkInstanceCreateInfo instanceCreateInfo = {}; instanceCreateInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; vkCreateInstance(&instanceCreateInfo, nullptr, &instance); // 获取Vulkan设备列表 uint32_t deviceCount = 0; vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); std::vector<VkPhysicalDevice> physicalDevices(deviceCount); vkEnumeratePhysicalDevices(instance, &deviceCount, physicalDevices.data()); // 选择第一个物理设备 VkPhysicalDevice physicalDevice = physicalDevices[0]; // 获取设备属性 VkPhysicalDeviceProperties deviceProperties; vkGetPhysicalDeviceProperties(physicalDevice, &deviceProperties); // 创建设备 VkDevice device; VkDeviceCreateInfo deviceCreateInfo = {}; deviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; vkCreateDevice(physicalDevice, &deviceCreateInfo, nullptr, &device); // 获取队列族索引 uint32_t queueFamilyIndex = 0; VkQueueFamilyProperties queueFamilyProperties; vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, &queueFamilyProperties); for (uint32_t i = 0; i < queueFamilyCount; i++) { if (queueFamilyProperties[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) { queueFamilyIndex = i; break; } } // 创建命令池 VkCommandPool commandPool; VkCommandPoolCreateInfo commandPoolCreateInfo = {}; commandPoolCreateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; commandPoolCreateInfo.queueFamilyIndex = queueFamilyIndex; vkCreateCommandPool(device, &commandPoolCreateInfo, nullptr, &commandPool); // 创建渲染管线 VkShaderModule vertexShaderModule; VkShaderModuleCreateInfo vertexShaderCreateInfo = {}; // 加载顶点着色器GLSL代码 vertexShaderCreateInfo.codeSize = sizeof(vertexShaderCode); vertexShaderCreateInfo.pCode = vertexShaderCode; vkCreateShaderModule(device, &vertexShaderCreateInfo, nullptr, &vertexShaderModule); VkShaderModule fragmentShaderModule; VkShaderModuleCreateInfo fragmentShaderCreateInfo = {}; // 加载片段着色器GLSL代码 fragmentShaderCreateInfo.codeSize = sizeof(fragmentShaderCode); fragmentShaderCreateInfo.pCode = fragmentShaderCode; vkCreateShaderModule(device, &fragmentShaderCreateInfo, nullptr, &fragmentShaderModule); VkPipelineShaderStageCreateInfo shaderStages[2] = {}; shaderStages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; shaderStages[0].stage = VK_SHADER_STAGE_VERTEX_BIT; shaderStages[0].module = vertexShaderModule; shaderStages[0].pName = "main"; shaderStages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; shaderStages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT; shaderStages[1].module = fragmentShaderModule; shaderStages[1].pName = "main"; VkPipelineVertexInputStateCreateInfo vertexInputCreateInfo = {}; vertexInputCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; VkPipelineInputAssemblyStateCreateInfo inputAssemblyCreateInfo = {}; inputAssemblyCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; inputAssemblyCreateInfo.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; VkViewport viewport = {}; viewport.x = 0.0f; viewport.y = 0.0f; viewport.width = WIDTH; viewport.height = HEIGHT; viewport.minDepth = 0.0f; viewport.maxDepth = 1.0f; VkRect2D scissor = {}; scissor.offset = {0, 0}; scissor.extent = {WIDTH, HEIGHT}; VkPipelineViewportStateCreateInfo viewportCreateInfo = {}; viewportCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; viewportCreateInfo.viewportCount = 1; viewportCreateInfo.pViewports = &viewport; viewportCreateInfo.scissorCount = 1; viewportCreateInfo.pScissors = &scissor; VkPipelineRasterizationStateCreateInfo rasterizationCreateInfo = {}; rasterizationCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; rasterizationCreateInfo.polygonMode = VK_POLYGON_MODE_FILL; rasterizationCreateInfo.cullMode = VK_CULL_MODE_BACK_BIT; rasterizationCreateInfo.frontFace = VK_FRONT_FACE_CLOCKWISE; rasterizationCreateInfo.lineWidth = 1.0f; VkPipelineMultisampleStateCreateInfo multisampleCreateInfo = {}; multisampleCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; multisampleCreateInfo.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; 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; VkPipelineColorBlendStateCreateInfo colorBlendCreateInfo = {}; colorBlendCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; colorBlendCreateInfo.attachmentCount = 1; colorBlendCreateInfo.pAttachments = &colorBlendAttachment; VkPipelineLayout pipelineLayout; VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = {}; pipelineLayoutCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout); VkGraphicsPipelineCreateInfo pipelineCreateInfo = {}; pipelineCreateInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; pipelineCreateInfo.stageCount = 2; pipelineCreateInfo.pStages = shaderStages; pipelineCreateInfo.pVertexInputState = &vertexInputCreateInfo; pipelineCreateInfo.pInputAssemblyState = &inputAssemblyCreateInfo; pipelineCreateInfo.pViewportState = &viewportCreateInfo; pipelineCreateInfo.pRasterizationState = &rasterizationCreateInfo; pipelineCreateInfo.pMultisampleState = &multisampleCreateInfo; pipelineCreateInfo.pColorBlendState = &colorBlendCreateInfo; pipelineCreateInfo.layout = pipelineLayout; pipelineCreateInfo.renderPass = renderPass; vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineCreateInfo, nullptr, &pipeline); // 渲染 VkCommandBuffer commandBuffer; VkCommandBufferAllocateInfo commandBufferAllocateInfo = {}; commandBufferAllocateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; commandBufferAllocateInfo.commandPool = commandPool; commandBufferAllocateInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; commandBufferAllocateInfo.commandBufferCount = 1; vkAllocateCommandBuffers(device, &commandBufferAllocateInfo, &commandBuffer); VkCommandBufferBeginInfo commandBufferBeginInfo = {}; commandBufferBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; vkBeginCommandBuffer(commandBuffer, &commandBufferBeginInfo); vkCmdBeginRenderPass(commandBuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); vkCmdDraw(commandBuffer, 3, 1, 0, 0); vkCmdEndRenderPass(commandBuffer); vkEndCommandBuffer(commandBuffer); // 释放资源 vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); vkDestroyCommandPool(device, commandPool, nullptr); vkDestroyShaderModule(device, vertexShaderModule, nullptr); vkDestroyShaderModule(device, fragmentShaderModule, nullptr); vkDestroyPipelineLayout(device, pipelineLayout, nullptr); vkDestroyPipeline(device, pipeline, nullptr); vkDestroyDevice(device, nullptr); vkDestroyInstance(instance, nullptr); return 0; } ``` 需要注意的是,上述示例代码仅仅是一个简单的渲染管线,实际应用中还需要添加更多的细节和逻辑,例如纹理加载、深度测试、光照等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值