顶点去重

按照之前的处理,我们没有达到索引缓冲节约空间的目的。三角形表面的顶点是被多个三角形表面共用的,而我们则是每个顶点都重新定义一次,vertices向量包含了大量重复的顶点数据。我们可以将完全相同的顶点数据只保留一个,来解决空间。这一去重过程可以通过STL的map或unordered_map来实现:

#include <unordered_map>

        ...

std::unordered_map<Vertex, uint32_t> uniqueVertices = {};

for (const auto& shape : shapes) {
    for (const auto& index : shape.mesh.indices) {
        Vertex vertex = {};

        ...

        if (uniqueVertices.count(vertex) == 0) {
            uniqueVertices[vertex] = static_cast<uint32_t>(vertices.size());
            vertices.push_back(vertex);
        }

        indices.push_back(uniqueVertices[vertex]);
    }
}

在从OBJ模型文件加载模型数据时,我们检查加载的顶点数据是否与已经加载的数据完全相同,如果相同,就不再将其加入vertices向量,将已经加载的顶点数据的索引存储到indices向量中。如果不同,将其加入vertices向量,并存储它对应的索引值到uniqueVertices容器中。然后将其索引存储在indices向量中。

我们需要实现两个函数来让Vertex结构体可以作为map变量的键值来检索map变量,首先是==函数:

bool operator==(const Vertex& other) const {
    return pos == other.pos && color == other.color && texCoord == other.texCoord;
}

然后是对Vertex结构体进行哈希的函数:

namespace std {
    template<> struct hash<Vertex> {
        size_t operator()(Vertex const& vertex) const {
            return ((hash<glm::vec3>()(vertex.pos) ^ (hash<glm::vec3>()(vertex.color) << 1)) >> 1) ^ (hash<glm::vec2>()(vertex.texCoord) << 1);
        }
    };
}

上面这两个函数的代码需要放在Vertex结构体的定义外。GLM库的变量类型的哈希函数可以通过下面的代码包含到我们的程序中:

#define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtx/hash.hpp>

GLM库的哈希函数目前还是一个试验性的扩展,被定义在了GLM库的gtx目录下。所以需要我们定义GLM_ENABLE_EXPERIMENTAL宏来启用它。作为试验性扩展意味着在未来版本的GLM库有可能发生变化,但一般而言,我们可以认为变化不会太大。

现在重新编译运行程序,查看vertices向量的大小,可以发现vertices向量的大小从1,500,000下降到了265,645。这也说明对于我们的模型数据,每个顶点数据平均被6个三角形表面使用。

本章节代码:

C++:

https://vulkan-tutorial.com/code/27_model_loading.cpp

Vertex Shader:

https://vulkan-tutorial.com/code/26_shader_depth.vert

Fragment Shader:

https://vulkan-tutorial.com/code/26_shader_depth.frag