需要解决的问题
在屏幕上生成了渲染图形后希望能够通过鼠标选取图形上对应的对象及对应的部位等,因此在渲染时仅仅生成可见像素就远远不够,例如为了做一个绘制图形的程序,则需要通过鼠标选择已绘制的图形,且需要知道鼠标在选择时捕捉到了图形的相应坐标,为完成上述的功能,在渲染图形时不仅需要生成基本的图形还需生成图形中每个像素对应的坐标及像素所代表的对象ID。
需要准备的数据
渲染一副图片正常需要准备一张用于颜色的图片及一张用于深度的图片,另外,为了记录像素所对应的坐标及对象ID则需要额外的准备一个图片。为了满足后续的额外需求又多准备了一张颜色图片。所有的图片格式具体如下:
- 用于显示的图片(PresentImg):创建交换链后通过vkGetSwapchainImagesKHR获取
- 用于深度的图片(DepthImg):格式为VK_FORMAT_D32_SFLOAT
- 用于记录坐标及对象ID的图片(CoordinateImg):格式为VK_FORMAT_R32G32B32A32_SFLOAT
- 媒介图片(IntermediaryImg):格式同显示图片
实现思路
- 绘图阶段1:记录颜色至IntermediaryImg(媒介图片),记录坐标及对象ID至CoordinateImg(坐标及ID图片)。
- 绘图阶段2:将IntermediaryImg(媒介图片)转至PresentImg(显示图片)最终显示
具体实现
渲染通道
void MLINVulkanPrivate::createRenderPassForCoordinateAndObjectId()
{
VkAttachmentDescription presentAttachment{}; //用于显示的颜色附件
presentAttachment.format = mSwapchainDetails.mSwapchainFormat.format;
presentAttachment.samples = mMsaaSamples;
presentAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
presentAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;//渲染结束后保存用于显示
presentAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
presentAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
presentAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
presentAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
VkAttachmentDescription depthAttachment = {}; //用于深度的附件
depthAttachment.format = VK_FORMAT_D32_SFLOAT;
depthAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
depthAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
VkAttachmentDescription intermediaryAttachment{}; //用于媒介的颜色附件
intermediaryAttachment.format = mSwapchainDetails.mSwapchainFormat.format;
intermediaryAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
intermediaryAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
intermediaryAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;//渲染结束后保存用于后续读取
intermediaryAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
intermediaryAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
intermediaryAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
intermediaryAttachment.finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;//后续用于采样
VkAttachmentDescription coordinateAttachment{}; //用于记录坐标及ID的附件
coordinateAttachment.format = VK_FORMAT_R32G32B32A32_SFLOAT;
coordinateAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
coordinateAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
coordinateAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;//渲染结束后保存用于后续读取
coordinateAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
coordinateAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
coordinateAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
coordinateAttachment.finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;//后续用于采样
VkAttachmentReference colorAttachmentRef{};
colorAttachmentRef.attachment = 0;
colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
VkAttachmentReference depthAttachmentRef{};
depthAttachmentRef.attachment = 1;
depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
VkAttachmentReference intermediaryAttachmentRef = {};
intermediaryAttachmentRef.attachment = 2;
intermediaryAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
VkAttachmentReference coordinateAttachmentRef = {};
coordinateAttachmentRef.attachment = 3;
coordinateAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
QVector<VkAttachmentReference> vecReferences0 = {
VkAttachmentReference{
2, //uint32_t attachment;
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL //VkImageLayout layout;
},
VkAttachmentReference{
3, //uint32_t attachment;
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL//VkImageLayout layout;
}};
VkSubpassDescription subpass_0{};//子通道0,将场景渲染至媒介图片并且记录坐标及ID
subpass_0.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpass_0.colorAttachmentCount = static_cast<uint32_t>(vecReferences0.size());
subpass_0.pColorAttachments = vecReferences0.constData();
subpass_0.pDepthStencilAttachment = &depthAttachmentRef;
QVector<VkAttachmentReference> vecReferences1 = {
VkAttachmentReference{
2, //uint32_t attachment;
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL//VkImageLayout layout;
},
VkAttachmentReference{
3, //uint32_t attachment;
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL//VkImageLayout layout;
} };
VkSubpassDescription subpass_1{};//子通道1,将媒介图片转至显示图片用于展示
subpass_1.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpass_1.colorAttachmentCount = 1;
subpass_1.pColorAttachments = &colorAttachmentRef;
subpass_1.inputAttachmentCount = static_cast<uint32_t>(vecReferences1.size());
subpass_1.pInputAttachments = vecReferences1.constData();//此处将子通道0中绘制得到的媒介图片用作子通道1的输入附件
QVector<VkSubpassDependency> vecDependency = {
VkSubpassDependency{
0, //uint32_t srcSubpass;
1, //uint32_t dstSubpass;
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,//VkPipelineStageFlags srcStageMask;
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, //VkPipelineStageFlags dstStageMask;
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,//VkAccessFlags srcAccessMask;
VK_ACCESS_SHADER_READ_BIT,//VkAccessFlags dstAccessMask;
VK_DEPENDENCY_BY_REGION_BIT//VkDependencyFlags dependencyFlags;
}
};
QVector<VkSubpassDescription> vecSubpassDes = { subpass_0, subpass_1 };
std::array<VkAttachmentDescription, 4> attachments = { presentAttachment, depthAttachment, intermediaryAttachment, coordinateAttachment };
VkRenderPassCreateInfo renderPassInfo{};
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
renderPassInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
renderPassInfo.pAttachments = attachments.data();
renderPassInfo.subpassCount = static_cast<uint32_t>(vecSubpassDes.size());
renderPassInfo.pSubpasses = vecSubpassDes.constData();
renderPassInfo.dependencyCount = 1;
renderPassInfo.pDependencies = vecDependency.constData();
if (vkCreateRenderPass(mDevice, &renderPassInfo, nullptr, &mRenderPassForCoordinateAndObjID) != VK_SUCCESS)
{
throw std::runtime_error("failed to create render pass!");
}
}
渲染管线
创建多通道对应的渲染管线时由于每个子通道所对应的输出图片数量不同,那么相应的混合状态数量也不相同,因此需要开启相应的设备特性,这个通过在创建逻辑设备时设置VkPhysicalDeviceFeatures.independentBlend = VK_TRUE时实现。在创建管线时对应的混合状态具体如下:
- 子通道0对应的管线:
QVector< VkPipelineColorBlendAttachmentState> step0BlendStates = {
VkPipelineColorBlendAttachmentState{
VK_TRUE,
VK_BLEND_FACTOR_SRC_ALPHA,
VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
VK_BLEND_OP_ADD, //VkBlendOp colorBlendOp;
VK_BLEND_FACTOR_SRC_ALPHA,
VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
VK_BLEND_OP_ADD, //VkBlendOp alphaBlendOp;
0xF},
VkPipelineColorBlendAttachmentState{
VK_FALSE,
VK_BLEND_FACTOR_ONE, //坐标和像素不需要考虑透明混合,记录最近显示的即可
VK_BLEND_FACTOR_ZERO,
VK_BLEND_OP_ADD,
VK_BLEND_FACTOR_ONE,
VK_BLEND_FACTOR_ZERO,
VK_BLEND_OP_ADD,
0xF}
};
VkPipelineColorBlendStateCreateInfo colorBlendStateStep0
{
VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
nullptr,
0,
VK_FALSE,
VK_LOGIC_OP_CLEAR,
static_cast<uint32_t>(step0BlendStates.size()),
step0BlendStates.constData(),
1.0, 1.0, 1.0, 1.0
};
- 子通道1对应的管线:
VkPipelineColorBlendStateCreateInfo cb;//在通道1中仅是将通道0绘制的图像复制过来,不用考虑颜色混合
memset(&cb, 0, sizeof(cb));
cb.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
cb.blendConstants[0] = 1.0;
cb.blendConstants[1] = 1.0;
cb.blendConstants[2] = 1.0;
cb.blendConstants[3] = 1.0;
VkPipelineColorBlendAttachmentState att
{
VK_FALSE,
VK_BLEND_FACTOR_SRC_ALPHA,
VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
VK_BLEND_OP_ADD,
VK_BLEND_FACTOR_SRC_ALPHA,
VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
VK_BLEND_OP_ADD,
0xF
};
cb.attachmentCount = 1;
cb.pAttachments = &att;
子通道0相关着色器
- 顶点着色器:
#version 440
#extension GL_ARB_separate_shader_objects : enable
#extension GL_ARB_shading_language_420pack : enable
const vec3 cDiffuse = vec3(0.8, 0.8, 0.8);
const vec3 cSpacular = vec3(0.7, 0.7, 0.7);
const float cRoughness = 6;
layout(location = 0) in vec3 i_pos; //坐标
layout(location = 1) in vec3 i_nor; //法向
layout(location = 2) in vec3 i_color; //颜色
layout(location = 3) in float i_transparency; //透明度
layout(location = 4) in uint i_EntID;
layout(location = 0) out vec4 o_color; //计算得到的最终颜色
layout(location = 1) out vec4 o_pos;
layout(std140, binding = 0) uniform UVal {
mat4 u_MVP;
vec3 u_LightDir; //光线反方向,单位向量
vec3 u_Ambient; //环境光强度系数
vec3 u_CameraPos; //相机位置
} uVal;
void main()
{
gl_Position = uVal.u_MVP * vec4(i_pos, 1.0);
vec3 diffuseFactor = max(0.0, dot(uVal.u_LightDir, i_nor)) * cDiffuse;
vec3 eyeDir = normalize(uVal.u_CameraPos - i_pos);
vec3 middleDir = normalize(eyeDir + uVal.u_LightDir);
vec3 spacularFactor = max(0.0, pow(dot(middleDir, i_nor), cRoughness)) * cSpacular;
vec3 finalFactor = uVal.u_Ambient + (diffuseFactor + spacularFactor) * uVal.u_Ambient;
o_color = vec4(finalFactor * i_color, i_transparency);
o_pos = vec4(i_pos, i_EntID);
}
- 片段着色器:
#version 440
#extension GL_ARB_separate_shader_objects : enable
#extension GL_ARB_shading_language_420pack : enable
layout(location = 0) in vec4 i_color;
layout(location = 1) in vec4 i_pos;
layout(location = 0) out vec4 fragColor;
layout(location = 1) out vec4 fragCoordinate;
void main()
{
fragColor = i_color;
fragCoordinate = i_pos;
}
子通道1相关着色器
- 顶点着色器:
#version 440
#extension GL_ARB_separate_shader_objects : enable
#extension GL_ARB_shading_language_420pack : enable
layout(location = 0) in vec2 i_pos;
void main()
{
gl_Position = vec4(i_pos, 0.0, 1.0);
}
2.片段着色器 :
#version 440
#extension GL_ARB_separate_shader_objects : enable
#extension GL_ARB_shading_language_420pack : enable
layout(location = 0) out vec4 fragColor;
layout (input_attachment_index = 0, set = 0, binding = 0) uniform subpassInput inputColor;
//坐标和对象ID在后续应用中可以使用,比如增加鼠标选中的描边特效等,此处仅做预留
layout (input_attachment_index = 1, set = 0, binding = 1) uniform subpassInput inputPosition;
void main()
{
fragColor = subpassLoad(inputColor);
}
运行效果
- 两个阶段绘制的图片:
- 坐标及ID图片中记录的值: