天天看點

Vulkan學習--22.加載模型

載入模型:

使用 tinyobjloader 庫來從 OBJ 檔案加載頂點資料。tinyobjloader

庫是一個簡單易用的單檔案 OBJ 加載器,我們隻需要下載下傳 tiny_obj_loader.h

檔案,然後在代碼中包含這一頭檔案就可以使用它了。

我們暫時不使用光照,隻簡單地将紋理貼在模型上。

在這裡,我們加載的模型叫做 Chalet Hippolyte Chassande Baroz。我

們對它的大小和方向進行了調整:

• chalet.obj

• chalet.jpg

這一模型大概由 50 萬面三角形構成。

大小為1.5x1.5x1.5。如果使用的模型大于這一尺寸,讀者就需要對使用的視圖矩陣進行修改。

示例:

#define GLM_FORCE_RADIANS//用來使 glm::rotate這些函數使用弧度作為參數的機關
/**
預設情況下,GLM 庫的透視投影矩陣使用 OpenGL 的深度值範圍 (-
1.0,1.0)。我們需要定義 GLM_FORCE_DEPTH_ZERO_TO_ONE 宏
來讓它使用 Vulkan 的深度值範圍 (0.0,1.0)。
  */
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
#define GLM_ENABLE_EXPERIMENTAL//啟用GLM 庫的哈希函數
#include <glm/glm.hpp>//線性代數庫
//為了使用glm::rotate之類的矩陣變換函數
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtx/hash.hpp>

#define TINYOBJLOADER_IMPLEMENTATION
#include <tiny_obj_loader.h>

#include <unordered_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);
        }
    };
}

const std::string MODEL_PATH=
        "E:/workspace/Qt5.6/VulkanLearn/models/chalet.obj";
const std::string TEXTURE_PATH=
        "E:/workspace/Qt5.6/VulkanLearn/models/chalet.jpg";

    std::vector<Vertex> vertices;
    std::vector<uint32_t> indices;

            //綁定頂點緩沖到指令緩沖對象--第三個參數為索引資料的類型
            vkCmdBindIndexBuffer(commandBuffers[i],indexBuffer,0,
                                 VK_INDEX_TYPE_UINT32);

        stbi_uc* pixels = stbi_load(TEXTURE_PATH.c_str(),
                                   &texWidth,&texHeight,&texChannels,
                                   STBI_rgb_alpha);

    //載入模型檔案,填充模型資料到 vertices 和 indices
    void loadModel(){
        /**
        一個 OBJ 模型檔案包含了模型的位置、法線、紋理坐标和表面資料。
        表面資料包含了構成表面的多個頂點資料的索引。
        attrib 變量來存儲載入的位置、法線和紋理坐标資料。
        shapes 變量存儲獨立的對象和它們的表面資料。
        每個表面資料包含了一個頂點數組,頂點數組中的每個頂點資料包含了頂點的
        位置索引、法線索引和紋理坐标索引。OBJ 模型檔案格式允許為模型的每
        個表面定義材質和紋理資料,但在這裡,我們沒有用到。
        err 變量來存儲載入模型檔案時産生的錯誤和警告資訊,
        比如載入時沒有找到引用的材質資訊。
        OBJ 模型檔案中的表面資料可以包含任意數量的頂點資料,但我們的程式隻能渲染三角形表面,
        這就需要進行轉換将 OBJ 模型檔案中的表面資料都轉換為三角形表面。
        tinyobj::LoadObj 函數有一個可選的預設參數,可以設定在加載 OBJ 模型
        資料時将表面資料轉換為三角形表面。由于這一設定是預設的,是以,我們不需要自己設定它。
         */
        tinyobj::attrib_t attrib;
        std::vector<tinyobj::shape_t> shapes;
        std::vector<tinyobj::material_t> materials;
        std::string warn;
        std::string err;
        if(!tinyobj::LoadObj(&attrib,&shapes,&materials,&err,&warn,
                             MODEL_PATH.c_str())){
            throw std::runtime_error(warn+err);
        }
        /**
        我們需要達到索引緩沖節約空間的目的。三角形表面的頂點是被多個三角形表面共用的,
        而我們則是每個頂點都重新定義一次,vertices 向量包含了大量重複的頂點資料。
        我們可以将完全相同的頂點資料隻保留一個,來解決空間。
        這一去重過程可以通過 STL 的 map 或unordered_map 來實作:
          */
        std::unordered_map<Vertex, uint32_t> uniqueVertices;

        //将加載的表面資料複制到我們的 vertices 和 indices 向量中
        for(const auto& shape : shapes){
            //載入的表面資料已經被三角形化,是以直接将它們複制到vertices 向量中
            for(const auto& index: shape.mesh.indices){
                Vertex vertex = {};
                /**
                為了簡化 indices 數組的處理,我們這裡假定每個頂點都是獨一無
                二的,可以直接使用 indices 數組的目前大小作為頂點索引資料。上面
                代碼中的 index 變量的類型為 tinyobj::index_t,這一類型的變量包含了
                vertex_index、normal_index 和 texcoord_index 三個成員變量。
                我們使用這三個成員變量來檢索存儲在 attrib 數組變量中的頂點資料:

                attrib.vertices 是一個浮點數組,并非 glm::vec3 數組,我們需要在使
                用索引檢索頂點資料時首先要把索引值乘以 3 才能得到正确的頂點資料位
                置。對于紋理坐标資料,則乘以 2 進行檢索。對于頂點位置資料,偏移值
                0 對應 X 坐标,偏移值 1 對應 Y 坐标,偏移值 2 對應 Z 坐标。對于紋理
                坐标資料,偏移值 0 對應 U 坐标,偏移值 1 對應 V 坐标
                  */
                vertex.pos = {
                    attrib.vertices[3*index.vertex_index+0],
                    attrib.vertices[3*index.vertex_index+1],
                    attrib.vertices[3*index.vertex_index+2]
                };
                /**
                Vulkan的紋理坐标的原點是左上角,而OBJ模型檔案格式假設紋理坐标原點是左下角。
                是以需要反轉紋理的 Y 坐标使兩個對應
                  */
                vertex.texCoord = {
                    attrib.texcoords[2*index.texcoord_index+0],
                    1.0f - attrib.texcoords[2*index.texcoord_index+1]
                };
                vertex.color = {1.0f,1.0f,1.0f};
                //優化頂點結構
                if (uniqueVertices.count(vertex) == 0) {
                    uniqueVertices[vertex] =
                            static_cast<uint32_t>(vertices.size());
                    vertices.push_back(vertex);
                }

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