前面章节我们简单的使用了解过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();
}
至此整个查询过程结束,可以通过下图对下遮挡剔除效果: