目錄node
加載前的準備ios
下載並編譯Assimpgit
配置Assimpgithub
.lib文件的配置koa
assimp文件的配置ide
舉個栗子函數
代碼佈局
Mesh.hpost
參考:LearnOpenGL
最終結果
注:因爲CSDN上傳的gif不能超過5兆,因此比較模糊,最後有清晰結果截圖。
加載前的準備見上一篇文章:下載並編譯Assimp
將生成的.dll文件放在.exe文件的同級目錄下
將生成的.lib文件放到你的庫目錄下,並在項目的附加依賴中添加.lib文件
在include下的assimmp文件放到你的庫目錄下
在你的模型加載類中包含頭文件
#include <assimp/Importer.hpp> //assimp庫頭文件 #include <assimp/scene.h> #include <assimp/postprocess.h>舉個栗子
#pragma once #include <glad/glad.h> // 全部頭文件 #include <glm/glm.hpp> #include <glm/gtc/matrix_transform.hpp> #include "Shader.h" #include <string> #include <fstream> #include <sstream> #include <iostream> #include <vector> using namespace std; //頂點 struct Vertex { // 位置 glm::vec3 Position; // 法向量 glm::vec3 Normal; // 紋理座標 glm::vec2 TexCoords; // u向量 glm::vec3 Tangent; // v向量 glm::vec3 Bitangent; }; //紋理 struct Texture { unsigned int id; string type; string path; }; //Mesh類 class Mesh { public: /* Mesh 數據 */ vector<Vertex> vertices; vector<unsigned int> indices; vector<Texture> textures; unsigned int VAO; /* 函數 */ // 構造函數 參數:頂點 索引 紋理 Mesh(vector<Vertex> vertices, vector<unsigned int> indices, vector<Texture> textures) { this->vertices = vertices; this->indices = indices; this->textures = textures; // 如今咱們擁有了全部必需的數據,設置頂點緩衝區及其屬性指針。 setupMesh(); } // 畫網格模型 void Draw(Shader shader) { // 綁定適當的紋理 unsigned int diffuseNr = 1; unsigned int specularNr = 1; unsigned int normalNr = 1; unsigned int heightNr = 1; for (unsigned int i = 0; i < textures.size(); i++) { glActiveTexture(GL_TEXTURE0 + i); // 綁定前激活適當的紋理單元 // 獲取紋理編號(diffuse_textureN中的N) string number; string name = textures[i].type; if (name == "texture_diffuse") number = std::to_string(diffuseNr++); else if (name == "texture_specular") number = std::to_string(specularNr++); else if (name == "texture_normal") number = std::to_string(normalNr++); else if (name == "texture_height") number = std::to_string(heightNr++); // 如今將採樣器設置爲正確的紋理單元 glUniform1i(glGetUniformLocation(shader.ID, (name + number).c_str()), i); // 最後綁定紋理 glBindTexture(GL_TEXTURE_2D, textures[i].id); } // 畫網格 glBindVertexArray(VAO); glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, 0); glBindVertexArray(0); // 一旦配置完畢將一切設置回默認值老是很好的作法,。 glActiveTexture(GL_TEXTURE0); } private: /* 渲染數據 */ unsigned int VBO, EBO; /* 函數 */ // 初始化全部緩衝區對象/數組 void setupMesh() { // 建立緩衝區/數組 glGenVertexArrays(1, &VAO); glGenBuffers(1, &VBO); glGenBuffers(1, &EBO); glBindVertexArray(VAO); //將數據加載到頂點緩衝區中 glBindBuffer(GL_ARRAY_BUFFER, VBO); // 關於結構的一個好處是它們的內存佈局對於它的全部項都是順序的。 // 結果是咱們能夠簡單地將指針傳遞給結構,而且它完美地轉換爲glm :: vec3 / 2數組,該數組再次轉換爲3/2浮點數,轉換爲字節數組。 glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(Vertex), &vertices[0], GL_STATIC_DRAW); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), &indices[0], GL_STATIC_DRAW); // 設置頂點屬性指針 // 頂點位置 glEnableVertexAttribArray(0); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)0); // 頂點法線 glEnableVertexAttribArray(1); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, Normal)); // 頂點紋理座標 glEnableVertexAttribArray(2); glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, TexCoords)); // u向量 glEnableVertexAttribArray(3); glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, Tangent)); // v向量 glEnableVertexAttribArray(4); glVertexAttribPointer(4, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, Bitangent)); glBindVertexArray(0); } };
#pragma once #include <glad/glad.h> //全部頭文件 #include <glm/glm.hpp> #include <glm/gtc/matrix_transform.hpp> #define STB_IMAGE_IMPLEMENTATION //原做者沒寫 #include <stb_image.h> #include <assimp/Importer.hpp> //assimp庫頭文件 #include <assimp/scene.h> #include <assimp/postprocess.h> #include "Mesh.h" #include "Shader.h" #include <string> #include <fstream> #include <sstream> #include <iostream> #include <map> #include <vector> using namespace std; //從文件中讀取紋理 unsigned int TextureFromFile(const char *path, const string &directory, bool gamma = false); //Model類 class Model { public: /* Model數據 */ //存儲到目前爲止加載的全部紋理,優化以確保紋理不會被加載屢次。 vector<Texture> textures_loaded; vector<Mesh> meshes; string directory; bool gammaCorrection; /* 函數 */ // 構造漢化,須要一個3D模型的文件路徑 Model(string const &path, bool gamma = false) : gammaCorrection(gamma) { loadModel(path); } // 繪製模型,從而繪製全部網格 void Draw(Shader shader) { for (unsigned int i = 0; i < meshes.size(); i++) meshes[i].Draw(shader); } private: /* 函數 */ // 從文件加載支持ASSIMP擴展的模型,並將生成的網格存儲在網格矢量中。 void loadModel(string const &path) { // 經過ASSIMP讀文件 Assimp::Importer importer; const aiScene* scene = importer.ReadFile(path, aiProcess_Triangulate | aiProcess_FlipUVs | aiProcess_CalcTangentSpace); // 檢查錯誤 if (!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode) // 若是不是0 { cout << "錯誤::ASSIMP:: " << importer.GetErrorString() << endl; return; } // 檢索文件路徑的目錄路徑 directory = path.substr(0, path.find_last_of('/')); // 以遞歸方式處理ASSIMP的根節點 processNode(scene->mRootNode, scene); } // 以遞歸方式處理節點。 處理位於節點處的每一個單獨網格,並在其子節點(若是有)上重複此過程。 void processNode(aiNode *node, const aiScene *scene) { // 處理位於當前節點的每一個網格 for (unsigned int i = 0; i < node->mNumMeshes; i++) { // 節點對象僅包含索引用來索引場景中的實際對象。 // 場景包含全部數據,節點只是爲了有組織的保存東西(如節點之間的關係)。 aiMesh* mesh = scene->mMeshes[node->mMeshes[i]]; meshes.push_back(processMesh(mesh, scene)); } // 在咱們處理完全部網格(若是有的話)後,咱們會遞歸處理每一個子節點 for (unsigned int i = 0; i < node->mNumChildren; i++) { processNode(node->mChildren[i], scene); } } Mesh processMesh(aiMesh *mesh, const aiScene *scene) { // 要填寫的數據 vector<Vertex> vertices; vector<unsigned int> indices; vector<Texture> textures; // 遍歷每一個網格的頂點 for (unsigned int i = 0; i < mesh->mNumVertices; i++) { Vertex vertex; // 咱們聲明一個佔位符向量,由於assimp使用它本身的向量類,它不直接轉換爲glm的vec3類,因此咱們首先將數據傳遞給這個佔位符glm :: vec3。 glm::vec3 vector; // 位置 vector.x = mesh->mVertices[i].x; vector.y = mesh->mVertices[i].y; vector.z = mesh->mVertices[i].z; vertex.Position = vector; // 法線 vector.x = mesh->mNormals[i].x; vector.y = mesh->mNormals[i].y; vector.z = mesh->mNormals[i].z; vertex.Normal = vector; // 紋理座標 if (mesh->mTextureCoords[0]) // 網格是否包含紋理座標? { glm::vec2 vec; // 頂點最多可包含8個不一樣的紋理座標。 所以,咱們假設咱們不會使用頂點能夠具備多個紋理座標的模型,所以咱們老是採用第一個集合(0)。 vec.x = mesh->mTextureCoords[0][i].x; vec.y = mesh->mTextureCoords[0][i].y; vertex.TexCoords = vec; } else vertex.TexCoords = glm::vec2(0.0f, 0.0f); // u向量 vector.x = mesh->mTangents[i].x; vector.y = mesh->mTangents[i].y; vector.z = mesh->mTangents[i].z; vertex.Tangent = vector; // v向量 vector.x = mesh->mBitangents[i].x; vector.y = mesh->mBitangents[i].y; vector.z = mesh->mBitangents[i].z; vertex.Bitangent = vector; vertices.push_back(vertex); } //如今遍歷每一個網格面(一個面是一個三角形的網格)並檢索相應的頂點索引。 for (unsigned int i = 0; i < mesh->mNumFaces; i++) { aiFace face = mesh->mFaces[i]; // 檢索麪的全部索引並將它們存儲在索引向量中 for (unsigned int j = 0; j < face.mNumIndices; j++) indices.push_back(face.mIndices[j]); } // 加工材料 aiMaterial* material = scene->mMaterials[mesh->mMaterialIndex]; // 咱們假設着色器中的採樣器名稱約定。 每一個漫反射紋理應命名爲'texture_diffuseN',其中N是從1到MAX_SAMPLER_NUMBER的序列號。 //一樣適用於其餘紋理,以下列總結: // diffuse: texture_diffuseN // specular: texture_specularN // normal: texture_normalN // 1. 漫反射貼圖 vector<Texture> diffuseMaps = loadMaterialTextures(material, aiTextureType_DIFFUSE, "texture_diffuse"); textures.insert(textures.end(), diffuseMaps.begin(), diffuseMaps.end()); // 2. 高光貼圖 vector<Texture> specularMaps = loadMaterialTextures(material, aiTextureType_SPECULAR, "texture_specular"); textures.insert(textures.end(), specularMaps.begin(), specularMaps.end()); // 3.法線貼圖 std::vector<Texture> normalMaps = loadMaterialTextures(material, aiTextureType_HEIGHT, "texture_normal"); textures.insert(textures.end(), normalMaps.begin(), normalMaps.end()); // 4. 高度貼圖 std::vector<Texture> heightMaps = loadMaterialTextures(material, aiTextureType_AMBIENT, "texture_height"); textures.insert(textures.end(), heightMaps.begin(), heightMaps.end()); // 返回從提取的網格數據建立的網格對象 return Mesh(vertices, indices, textures); } // 檢查給定類型的全部材質紋理,若是還沒有加載紋理,則加載紋理。 // 所需信息做爲Texture結構返回。 vector<Texture> loadMaterialTextures(aiMaterial *mat, aiTextureType type, string typeName) { vector<Texture> textures; for (unsigned int i = 0; i < mat->GetTextureCount(type); i++) { aiString str; mat->GetTexture(type, i, &str); // 檢查以前是否加載了紋理,若是是,則繼續下一次迭代:跳過加載新紋理 bool skip = false; for (unsigned int j = 0; j < textures_loaded.size(); j++) { if (std::strcmp(textures_loaded[j].path.data(), str.C_Str()) == 0) { textures.push_back(textures_loaded[j]); skip = true; break;// 已加載具備相同文件路徑的紋理,繼續下一個(優化)。 } } if (!skip) { // 若是還沒有加載紋理,請加載它 Texture texture; texture.id = TextureFromFile(str.C_Str(), this->directory); texture.type = typeName; texture.path = str.C_Str(); textures.push_back(texture); textures_loaded.push_back(texture); //將其存儲爲整個模型加載的紋理,以確保咱們不會加載重複紋理。 } } return textures; } }; //從文件讀取紋理函數 unsigned int TextureFromFile(const char *path, const string &directory, bool gamma) { string filename = string(path); filename = directory + '/' + filename; unsigned int textureID; glGenTextures(1, &textureID); int width, height, nrComponents; unsigned char *data = stbi_load(filename.c_str(), &width, &height, &nrComponents, 0); if (data) { GLenum format; if (nrComponents == 1) format = GL_RED; else if (nrComponents == 3) format = GL_RGB; else if (nrComponents == 4) format = GL_RGBA; glBindTexture(GL_TEXTURE_2D, textureID); glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data); glGenerateMipmap(GL_TEXTURE_2D); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); stbi_image_free(data); } else { std::cout << "紋理沒法今後路徑加載: " << path << std::endl; stbi_image_free(data); } return textureID; }
//頭文件 #include <glad/glad.h> #include <GLFW/glfw3.h> #include <glm/glm.hpp> #include <glm/gtc/matrix_transform.hpp> #include <glm/gtc/type_ptr.hpp> #include "Shader.h" #include "Camera.h" #include "Model.h" #include <iostream> //-----------------------------------函數聲明------------------------------------------- void framebuffer_size_callback(GLFWwindow* window, int width, int height); void mouse_callback(GLFWwindow* window, double xpos, double ypos); void scroll_callback(GLFWwindow* window, double xoffset, double yoffset); void processInput(GLFWwindow *window); //-------------------------------------全局變量------------------------------------------- //窗體寬高 const unsigned int SCR_WIDTH = 800; const unsigned int SCR_HEIGHT = 600; //攝像機相關 Camera camera(glm::vec3(0.0f, 0.0f, 3.0f)); float lastX = SCR_WIDTH / 2.0f; float lastY = SCR_HEIGHT / 2.0f; bool firstMouse = true; // 時間 float deltaTime = 0.0f; float lastFrame = 0.0f; //主函數 int main() { // glfw: 初始化和配置 glfwInit(); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); #ifdef __APPLE__ glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // uncomment this statement to fix compilation on OS X #endif // glfw 窗體建立 GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "ModelDemo", NULL, NULL); if (window == NULL) { std::cout << "建立GLFW窗體失敗" << std::endl; glfwTerminate(); return -1; } glfwMakeContextCurrent(window); glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); glfwSetCursorPosCallback(window, mouse_callback); glfwSetScrollCallback(window, scroll_callback); // 鼠標滑動回調函數 glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); // glad: load all OpenGL function pointers if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { std::cout << "Failed to initialize GLAD" << std::endl; return -1; } // 設置全局opengl狀態 glEnable(GL_DEPTH_TEST);//開啓深度測試 //建立並編譯shader Shader ourShader("vertexSource.txt", "fragmentSource.txt"); // 加載模型 //FileSystem::getPath("resources/objects/nanosuit/nanosuit.obj") //修改成相對路徑 Model ourModel("../Debug/model/nanosuit/nanosuit.obj"); //Model ourModel("../Debug/model/warrior/arakkoa_warrior.obj"); // draw in wireframe //glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); //循環渲染 while (!glfwWindowShouldClose(window)) { //獲取時間 float currentFrame = glfwGetTime(); deltaTime = currentFrame - lastFrame; lastFrame = currentFrame; // 鍵盤輸入 processInput(window); // 渲染 glClearColor(0.05f, 0.05f, 0.05f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 設置uniforms前使用Shader ourShader.use(); // view/projection矩陣 glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f); glm::mat4 view = camera.GetViewMatrix(); ourShader.setMat4("projection", projection); ourShader.setMat4("view", view); // 渲染加載的3d模型 glm::mat4 model = glm::mat4(1.0f); //使其位於場景的中心 model = glm::translate(model, glm::vec3(0.0f, -1.75f, 0.0f)); //縮小它 model = glm::scale(model, glm::vec3(0.2f, 0.2f, 0.2f)); ourShader.setMat4("model", model); ourModel.Draw(ourShader); // glfw: 交換緩衝區和輪詢IO事件(按下/釋放按鍵,移動鼠標等) glfwSwapBuffers(window); glfwPollEvents(); } // glfw: 終止,清除全部先前分配的GLFW資源。 glfwTerminate(); return 0; } //鍵盤按鍵控制 void processInput(GLFWwindow *window) { if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) glfwSetWindowShouldClose(window, true); if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) camera.ProcessKeyboard(Camera::FORWARD, deltaTime); if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) camera.ProcessKeyboard(Camera::BACKWARD, deltaTime); if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) camera.ProcessKeyboard(Camera::LEFT, deltaTime); if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) camera.ProcessKeyboard(Camera::RIGHT, deltaTime); } // glfw: 窗口改變回調函數 void framebuffer_size_callback(GLFWwindow* window, int width, int height) { glViewport(0, 0, width, height); } // glfw: 鼠標滑動回調函數 void mouse_callback(GLFWwindow* window, double xpos, double ypos) { if (firstMouse) { lastX = xpos; lastY = ypos; firstMouse = false; } float xoffset = xpos - lastX; float yoffset = lastY - ypos; lastX = xpos; lastY = ypos; camera.ProcessMouseMovement(xoffset, yoffset); } // glfw: 鼠標滾輪迴調函數 void scroll_callback(GLFWwindow* window, double xoffset, double yoffset) { camera.ProcessMouseScroll(yoffset); }
採用相對路徑,.obj文件及所用圖片放在同一文件夾內
着色器類增長了一個setMat4函數,照以前的座標系統那一篇文章加上就好
Model類中,包含stb_image.h前須要宏定義 #define STB_IMAGE_IMPLEMENTATION
更多OpenGL知識:現代OpenGL入門教程
有問題請下方評論,轉載請註明出處,並附有原文連接,謝謝!若有侵權,請及時聯繫。