Vulkan中片元着色器多输出记录及单独读取代码示例
Vulkan 片元着色器多输出实现方案
Vulkan 中单个片元着色器的多个输出,依靠*多渲染目标(MRT, Multiple Render Targets)*机制实现:着色器中layout(location = N)标记的输出,会和帧缓冲绑定的第N个颜色附件一一对应,每个输出写入独立的附件图像,渲染完成后分别从对应图像读取数据即可。
具体配置步骤
以给出的双输出片元着色器为例,outColor(location=0,vec4类型)、outID(location=1,float类型)的配置流程如下:
- 配置渲染Pass的多颜色附件
渲染Pass需要为每个输出定义独立的附件描述,附件格式和着色器输出类型匹配:outColor用VK_FORMAT_R8G8B8A8_UNORM(4通道8位归一化格式),outID用VK_FORMAT_R32_SFLOAT(单通道32位浮点数格式)。子pass中需要按location顺序配置颜色附件引用,和着色器输出点位一一对应。// 定义2个颜色附件 VkAttachmentDescription colorAttachments[2] = {}; // 附件0:对应location=0的outColor colorAttachments[0].format = VK_FORMAT_R8G8B8A8_UNORM; colorAttachments[0].samples = VK_SAMPLE_COUNT_1_BIT; colorAttachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; colorAttachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE; colorAttachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; colorAttachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; colorAttachments[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; colorAttachments[0].finalLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; // 附件1:对应location=1的outID colorAttachments[1].format = VK_FORMAT_R32_SFLOAT; colorAttachments[1].samples = VK_SAMPLE_COUNT_1_BIT; colorAttachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; colorAttachments[1].storeOp = VK_ATTACHMENT_STORE_OP_STORE; colorAttachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; colorAttachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; colorAttachments[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; colorAttachments[1].finalLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; // 子pass颜色引用,顺序和location严格对应 VkAttachmentReference colorRefs[2] = {}; colorRefs[0].attachment = 0; colorRefs[0].layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; colorRefs[1].attachment = 1; colorRefs[1].layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; VkSubpassDescription subpass = {}; subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; subpass.colorAttachmentCount = 2; // 关键:声明使用2个颜色附件 subpass.pColorAttachments = colorRefs; - 创建帧缓冲绑定对应图像
分别创建两张和渲染尺寸一致的图像:一张RGBA8格式存储颜色结果,一张R32_SFLOAT格式存储ID结果,为两张图像分别创建图像视图,创建帧缓冲时按附件顺序传入两个视图。// 两个图像视图按附件顺序传入 VkImageView fbAttachments[2] = {colorOutputImageView, idOutputImageView}; VkFramebufferCreateInfo fbCreateInfo = {}; fbCreateInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; fbCreateInfo.renderPass = renderPass; fbCreateInfo.attachmentCount = 2; fbCreateInfo.pAttachments = fbAttachments; fbCreateInfo.width = renderWidth; fbCreateInfo.height = renderHeight; fbCreateInfo.layers = 1; vkCreateFramebuffer(device, &fbCreateInfo, nullptr, &framebuffer); - 配置图形管线的颜色混合状态
图形管线的颜色混合状态需要为每个附件单独配置,双输出场景需要传入2个混合状态结构体,不能只配置单附件。VkPipelineColorBlendAttachmentState blendStates[2] = {}; // 附件0(outColor)写入配置:开启全部RGBA通道写入,关闭混合 blendStates[0].colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; blendStates[0].blendEnable = VK_FALSE; // 附件1(outID)写入配置:仅开启R通道写入,关闭混合 blendStates[1].colorWriteMask = VK_COLOR_COMPONENT_R_BIT; blendStates[1].blendEnable = VK_FALSE; VkPipelineColorBlendStateCreateInfo blendCreateInfo = {}; blendCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; blendCreateInfo.attachmentCount = 2; blendCreateInfo.pAttachments = blendStates; - 分别读取两个输出的内容
渲染完成后,将两张附件图像的布局切换为传输源,分别拷贝到对应的主机可见缓冲区,映射内存即可读取数据:- 读取
outColor的逻辑和单输出场景完全一致,图像数据按像素存储为4个8位通道值,尺寸为(width, height, 4) - 读取
outID时,映射对应缓冲区的内存,按单32位浮点数解析每个像素的值即可
// 读取outID示例 float* idDataPtr; vkMapMemory(device, idBufferMemory, 0, renderWidth * renderHeight * sizeof(float), 0, (void**)&idDataPtr); // 读取坐标(x,y)位置的outID值 float currentPixelID = idDataPtr[y * renderWidth + x]; vkUnmapMemory(device, idBufferMemory); - 读取
注意事项
- 所有颜色附件的分辨率、采样数必须保持一致,格式可根据输出数据类型灵活调整:如果outID是整数类型,可将附件格式替换为
VK_FORMAT_R32_UINT,着色器中对应把outID声明为uint类型即可。 - 不要尝试把多个输出打包到同一张图像的不同通道,MRT的独立附件设计更灵活,也不会出现格式对齐、精度不匹配的问题。
- 除颜色附件相关配置外,管线的顶点输入、光栅化、深度测试等其他配置和单输出场景完全一致,不需要额外修改。
内容的提问来源于stack exchange,提问作者Akshayakeerthi Duraisamy
相关产品推荐
相关产品推荐

