大规模渲染

1,063 阅读2分钟

万人同屏渲染.gif

参考vk-guide, vkguide.dev

项目地址 github.com/limingyin18…

两个点:实例化和间接绘制。实例化解决相同物体在不同位置的渲染,减少绘制命令的调用。间接绘制是在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为: 实例索引buffer0.gif
经过剔除后,只需要绘制1,3,5,3个实例,实例索引buffer变为: 实例索引buffer1.gif
即将可见的实例索引写在前面。然后,指定实例数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:

实例索引buffer2.gif