前面章節我們簡單的使用了解過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();
}
至此整個查詢過程結束,可以通過下圖對下遮擋剔除效果: