Vulkan 学习(17)---- 使用 IndexBuffer
目录
- Index Buffer 简介
- 创建 IndexBuffer
- 使用 IndexBuffer 绘制
Index Buffer 简介
绘制一个矩形可以通过绘制两个三角形来实现,如果不共享顶点,就需要 6
个顶点,共享顶点的话,只需要 4
个顶点就可以
可以想象对于更加复杂的三维网格,通过共享顶点可以节约大量内存资源
索引缓冲(IndexBuffer
)是一个包含了指向顶点缓冲中顶点数据的索引数组的缓冲,使用索引缓冲,我们可以对顶点数据进行复用
创建 IndexBuffer
- 首先定义矩形的四个顶点
const std::vector<Vertex> vertices = {{{-0.5f, 0.5f}, {1.0f, 0.0f, 0.0f}}, // color R{{-0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}}, // color G{{0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}}, // color B{{0.5f, -0.5f }, {1.0f, 0.0f, 0.0f} } // color R
};
四个顶点定义两个三角形,这个两个三角形的绘制顺序如下,
// 这里按照顺时针的方式绘制
const std::vector<uint16_t> indices = {0, 1, 3, 0, 3, 2
};
可以使用 uint16_t
或 uint32_t
变量类型作为索引的类型,这里是根据顶点的最大数量作为定义依据
方法还是将索引数据加载到一个VkBuffer
来让 GPU
可以访问它,我们定义了两个类成员变量来存储索引缓冲对象
VkBuffer vertexBuffer;
VkDeviceMemory vertexBufferMemory;
VkBuffer indexBuffer;
VkDeviceMemory indexBufferMemory;
std::vector<uint16_t> drawIndices;
添加 createIndexBuffer
函数用于索引缓冲创建,它和内容和 createVertexBuffer
函数的内容几乎一样
void basicTriangleIndexBuffer::createIndexBuffer() {VkDeviceSize bufferSize = sizeof(drawIndices[0]) * drawIndices.size();VkBuffer stagingBuffer;VkDeviceMemory stagingBufferMemory;createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory);void* data;vkMapMemory(device, stagingBufferMemory, 0, bufferSize, 0, &data);memcpy(data, drawIndices.data(), (size_t) bufferSize);vkUnmapMemory(device, stagingBufferMemory);createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, \indexBuffer, indexBufferMemory);copyBuffer(stagingBuffer, indexBuffer, bufferSize);vkDestroyBuffer(device, stagingBuffer, nullptr);vkFreeMemory(device, stagingBufferMemory, nullptr);
}
需要注意的是,有两处明显不同的地方:
bufferSize
的大小是根据顶点的数量计算出来的indexBuffer
的用法标记为VK_BUFFER_USAGE_INDEX_BUFFER_BIT
除此之外的处理和顶点缓冲的创建是相同的, 我们也需要创建一个暂存缓冲来存储 indices
数组中的索引数据,然后复制暂存缓冲中的索引数据到 GPU
能够快速访问的缓冲中
使用 IndexBuffer 绘制
首先我们需要将顶点缓冲区绑定到指令缓存对象上,和绑定顶点缓冲基本类似,不同之处是我们只能绑定一个索引缓冲对象
vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT16);
索引缓冲通过调用 vkCmdBindIndexBuffer
函数来进行绑定,vkCmdBindIndexBuffer
函数以索引缓冲对象,索引数据在索引缓冲中的偏移,以及索引数据的类型作为参数
仅仅绑定索引缓冲是不会起任何作用的,我们需要使用 vkCmdDrawIndexed
指令替换之前使用 vkCmdDraw
指令进行绘制操作
vkCmdDrawIndexed(commandBuffer, static_cast<uint32_t>(drawIndices.size()), 1, 0, 0, 0);
//替代
//vkCmdDraw(commandBuffer, static_cast<uint32_t>(vertices.size()), 1, 0, 0);
vkCmdDrawIndexed
函数使用和 vkCmdDraw
函数类似,前两个参数分别为指令缓冲区对象(commandbuffer
)和指定索引的个数,第三个参数表示实例的个数,在这里我们没有使用实例渲染,所以将实例个数设置为 1
第四个参数偏移值用于指定 GPU
开始读取索引的位置,偏移值为 1
对应索引数据中的第二个索引
倒数第二个参数是检索顶点数据前加到顶点索引上的数值
最后一个参数用于第一个被渲染的实例的ID
,在这里我们没有使用
最后显示效果如下:
这里我们分别创建了 vertexBuffer
和 indexBuffer
,实际上可以更进一步,使用一个缓冲对象通过偏移值来存储多个不同的顶点缓冲和索引缓冲数据
也就是只创建一个 vkBuffer
类型,这样做之后,由于数据之间非常紧凑,可以更好地被缓存,对于没有同时进行的操作使用的内存块可以供多个对象复用
This is known as aliasing and some Vulkan functions have explicit flags to specify that you want to do this