操作系统:Windows8.1

显卡:Nivida GTX965M

开发工具:Visual Studio 2017


Introduction

在实际产品的运行环境中3D模型的数据往往共享多个三角形之间的顶点数据。即使绘制一些简单的图形也是如此,比如矩形:

绘制矩形需要两个三角形,通常意味着我们需要6个顶点数据。问题是其中的两个顶点会重复,导致数据会有50%的冗余。如果更复杂的模型,该问题会更加严重,平均每三个三角形就会发生重复顶点使用的情况。解决问题的方法是使用index buffer,即索引缓冲区。

索引缓冲区纯粹是一个指向顶点缓冲区的指针数组。它允许我们重排列顶点数据,并复用多个已经存在的顶点数据。上图显示了有一个包含四个不重复顶点数据的顶点缓冲区,通过索引缓冲区将如何显示矩形的情况。前三个索引定义右上角的三角形,最后三个索引定义了左下角的三角形。

Index buffer creation


在本章节中,为了绘制如上图所示的矩形,我们需要修改顶点数据并添加索引数据。修改后的顶点数据分别代表矩形四个角:

const std::vector<Vertex> vertices = {
{{-0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}},
{{0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}},
{{0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}},
{{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}}
};

左上角是红色,右上角是绿色,右下角是蓝色,左下角是白色。我们添加一个新的索引数组indices代表索引缓冲区的数据内容。它应该匹配途中的索引来绘制右上角的三角形和左下角的三角形。

const std::vector<uint16_t> indices = {
, , , , ,
};

根据vertices中的条目个数,我们可以使用uint16_tuint32_t作为索引缓冲区类型。现在我们可以使用uint16_t,因为我们使用的独立顶点数量小于65535。

如顶点数据,为了使GPU可以访问到它们,需要将索引数据上传到缓冲区VkBuffer。定义两个类成员保存索引缓冲区的资源:

VkBuffer vertexBuffer;
VkDeviceMemory vertexBufferMemory;
VkBuffer indexBuffer;
VkDeviceMemory indexBufferMemory;

createIndexBuffer函数与之前的createVertexBuffer函数非常类似:

void initVulkan() {
...
createVertexBuffer();
createIndexBuffer();
...
} void createIndexBuffer() {
VkDeviceSize bufferSize = sizeof(indices[]) * indices.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, , bufferSize, , &data);
memcpy(data, indices.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现在等于索引数量乘以索引类型的大小,该类型或者是uint16_t,或者是uint32_tindexBuffer的用法需改用VK_BUFFER_USAGE_INDEX_BUFFER_BIT代替VK_BUFFER_USAGE_VERTEX_BUFFER_BIT。其他的过程是一致的。我们创建暂存缓冲区拷贝顶点数据的内容,并最终拷贝到设备本地索引缓冲区。

索引缓冲区在程序退出的时候需要清理,与顶点缓冲区类似:

void cleanup() {
cleanupSwapChain(); vkDestroyBuffer(device, indexBuffer, nullptr);
vkFreeMemory(device, indexBufferMemory, nullptr); vkDestroyBuffer(device, vertexBuffer, nullptr);
vkFreeMemory(device, vertexBufferMemory, nullptr); ...
}

Using an index buffer


使用索引缓冲区绘制需要修改createCommandBuffers函数两个地方。首先需要绑定索引缓冲区,就像之前的顶点缓冲区一样。区别是现在仅使用一个索引缓冲区。不幸的是,不可能对每个顶点属性使用不同的索引,所以即使只有一个属性不同,我们仍然必须完全复制顶点数据。

vkCmdBindVertexBuffers(commandBuffers[i], , , vertexBuffers, offsets);

vkCmdBindIndexBuffer(commandBuffers[i], indexBuffer, , VK_INDEX_TYPE_UINT16);

索引缓冲区使用vkCmdBindIndexBuffer绑定,它持有索引缓冲区作为参数,还需要偏移量和索引数据的类型。如前所述,可能的类型是VK_INDEX_TYPE_UINT16VK_INDEX_TYPE_UINT32

仅仅绑定索引缓冲区不会发生任何改变,我们还需要告知Vulkan在使用索引缓冲区后,对应的绘制命令的变化。移除vkCmdDraw函数,并用vkCmdDrawIndexed替换:

vkCmdDrawIndexed(commandBuffers[i], static_cast<uint32_t>(indices.size()), , , , );

该函数的调用与vkCmdDraw非常类似。前两个参数指定索引的数量和几何instance数量。我们没有使用instancing,所以指定1。索引数表示被传递到顶点缓冲区中的顶点数量。下一个参数指定索引缓冲区的偏移量,使用1将会导致图形卡在第二个索引处开始读取。倒数第二个参数指定索引缓冲区中添加的索引的偏移。最后一个参数指定instancing偏移量,我们没有使用该特性。

现在运行程序如下所示:

现在我们已经通过索引缓冲区复用了顶点数据。在未来的加载复杂的3D模型数据中,该技术会非常的重要。

在前一章提到,为了更优的分配使用资源,推荐在单个内存中分配多个资源,如缓冲区,但是实际上,我们应该更进一步细化。来自Nvidia的驱动程序开发者建议将多个缓冲区(顶点缓冲区、索引缓冲区)存储到单个VkBuffer中。并在诸如vkCmdBindVertexBuffers之类的命令中使用偏移量。优点在于,在这种情况下,数据会更加充分的利用缓存,因为它们排列在一块区域。甚至在同一个渲染操作中可以复用来自相同内存块的多个资源块,只要刷新数据即可。该技巧称为称为aliasing,一些Vulkan函数有明确的标志指定这样做的意图。

项目代码 GitHub 地址。

Vulkan Tutorial 22 Index buffer的更多相关文章

  1. Vulkan Tutorial 20 Vertex buffer creation

    操作系统:Windows8.1 显卡:Nivida GTX965M 开发工具:Visual Studio 2017 Introduction 在Vulkan中,缓冲区是内存的一块区域,该区域用于向显卡 ...

  2. Vulkan Tutorial 21 Staging buffer

    操作系统:Windows8.1 显卡:Nivida GTX965M 开发工具:Visual Studio 2017 Introduction 顶点缓冲区现在已经可以正常工作,但相比于显卡内部读取数据, ...

  3. [译]Vulkan教程(22)创建顶点buffer

    [译]Vulkan教程(22)创建顶点buffer Vertex buffer creation 创建顶点buffer Introduction 入门 Buffers in Vulkan are re ...

  4. [译]Vulkan教程(24)索引buffer

    [译]Vulkan教程(24)索引buffer Index buffer 索引buffer Introduction 入门 The 3D meshes you'll be rendering in a ...

  5. directx12中vetex buffer、index buffer和constant buffer绑定piple line的时机

    类别 时机 函数 建Heap vetex buffer 在Draw函数中 ID3D12GraphicsCommandList::IASetVertexBuffer 否 index buffer 在Dr ...

  6. Vulkan Tutorial 23 Descriptor layout and buffer

    操作系统:Windows8.1 显卡:Nivida GTX965M 开发工具:Visual Studio 2017 Introduction 我们现在可以将任意属性传递给每个顶点的顶点着色器使用.但是 ...

  7. Vulkan Tutorial 29 Loading models

    操作系统:Windows8.1 显卡:Nivida GTX965M 开发工具:Visual Studio 2017 Introduction 应用程序现在已经可以渲染纹理3D模型,但是 vertice ...

  8. Vulkan Tutorial 11 Shader modules

    操作系统:Windows8.1 显卡:Nivida GTX965M 开发工具:Visual Studio 2017 与之前的图像API不同,Vulkan中的着色器代码必须以二进制字节码的格式使用,而不 ...

  9. Vulkan Tutorial 12 Fixed functions

    操作系统:Windows8.1 显卡:Nivida GTX965M 开发工具:Visual Studio 2017 早起的图形API在图形渲染管线的许多阶段提供了默认的状态.在Vulkan中,从vie ...

随机推荐

  1. Flume总结(1)

    一.日志采集:从网络端口接收数据,下沉到logger 文件netcat-logger.conf: # Name the components on this agent #给那三个组件取个名字 a1. ...

  2. java内存模型6-final

    与前面介绍的锁和volatile相比较,对final域的读和写更像是普通的变量访问.对于final域,编译器和处理器要遵守两个重排序规则: 在构造函数内对一个final域的写入,与随后把这个被构造对象 ...

  3. 华为ensp模拟某公司网络架构及配置详解

    1.先晒下架构图,二层设备省略..... 2.下面开始具体配置详解 2.1.从路由器开始配置,先用远程工具远程AR1220F-S路由,secureCRT ,putty,xshell任选其中一个均可,功 ...

  4. 手机端的viewport属性

    Window.devicePixelRatioThis read-only property returns the ratio of the resolution in physical pixel ...

  5. struts2.1.6教程二、struts.xml配置及例程

    1.配置文件的优先级 在struts2中一些配置(比如常量)可以同时在struts-default.xml(只读性),strtus-plguin.xml(只读性),struts.xml,struts. ...

  6. 开涛spring3(7.4) - 对JDBC的支持 之 7.4 Spring提供的其它帮助

    7.4  Spring提供的其它帮助 7.4.1  SimpleJdbc方式 Spring JDBC抽象框架提供SimpleJdbcInsert和SimpleJdbcCall类,这两个类通过利用JDB ...

  7. mongodb 创建LBS位置索引

    <dependency> <groupId>org.mongodb</groupId> <artifactId>mongo-java-driver< ...

  8. 更换包管理工具npm为yarn

    官网:https://yarnpkg.com/zh-Hans/ 主要考虑: 1. npm管理安装模块依赖的版本不太方便,容易在删除node_modules重新install或在其他机器上新安装时, 安 ...

  9. Java之进程与线程

    一.进程 二.线程 1.定义及特点 1)[定义]线程是一个进程内部的一条执行路径,Java虚拟机允许应用程序并发地运行多个执行路径 是系统独立调度和分派[CPU]的基本单位 2)特点 进程中执行运算的 ...

  10. HTMLCollection 对象详解,以及为什么循环获取的dom合集操作可能会出现下标不正确的情况?

    有时候循环dom合集,然后操作其中的某些dom之后,发现下标不正确了 比如我们要删除一个dom合集的时候: var selectDom = document.getElementsByClassNam ...