OpenGL-使用Assimp加載3d模型

目錄node

加載前的準備ios

下載並編譯Assimpgit

配置Assimpgithub

.dll文件的配置數組

.lib文件的配置koa

assimp文件的配置ide

舉個栗子函數

代碼佈局

Mesh.hpost

Model.h

main.cpp

注意項

結果截圖

資源下載


參考:LearnOpenGL

最終結果

模型1
模型2

 注:因爲CSDN上傳的gif不能超過5兆,因此比較模糊,最後有清晰結果截圖。

加載前的準備

見上一篇文章:下載並編譯Assimp

配置Assimp

.dll文件的配置

將生成的.dll文件放在.exe文件的同級目錄下

.dll文件配置

.lib文件的配置

將生成的.lib文件放到你的庫目錄下,並在項目的附加依賴中添加.lib文件

.lib文件配置

assimp文件的配置

在include下的assimmp文件放到你的庫目錄下


assimp文件配置

在你的模型加載類中包含頭文件

#include <assimp/Importer.hpp>        //assimp庫頭文件
#include <assimp/scene.h>
#include <assimp/postprocess.h>
舉個栗子

代碼

Mesh.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);
	}
};

Model.h

#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;
}

main.cpp

//頭文件
#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

結果截圖
模型1結果截圖
模型2結果截圖
資源下載·

更多OpenGL知識:現代OpenGL入門教程

有問題請下方評論,轉載請註明出處,並附有原文連接,謝謝!若有侵權,請及時聯繫。

相關文章
相關標籤/搜索