載入模型:
使用 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]);
}
}
}