参考vk-guide, vkguide.dev
两个点:实例化和间接绘制。实例化解决相同物体在不同位置的渲染,减少绘制命令的调用。间接绘制是在GPU端执行剔除(比如:可见性剔除),其结果写在GPU buffer中。接下来的绘制参数在这个gpu buffer中读取,从而减少CPU与GPU之间数据交换。
实例化
告诉GPU在同一个顶点缓冲和索引缓冲上重复绘制多次,在调用绘制命令时传入firstInstance,instanceCount,在顶点着色器中利用gl_InstanceIndex在位置缓冲中读取出该实例的位置。从而将相同的物体在不同的位置绘制。
间接绘制
使用间接绘制api,vkCmdDrawIndexedIndirect,其参数在GPU buffer中,通常是在计算着色器中进行可见性剔除将结果写入后。
传统的绘制命令要在CPU端指定要绘制的物体,参数从CPU端传入:
void vkCmdDrawIndexed(
VkCommandBuffer commandBuffer,
uint32_t indexCount,
uint32_t instanceCount,
uint32_t firstIndex,
int32_t vertexOffset,
uint32_t firstInstance);
间接绘制命令则是将参数写在GPU的buffer中:
void vkCmdDrawIndexedIndirect(
VkCommandBuffer commandBuffer,
VkBuffer buffer,
VkDeviceSize offset,
uint32_t drawCount,
uint32_t stride);
即buffer的内容为传统绘制命令的参数:
struct VkDrawIndexedIndirectCommand {
uint32_t indexCount;
uint32_t instanceCount;
uint32_t firstIndex;
int32_t vertexOffset;
uint32_t firstInstance;
};
而这个buffer可以由计算着色器执行完可见性剔除后写入。
Detail
主要是写入实例数和实例索引。 剔除计算着色器:
if(IsVisible(objectID))
{
uint batchIndex = compactInstanceBuffer.Instances[gID].batchID;
uint countIndex = atomicAdd(drawBuffer.Draws[batchIndex].instanceCount,1);
uint instanceIndex = drawBuffer.Draws[batchIndex].firstInstance + countIndex;
finalInstanceBuffer.IDs[instanceIndex] = objectID;
}
若物体可见:
objectID 实例索引
uint countIndex = atomicAdd(drawBuffer.Draws[batchIndex].instanceCount,1); 实例数加1(并行原子操作)
uint instanceIndex = drawBuffer.Draws[batchIndex].firstInstance + countIndex; 当前实例在实例索引buffer中的位置
finalInstanceBuffer.IDs[instanceIndex] = objectID; 写入当前实例索引
比如有一个物体,要在5个不同的位置绘制,实例索引buffer为:
经过剔除后,只需要绘制1,3,5,3个实例,实例索引buffer变为:
即将可见的实例索引写在前面。然后,指定实例数instanceCount=3。
顶点着色器:
mat4 model = bufferModel[bufferInstance[gl_InstanceIndex]];
gl_InstanceIndex实例索引为1,2,3
bufferInstance[gl_InstanceIndex] 即在实例索引buffer中读取前3个,即1,3,5
bufferModel[bufferInstance[gl_InstanceIndex]] 最后在模型位置buffer中读取出第1,第3,第5个位置进行绘制。
整个场景的所有物体先整理在一个buffer中(实际可能会将分成透明不透明物体,静态动态)
比如有5棵草,实例索引1~5,有5个树,实例索引6~10,有5个士兵,实例索引11~15。所以士兵的firstInstance=11
经过剔除后,11,13,15士兵可见,那么instanceCount=3,实例索引buffer: