Vulkan

    科技2024-04-11  84

    前面章节我们简单的使用了解过Vulkan_基于GPU的视锥体剔除和LOD与CPU中实现的视锥体剔除,本次我们主要来使用Vulkan的遮挡查询来实现视锥体内的遮挡剔除功能。

    想要具体了解所有剔除原理的同学,可以研读知乎图形大佬的这篇文章剔除:从软件到硬件。

    一、vulkan查询

    从Vulkan中读取冲统计数据的主要机制是依靠查询对象。查询对象通过池进行创建和管理,每个对象实际上是池中的一个槽(slot),而不是一个单独管理的离散对象。针对用户提交给设备的业务,有多种不同的查询对象可供使用,每种用于测量设备上指令执行的一个方面。因为所有类型的查询对象都通过池来管理,所以查询的第一步是要创建一个查询池对象,用于保存这些查询对象。

    1.1 遮挡查询

    如果查询池的查询类型为VK_QUERY_TYPE_OCCLUSION,则统计通过深度和模板测试的片段数。这可以用于确定可见性,甚至可以以像素为单位测量几何体区域。 如果不太在意对象的可见区域,只关心对象是否可见,则在创建查询池的时候不在参数flags中设置VK_QUERY_CONTROL_PRECISE_BIT标识。如果未设置此标识,则查询结果可视为布尔值(即0不可见,非零可见)。

    二、主要实现

    2.1 创建查询池

    我们新建一个setupQueryPool函数来创建用于存储遮挡查询结果的查询池,如下:

    // 查询池 VkQueryPool queryPool; // 片段数 uint64_t passedSamples[2] = { 1,1 }; void setupQueryPool() { VkQueryPoolCreateInfo queryPoolInfo = {}; queryPoolInfo.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO; queryPoolInfo.queryType = VK_QUERY_TYPE_OCCLUSION; queryPoolInfo.queryCount = 2; vkCreateQueryPool(device, &queryPoolInfo, NULL, &queryPool); }

    之后我们创建一个函数getQueryResults来检索提交到命令缓冲区的阻塞查询的结果:

    void getQueryResults() { vkGetQueryPoolResults( device, queryPool, 0, 2, sizeof(passedSamples), passedSamples, sizeof(uint64_t), VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT); }

    上边的获取函数中我们使用vkGetQueryResults将结果复制到主机可见的缓冲区中并且将结果存储为64位值,并等待结果完成。如果不想等待,可以使用VK_QUERY_RESULT_WITH_AVAILABILITY_BIT立刻返回结果的状态。

    2.2 执行查询

    查询通过在命令缓冲区中包含特定的开始和停止指令:vkCmdBeginQuery()和vkCmdEndQuery()来执行。 因此我们在buildCommandBuffers函数中来执行查询操作:

    void buildCommandBuffers() { ... for (int32_t i = 0; i < drawCmdBuffers.size(); ++i) { ... // 首先必须在渲染通道之前重置查询池 vkCmdResetQueryPool(drawCmdBuffers[i], queryPool, 0, 2); ... // 查询阶段 // 茶壶绘制查询 vkCmdBeginQuery(drawCmdBuffers[i], queryPool, 0, VK_FLAGS_NONE); vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets.teapot, 0, NULL); vkCmdBindVertexBuffers(drawCmdBuffers[i], VERTEX_BUFFER_BIND_ID, 1, &models.teapot.vertices.buffer, offsets); vkCmdBindIndexBuffer(drawCmdBuffers[i], models.teapot.indices.buffer, 0, VK_INDEX_TYPE_UINT32); vkCmdDrawIndexed(drawCmdBuffers[i], models.teapot.indexCount, 1, 0, 0, 0); //结束查询 vkCmdEndQuery(drawCmdBuffers[i], queryPool, 0); // 圆球绘制开始查询 vkCmdBeginQuery(drawCmdBuffers[i], queryPool, 1, VK_FLAGS_NONE); vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets.sphere, 0, NULL); vkCmdBindVertexBuffers(drawCmdBuffers[i], VERTEX_BUFFER_BIND_ID, 1, &models.sphere.vertices.buffer, offsets); vkCmdBindIndexBuffer(drawCmdBuffers[i], models.sphere.indices.buffer, 0, VK_INDEX_TYPE_UINT32); vkCmdDrawIndexed(drawCmdBuffers[i], models.sphere.indexCount, 1, 0, 0, 0); //结束查询 vkCmdEndQuery(drawCmdBuffers[i], queryPool, 1); // Visible pass // Clear color and depth attachments VkClearAttachment clearAttachments[2] = {}; clearAttachments[0].aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; clearAttachments[0].clearValue.color = defaultClearColor; clearAttachments[0].colorAttachment = 0; clearAttachments[1].aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; clearAttachments[1].clearValue.depthStencil = { 1.0f, 0 }; VkClearRect clearRect = {}; clearRect.layerCount = 1; clearRect.rect.offset = { 0, 0 }; clearRect.rect.extent = { width, height }; vkCmdClearAttachments( drawCmdBuffers[i], 2, clearAttachments, 1, &clearRect); vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.solid); // 是否绘制茶壶 if (passedSamples[0] > 0) { vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets.teapot, 0, NULL); vkCmdBindVertexBuffers(drawCmdBuffers[i], VERTEX_BUFFER_BIND_ID, 1, &models.teapot.vertices.buffer, offsets); vkCmdBindIndexBuffer(drawCmdBuffers[i], models.teapot.indices.buffer, 0, VK_INDEX_TYPE_UINT32); vkCmdDrawIndexed(drawCmdBuffers[i], models.teapot.indexCount, 1, 0, 0, 0); } // 着色可见阶段 // 是否绘制茶壶球体 if (passedSamples[1] > 0) { vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets.sphere, 0, NULL); vkCmdBindVertexBuffers(drawCmdBuffers[i], VERTEX_BUFFER_BIND_ID, 1, &models.sphere.vertices.buffer, offsets); vkCmdBindIndexBuffer(drawCmdBuffers[i], models.sphere.indices.buffer, 0, VK_INDEX_TYPE_UINT32); vkCmdDrawIndexed(drawCmdBuffers[i], models.sphere.indexCount, 1, 0, 0, 0); } // 遮挡面绘制 vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.occluder); vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL); vkCmdBindVertexBuffers(drawCmdBuffers[i], VERTEX_BUFFER_BIND_ID, 1, &models.plane.vertices.buffer, offsets); vkCmdBindIndexBuffer(drawCmdBuffers[i], models.plane.indices.buffer, 0, VK_INDEX_TYPE_UINT32); vkCmdDrawIndexed(drawCmdBuffers[i], models.plane.indexCount, 1, 0, 0, 0); vkCmdEndRenderPass(drawCmdBuffers[i]); vkEndCommandBuffer(drawCmdBuffers[i]); } }

    2.3 更新查询结果

    通过上述执行查询后我们需要在renderLoop中实时更新获取到的片段数:

    void draw() { updateUniformBuffers(); VulkanExampleBase::prepareFrame(); submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer]; VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE)); // 读取查询结果,以便在下一帧中使用 getQueryResults(); VulkanExampleBase::submitFrame(); }

    至此整个查询过程结束,可以通过下图对下遮挡剔除效果:

    Processed: 0.012, SQL: 8