需要解决的问题
在上一篇《图形交互1》中在渲染的时候除了将场景绘制到交换链图片中外还在渲染过程中记录了一份用于作为媒介的中间颜色图片以及一份用于记录坐标和物体ID的图片数据,但如果每次渲染均需记录后面两份额外的数据那么无疑是不必要的,因此为了简化渲染流程,我们需要将渲染流程进行细化:
- 当需要渲染的场景变化时(例如平移、旋转操作等):绘制场景至媒介图片并记录坐标及物体ID→读取媒介图片数据至显示图片→提交显示。
- 当渲染场景不变时:读取媒介图片数据至显示图片→提交显示。
整个渲染流程
具体实现
当场景发生变化时整个过程就是上篇《图形交互1》中提到的渲染流程,相比之下当场景不变的情况下其实是少了将整个场景绘制至媒介图片的过程,因此减少了渲染量,因此仅需重建一个渲染通道及相关的渲染资源即可,在这里仅记录相应的渲染通道部分代码:
void MLINVulkanPrivate::createRenderPassForCommon()
{
VkAttachmentDescription colorAttachment{};//用于显示的颜色附件
colorAttachment.format = mSwapchainDetails.mSwapchainFormat.format;
colorAttachment.samples = mMsaaSamples;
colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
VkAttachmentDescription intermediaryAttachment{};
intermediaryAttachment.format = mSwapchainDetails.mSwapchainFormat.format;
intermediaryAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
intermediaryAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
intermediaryAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
intermediaryAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
intermediaryAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
intermediaryAttachment.initialLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
intermediaryAttachment.finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
VkAttachmentDescription coordinateAttachment{};
coordinateAttachment.format = VK_FORMAT_R32G32B32A32_SFLOAT;
coordinateAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
coordinateAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
coordinateAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
coordinateAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
coordinateAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
coordinateAttachment.initialLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
coordinateAttachment.finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
VkAttachmentReference colorAttachmentRef{};
colorAttachmentRef.attachment = 0;
colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
VkAttachmentReference intermediaryAttachmentRef = {};
intermediaryAttachmentRef.attachment = 1;
intermediaryAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
VkAttachmentReference coordinateAttachmentRef = {};
coordinateAttachmentRef.attachment = 2;
coordinateAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
QVector<VkAttachmentReference> vecReferences1 = {
VkAttachmentReference{
1, //uint32_t attachment;
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL //VkImageLayout layout;
},
VkAttachmentReference{
2, //uint32_t attachment;
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL //VkImageLayout layout;
} };
VkSubpassDescription subpass_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();
QVector<VkSubpassDependency> vecDependency = {
VkSubpassDependency{
VK_SUBPASS_EXTERNAL, //uint32_t srcSubpass;
0, //uint32_t dstSubpass;
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,//VkPipelineStageFlags srcStageMask;
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, //VkPipelineStageFlags dstStageMask;
0,//VkAccessFlags srcAccessMask;
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,//VkAccessFlags dstAccessMask;
VK_DEPENDENCY_BY_REGION_BIT//VkDependencyFlags dependencyFlags;
}
};
QVector<VkSubpassDescription> vecSubpassDes = { subpass_1 };
std::array<VkAttachmentDescription, 3> attachments =
{ colorAttachment, 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, &mRenderPassForCommon) != VK_SUCCESS)
{
throw std::runtime_error("failed to create render pass!");
}
}
以上代码中需要注意的地方如下:
- 输入附件intermediaryAttachment和coordinateAttachment在渲染通道开始时无需清除原有图片中的数据,因此loadOp的值应该设置成VK_ATTACHMENT_LOAD_OP_CLEAR;
- 需要注意输入附件intermediaryAttachment和coordinateAttachment的初始布局,因为这两个附件是在场景变化后渲染场景时生成的数据并且后生成数据后直接被用作了输入附件采样使用,因此在场景不变的这个渲染流程中这两个附件的初始布局是场景变化的渲染流程中结束时的布局,因此此时这两个附件的初始布局为 initialLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL。
主程序绘制代码添加分支
void MLINVulkanPrivate::drawFrame()
{
if (!mCanDrawNext)
return;
vkWaitForFences(mDevice, 1, &mInFlightFences[mCurrentFrame], VK_TRUE, UINT64_MAX);
uint32_t imageIndex;
VkResult result = vkAcquireNextImageKHR(mDevice, mSwapChain, UINT64_MAX, mImageAvailableSemaphores[mCurrentFrame], VK_NULL_HANDLE, &imageIndex);
if (result == VK_ERROR_OUT_OF_DATE_KHR)
{
recreateSwapChain();
return;
}
else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR)
{
throw std::runtime_error("failed to acquire swap chain image!");
}
vkResetFences(mDevice, 1, &mInFlightFences[mCurrentFrame]);
vkResetCommandBuffer(mCommandBuffers[mCurrentFrame], 0);
if (!mObjIDAndCoordinatePipelineRes.mRenderingOnlyForColorAttachment)
nextDraw_1(mCommandBuffers[mCurrentFrame], imageIndex);//场景变化时渲染
else
nextDraw_1_Common(mCommandBuffers[mCurrentFrame], imageIndex);//场景不变时渲染
VkSubmitInfo submitInfo{};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
VkSemaphore waitSemaphores[] = { mImageAvailableSemaphores[mCurrentFrame] };
VkPipelineStageFlags waitStages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT };
submitInfo.waitSemaphoreCount = 1;
submitInfo.pWaitSemaphores = waitSemaphores;
submitInfo.pWaitDstStageMask = waitStages;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &mCommandBuffers[mCurrentFrame];
VkSemaphore signalSemaphores[] = { mRenderFinishedSemaphores[mCurrentFrame] };
submitInfo.signalSemaphoreCount = 1;
submitInfo.pSignalSemaphores = signalSemaphores;
VkResult res = vkQueueSubmit(mGraphicsQueue, 1, &submitInfo, mInFlightFences[mCurrentFrame]);
if (res != VK_SUCCESS)
{
throw std::runtime_error("failed to submit draw command buffer!");
}
if (!mObjIDAndCoordinatePipelineRes.mRenderingOnlyForColorAttachment)
{
do
{
result = vkWaitForFences(mDevice, 1, &mInFlightFences[mCurrentFrame], VK_TRUE, FENCE_TIMEOUT);
} while (result == VK_TIMEOUT);
//场景变化渲染结束后切换渲染状态
mObjIDAndCoordinatePipelineRes.mRenderingOnlyForColorAttachment = true;
}
VkPresentInfoKHR presentInfo{};
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
presentInfo.waitSemaphoreCount = 1;
presentInfo.pWaitSemaphores = signalSemaphores;
VkSwapchainKHR swapChains[] = { mSwapChain };
presentInfo.swapchainCount = 1;
presentInfo.pSwapchains = swapChains;
presentInfo.pImageIndices = &imageIndex;
result = vkQueuePresentKHR(mPresentQueue, &presentInfo);
if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR)
recreateSwapChain();
else if (result != VK_SUCCESS)
throw std::runtime_error("failed to present swap chain image!");
mCurrentFrame = (mCurrentFrame + 1) % MAX_FRAMES_IN_FLIGHT;
m_pParent->requestUpdate();
}
注意事项1:以上代码中mRenderingOnlyForColorAttachment变量是为了区分两种不同的渲染情况,当至为true是表示仅从附件中采样数据至显示图片的过程,而值为false时是场景变化时实际渲染整个场景的过程,因此在整个场景渲染后需要将切设置为true。
注意事项2:mRenderingOnlyForColorAttachment至初始值应该为false,及表示第一次绘制时需绘制完整的场景,并且在后续场景变化时如缩放窗口、旋转、平移等操作时,需要再细分,比如在平移视口未结束时是不需要记录坐标及物体ID的,但在平移结束后那么在第一次渲染时就需要记录坐标和物体ID了,那么在鼠标弹起时将mRenderingOnlyForColorAttachment值设置为false即可,至于渲染场景细分为记录和不记录坐标及ID的过程就不在这里记录了。
结束语
之所以在实际的场景绘制时选择记录坐标及物体ID是为了在后续的鼠标检测物体及选择物体时能够快速获取鼠标位置的物体及相应的坐标,在本学习实例中目前仅设置了一张图片同时记录坐标及ID,但如果为了更详细的区分选择了哪个物体的哪个部位,那么则可以单独设置一张专门用于记录物体ID的图片,比如图片像素的第一个通道记录物体ID,第二个通道记录物体中某个面的ID,第三个通道中记录某个面中某条线的ID,那么在选取时就能快速通过物体ID、面ID及线ID来确定到选定线的具体数据,这种处理方式能够模拟AutoCad实体中的子实体标记,为鼠标快速捕捉线段上的各个特征点提供了良好的支持。