前言
要想讓自己的 3D 之旅多一點樂趣,肯定得想辦法找一些有意思一點的 3D 模型。3D 模型有各種各樣的格式,obj的,stl的,fbx的等等不一而足。特別是 obj 格式的 3D 模型,完全是純文本格式,網絡上很多高手都喜歡自己寫程序對 obj 格式的模型文件進行解析。我自己收集了一些 3D 模型,有幾個 obj 格式的,也有幾個 stl 格式的,為我以后的學習做准備。當我需要查看這些模型的時候,我首選是使用 Blender。在我的程序中使用的時候,我首選 Assimp 庫。在我之前的隨筆中,已經對 Assimp 庫做了介紹。見 為什么說使用 Linux 系統學習 OpenGL 更方便。
使用 Assimp 加載 3D 模型
Assimp 的使用是非常簡單的,直接參考 Assimp 的文檔,依葫蘆畫瓢即可。這里我只簡單介紹一下 Assimp 中的數據組織結構。Assimp 讀入模型后,先有一個 aiScene 對象,在這個對象中,有一個根節點,根節點又有子結點,子結點還可能有子結點,形成一個樹狀的結構。節點的類型是 aiNode。每一個節點包含一個或多個 aiMesh,而 aiMesh 又包含頂點數據和索引數據,索引數據是儲存在 aiFace 類型中的,一般來說,我們的每一個 aiFace 都應該是一個三角形。每一個 aiMesh 又有對應的材質信息,因為我這里還沒有涉及到光照和貼圖,所以暫不考慮材質信息。只需要遞歸地進行解析,我們就可以很容易獲得模型中的所有頂點信息和索引信息。
在這里,我創建了一個 Model 類,同時利用了之前的 Mesh 類,一個 Model 類的對象中可以包含多個 Mesh 類的對象。然后,在 Model 類的 loadModel() 方法中,使用 Assimp 讀取模型數據,遍歷各 aiNode, 再遍歷 aiMesh,將 aiMesh 中的數據拷貝到 Mesh 中,然后調用所有 Mesh 對象的 setup() 方法和 render() 方法,即可完成模型的渲染。Model 類的內容如下:
#ifndef __MODEL_HPP__
#define __MODEL_HPP__
#include "mesh.hpp"
#include <assimp/Importer.hpp>
#include <assimp/postprocess.h>
#include <assimp/scene.h>
class Model
{
private:
std::vector<Mesh> meshes;
public:
void loadModel(std::string filename)
{
Assimp::Importer importer;
const aiScene *scene = importer.ReadFile(filename,
aiProcess_Triangulate |
aiProcess_GenNormals |
aiProcess_FlipUVs);
processNode(scene->mRootNode, scene);
}
void processNode(aiNode *node, const aiScene *scene)
{
for (int i = 0; i < node->mNumMeshes; i++)
{
aiMesh *mesh = scene->mMeshes[node->mMeshes[i]];
Vertex tempVertex;
std::vector<Vertex> tempVertices;
std::vector<GLuint> tempIndices;
//先讀取頂點信息
for (int j = 0; j < mesh->mNumVertices; j++)
{
if (mesh->HasPositions())
{
tempVertex.position.x = mesh->mVertices[j].x;
tempVertex.position.y = mesh->mVertices[j].y;
tempVertex.position.z = mesh->mVertices[j].z;
tempVertex.position.w = 1.0f;
}
if (mesh->HasNormals())
{
tempVertex.normal.x = mesh->mNormals[j].x;
tempVertex.normal.y = mesh->mNormals[j].y;
tempVertex.normal.z = mesh->mNormals[j].z;
}
if (mesh->HasTextureCoords(0))
{
tempVertex.texCoord.x = mesh->mTextureCoords[0][j].x;
tempVertex.texCoord.y = mesh->mTextureCoords[0][j].y;
}
tempVertices.push_back(tempVertex);
}
//再讀取索引信息
for (int i = 0; i < mesh->mNumFaces; i++)
{
for (int j = 0; j < mesh->mFaces[i].mNumIndices; j++)
{
tempIndices.push_back(mesh->mFaces[i].mIndices[j]);
}
}
Mesh tempMesh;
tempMesh.setVertices(std::move(tempVertices));
tempMesh.setIndices(std::move(tempIndices));
tempMesh.setup();
this->meshes.push_back(std::move(tempMesh));
}
if (node->mNumChildren != 0)
{
for (int k = 0; k < node->mNumChildren; k++)
{
processNode(node->mChildren[k], scene);
}
}
return;
}
void render()
{
for (auto i = meshes.begin(); i != meshes.end(); i++)
{
i->render();
}
}
};
#endif
然后來測試一下我之前收集的那些模型文件,其主文件 LoadModel.cpp 內容如下:
#include "../include/app.hpp"
#include "../include/shader.hpp"
#include "../include/model.hpp"
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
class MyApp : public App {
private:
const GLfloat clearColor[4] = {0.2f, 0.3f, 0.3f, 1.0f};
Model allis;
Model buma;
Model saber;
Model cottage;
Model bugatti;
Shader* simpleShader;
public:
void init(){
ShaderInfo shaders[] = {
{GL_VERTEX_SHADER, "simpleShader.vert"},
{GL_FRAGMENT_SHADER, "simpleShader.frag"},
{GL_NONE, ""}
};
simpleShader = new Shader(shaders);
allis.loadModel( "allis.stl");
buma.loadModel("buma.stl");
saber.loadModel( "saber.stl");
bugatti.loadModel("bugatti/bugatti.obj");
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
}
void display(){
glClearBufferfv(GL_COLOR, 0, clearColor);
glClear(GL_DEPTH_BUFFER_BIT);
glm::mat4 I(1.0f);
glm::vec3 X(1.0f, 0.0f, 0.0f);
glm::vec3 Y(0.0f, 1.0f, 0.0f);
glm::vec3 Z(0.0f, 0.0f, 1.0f);
float t = (float)glfwGetTime();
glm::mat4 view_matrix = glm::translate(I, glm::vec3(0.0f, 0.0f, -10.0f))
* glm::rotate(I, t, Y);
glm::mat4 projection_matrix = glm::perspective(glm::radians(45.0f), aspect, 1.0f, 100.0f);
glm::mat4 allis_model_matrix = glm::translate(I, glm::vec3(-3.0f, -1.5f, 0.0f))
* glm::scale(I, glm::vec3(1.0f, 1.0f, 1.0f)) * glm::rotate(I, glm::radians(-90.0f), X);
simpleShader->setModelMatrix(allis_model_matrix);
simpleShader->setViewMatrix(view_matrix);
simpleShader->setProjectionMatrix(projection_matrix);
simpleShader->setCurrent();
allis.render();
glm::mat4 buma_model_matrix = glm::translate(I, glm::vec3(-1.0f, -1.5f, 0.0f))
* glm::scale(I, glm::vec3(0.03f, 0.03f, 0.03f)) * glm::rotate(I, glm::radians(0.0f), X);
simpleShader->setModelMatrix(buma_model_matrix);
buma.render();
glm::mat4 saber_model_matrix = glm::translate(I, glm::vec3(7.5f, 0.6f, 1.0f))
* glm::scale(I, glm::vec3(0.03f, 0.03f, 0.03f)) * glm::rotate(I, glm::radians(-90.0f), X);
simpleShader->setModelMatrix(saber_model_matrix);
saber.render();
glm::mat4 bugatti_model_matrix = glm::translate(I, glm::vec3(5.0f, -1.0f, 0.0f))
* glm::scale(I, glm::vec3(0.2f, 0.2f, 0.2f)) * glm::rotate(I, glm::radians(0.0f), X);
simpleShader->setModelMatrix(bugatti_model_matrix);
bugatti.render();
}
~MyApp(){
if(simpleShader != NULL){
delete simpleShader;
}
}
};
DECLARE_MAIN(MyApp)
效果如下:
我這里只展示了三個美女和一個汽車,從左側的文件列表中可以看出,我還收集了不少其它的模型,以后再逐漸與大家見面。
目前看起來還只是白茫茫一片,那是因為模型太精細了,所以看不清楚網格。下一步,我將在我的程序框架中添加改變窗口大小、在場景中漫游、線框模型和面模型之間切換等功能。今天就到這里吧。
版權申明
該隨筆由京山游俠在2021年08月07日發布於博客園,引用請注明出處,轉載或出版請聯系博主。QQ郵箱:1841079@qq.com