关于openGl模型的加载需要使用到Assimp库,非常喜闻乐见的是如果你用的编译器是Vs的话,就能通过NuGet直接获取到这个库,装载即可。我这里就不演示了!直接去网上搜索一下就知道怎么安装包了。
根据上图,我们可以看到模型中有一个scene场景,然后左边的是则是各种节点,我们要的做就是遍历左边的接单获取到右边的数据。
我们可以看到我们目前还缺少一个网格类。
Mesh.h
#pragma once
#include<glm/glm.hpp>
#include<string>
#include<vector>
#include"Shader.h"
#include<Gl/glew.h>
#include <assimp/Importer.hpp>
struct Vertex//顶点结构体
{
glm::vec3 Position;
glm::vec3 Normal;
glm::vec2 TexCoords;
};
struct Texture{//贴图结构体
unsigned int id;
std::string type;
aiString path;
};
class Mesh
{
public :
std::vector<Vertex> vertices;//顶点容器,似乎就是个动态列表
std::vector<Texture> textures;//贴图容器
std::vector<unsigned int> indices;//EBO绘制的索引,
Mesh(float vertices[]);//给你测试网格类是否出错的,输入我们前面的那个组就能直接绘画出正方体
Mesh(std::vector<Vertex> vertices,std::vector<Texture> textures,std::vector<unsigned int> indices);//这是带三个参数构造函数
void Draw(Shader* shader);//绘制
private:
unsigned int VAO, VBO, EBO;//这些相信你应该还有映像吧,就是我们将顶点的绘制封装成一个类
void SteupMesh();//初始化网格类
};
Mesh.cpp
#include "Mesh.h"
#include <string>
#include<iostream>
Mesh::Mesh(float vertices[])//测试用的
{
this->vertices.resize(36);
memcpy(&(this->vertices[0]),vertices,36*8*sizeof(float));
SteupMesh();
}
Mesh::Mesh(std::vector<Vertex> vertices, std::vector<Texture> textures, std::vector<unsigned int> indices)//构造
{
this->vertices = vertices;
this->indices = indices;
this->textures = textures;
SteupMesh();
}
void Mesh::Draw(Shader* shader)//绘制网格
{
for (GLuint i = 0; i < this->textures.size(); i++)//遍历贴图容器
{
std::string name = this->textures[i].type;
if (name == "texture_diffuse")//如果类型为diffuse,就放到0号槽位
{
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D,this->textures[i].id);
shader->SetUniform1i("material.diffuse",0);
}
else if(name =="texture_specular")//如果类型为specular,就放到1号槽位
{
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, this->textures[i].id);
shader->SetUniform1i("material.specular",1);
}
}
glBindVertexArray(this->VAO);
//测试时绘制正方体时使用,
//glDrawArrays(GL_TRIANGLES,0,36);
//根据元素索引绘制,测试时需要注释掉
glDrawElements(GL_TRIANGLES,indices.size(),GL_UNSIGNED_INT,0);
glBindVertexArray(0);
glBindTexture(GL_TEXTURE_2D,0);
}
//这个就不讲了,就是绘制网格的流水线配置
void Mesh::SteupMesh()
{
glGenVertexArrays(1,&VAO);
glBindVertexArray(VAO);
glGenBuffers(1,&VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER,sizeof(Vertex)* vertices.size(),&vertices[0],GL_STATIC_DRAW);
//测试绘制的时候,需要注释掉下面三行,因为测试绘制没有索引属性。
glGenBuffers(1,&EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER,sizeof(unsigned int)*indices.size(),&indices[0],GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);
glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,sizeof(Vertex),(GLvoid*)0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*)(3*sizeof(GL_FLOAT)));
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*)(6 * sizeof(GL_FLOAT)));
glBindVertexArray(0);
}
如要测试,只要在main.cpp中输入
Mesh mesh(vertices);
mesh.Draw(myShader);
就能得到一个正方体了,注意该注释的要注释掉。
接下来是
Model.h
#pragma once
#include<string>
#include<vector>
#include"Mesh.h"
#include"Shader.h"
#include<iostream>
#include <assimp/Importer.hpp>
#include <assimp/scene.h>
#include <assimp/postprocess.h>
class Model
{
public :
Model(std::string path);//构造
void Draw(Shader* shader);
private:
std::vector<Mesh> meshes;//网格容器
std::vector<Texture> textures_loaded;//记录已经加载过的贴图
std::string directory;//路径
void loadModel(std::string path);//加载模型
void processNode(aiNode* node, const aiScene *scene);//查找节点
Mesh processMesh(aiMesh* mesh, const aiScene* scene);//转换为我们自己的Mesh类
std::vector<Texture> loadMaterialTextures(aiMaterial *mat,aiTextureType type,std::string typeName);//通过类型加载贴图
GLint TextureFromFile(const char* path, std::string directory);//通过文件路径下载贴图
};
Model.cpp
#include "Model.h"
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
using namespace std;
#include<vector>
Model::Model(std::string path)//构造并加载模型
{
loadModel(path);
}
void Model::Draw(Shader* shader)
{
for (unsigned int i = 0; i < meshes.size(); i++)//将获取到网格全部绘制出来
{
meshes[i].Draw(shader);
}
}
void Model::loadModel(const std::string path)//通过文件路径加载模型
{
Assimp::Importer importer;
//读取obj文件
const aiScene* scene = importer.ReadFile(path,aiProcess_Triangulate|aiProcess_FlipUVs|aiProcess_CalcTangentSpace);
if (!scene||scene->mFlags&AI_SCENE_FLAGS_INCOMPLETE||!scene->mRootNode) //没找到就退出
{
std::cout << "ERROR::ASSIMP::" << importer.GetErrorString() << std::endl;
return;
}
//获取到obj文件的所在文件夹地址,如...\model\one.obj,得到就是...\model
this->directory = path .substr(0, path.find_last_of("\\"));
processNode(scene->mRootNode,scene);//递归遍历模型的所有节点
}
void Model::processNode(aiNode* node, const aiScene* scene)//递归遍历节点
{
for (unsigned int i = 0; i < node->mNumMeshes; i++)//遍历当前节点的所有网格,
{
aiMesh* curMesh = scene->mMeshes[node->mMeshes[i]];//获取到对应的网格配置
this->meshes.push_back(processMesh(curMesh,scene));//将网格转换成我们的网格,加到我们的meshes容器里
}
for (unsigned int i = 0; i < node->mNumChildren; i++)//递归遍历节点
{
processNode(node->mChildren[i],scene);
}
}
Mesh Model::processMesh(aiMesh* mesh, const aiScene* scene)//转换Mesh类
{
std::vector<Vertex> vertics;
std::vector<GLuint>indices;
std::vector<Texture>textures;
for (unsigned int i = 0; i < mesh->mNumVertices; i++)
{
//交换Vertex值
Vertex tempVertex;
tempVertex.Position.x = mesh->mVertices[i].x;
tempVertex.Position.y = mesh->mVertices[i].y;
tempVertex.Position.z = mesh->mVertices[i].z;
tempVertex.Normal.x = mesh->mNormals[i].x;
tempVertex.Normal.y = mesh->mNormals[i].y;
tempVertex.Normal.z = mesh->mNormals[i].z;
if (mesh->mTextureCoords[0])
{
tempVertex.TexCoords.x = mesh->mTextureCoords[0][i].x;
tempVertex.TexCoords.y = mesh->mTextureCoords[0][i].y;
}
else
{
tempVertex.TexCoords = glm::vec2(0.0f,0.0f);
}
vertics.push_back(tempVertex);
}
//获取所有索引
for (unsigned int i = 0; i < mesh->mNumFaces; i++)//遍历所有面
{
for (unsigned int j = 0; j < mesh->mFaces[i].mNumIndices; j++)//遍历对应面的所有索引
{
indices.push_back(mesh->mFaces[i].mIndices[j]);
}
}
if (mesh->mMaterialIndex>=0)//材质不为0时,运行
{
aiMaterial* material = scene->mMaterials[mesh->mMaterialIndex];//获取到aiMesh的当前材质
std::vector<Texture> diffuseMaps = this->loadMaterialTextures(material,aiTextureType_DIFFUSE,"texture_diffuse");//获取diffuse贴图
textures.insert(textures.end(),diffuseMaps.begin(),diffuseMaps.end());//插入diffuse贴图
std::vector<Texture> specularMaps = this->loadMaterialTextures(material, aiTextureType_SPECULAR, "texture_specular");//获取所有specular贴图
textures.insert(textures.end(), specularMaps.begin(), specularMaps.end());//插入specular贴图
}
return Mesh(vertics,textures,indices);//转换成我们的Mesh类
}
//获取对应类型的贴图
std::vector<Texture> Model::loadMaterialTextures(aiMaterial* mat, aiTextureType type, std::string typeName)
{
std::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.C_Str(), str.C_Str()) == 0 )
{
textures.push_back(textures_loaded[j]);
skip = true; // a texture with the same filepath has already been loaded, continue to next one. (optimization)
break;
}
}
if (!skip)//加载新贴图
{
Texture texture;
texture.id = TextureFromFile(str.C_Str(), this->directory);
texture.path = str;//str
texture.type = typeName;
textures.push_back(texture);
this->textures_loaded.push_back(texture);//加载进当前函数贴图容器
}
}
return textures;
}
GLint Model::TextureFromFile( const char* path,std::string directory)//从文件中加载贴图
{
//配置文件路径
std::string fileName = std::string(path);
fileName = directory + '\\' + fileName;
GLuint textureID;
glGenTextures(1,&textureID);
int width, height, nrChannels;
//这就是前面那个LoadImageToGpu的内容,加载贴图。
unsigned char* image = stbi_load(fileName.c_str(), &width, &height, &nrChannels, 0);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, textureID);
if(image)
{
GLenum format;//判断贴图通道
if (nrChannels == 1)
format = GL_RED;
else if(nrChannels == 3)
format = GL_RGB;
else if (nrChannels==4)
format = GL_RGBA;
glTexImage2D(GL_TEXTURE_2D,0,format,width,height,0,format,GL_UNSIGNED_BYTE,image);
glGenerateMipmap(GL_TEXTURE_2D);
}
else
{
std::cout << "Error! stbi load fail!" << std::endl;
}
glUniform1i(glGetUniformLocation(myShader->ID, "material.diffuse"), 0);
stbi_image_free(image);
glBindTexture(GL_TEXTURE_2D, 0);
return textureID;
}
main.cpp
#include<iostream>
#define GLEW_STATIC
#include <GL/glew.h>
#include<GLFW/glfw3.h>
//#define STB_IMAGE_IMPLEMENTATION
//#include "stb_image.h"
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include "Shader.h"
#include "Material.h"
#include "Camera.h"
#include "LightDirectional.h"
#include "LightPoint.h"
#include "LightSpot.h"
#include "Mesh.h"
#include "Model.h"
using namespace std;
#pragma region Model Data
GLfloat vertices[] = {
// Positions // Normals // Texture Coords
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f,
0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f,
0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,
0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f
};
glm::vec3 cubePositions[] = {
glm::vec3(0.0f, 0.0f, 0.0f),
glm::vec3(2.0f, 5.0f, -15.0f),
glm::vec3(-1.5f, -2.2f, -2.5f),
glm::vec3(-3.8f, -2.0f, -12.3f),
glm::vec3(2.4f, -0.4f, -3.5f),
glm::vec3(-1.7f, 3.0f, -7.5f),
glm::vec3(1.3f, -2.0f, -2.5f),
glm::vec3(1.5f, 2.0f, -2.5f),
glm::vec3(1.5f, 0.2f, -1.5f),
glm::vec3(-1.3f, 1.0f, -1.5f)
};
#pragma endregion
#pragma region Input Declare
void ProcessInput(GLFWwindow* window);
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
// Window dimensions
const GLuint WIDTH = 800, HEIGHT = 600;
#pragma region Camera Declare
Camera* myCamera = new Camera(glm::vec3(0, 0, 5.0f), glm::radians(-15.0f), glm::radians(180.0f), glm::vec3(0, 1.0f, 0));
#pragma endregion
#pragma region LightDriection Drclare
LightDirectional myLightD(glm::vec3(10.0f,10.0f,5.0f),glm::vec3(glm::radians(0.0f), glm::radians(0.0f),0),glm::vec3(1.0f,1.0f,1.0f));
#pragma endregion
// The MAIN function, from here we start the application and run the game loop
int main(int argc, char* argv[])
{
std::string directory = argv[0];
directory = directory.substr(0, directory.find_last_of("\\")) + "\\Model\\nanosuit.obj";
#pragma region Open a Window
// Init GLFW
glfwInit();
// Set all the required options for GLFW
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
// Create a GLFWwindow object that we can use for GLFW's functions
GLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "LearnOpenGL", nullptr, nullptr);
glfwMakeContextCurrent(window);
//隐藏鼠标
glfwSetInputMode(window,GLFW_CURSOR,GLFW_CURSOR_DISABLED);
//鼠标移动时呼叫mouse_callback函数
glfwSetCursorPosCallback(window, mouse_callback);
// Set this to true so GLEW knows to use a modern approach to retrieving function pointers and extensions
glewExperimental = GL_TRUE;
Initialize GLEW to setup the OpenGL Function pointers
glewInit();
// Define the viewport dimensions
glViewport(0, 0, WIDTH, HEIGHT);
#pragma endregion
Model model(directory);
#pragma region Create Matrix
glm::mat4 viewMat;
glm::mat4 projMat;
projMat = glm::perspective(glm::radians(45.0f), (float)WIDTH / (float)HEIGHT, 0.01f, 100.0f);
glEnable(GL_DEPTH_TEST);
#pragma endregion
#pragma region Init Shader
Shader* myShader = new Shader("vertexSource.vert", "fragmentSource.frag");
#pragma endregion
// Game loop
while (!glfwWindowShouldClose(window))
{ // 检查事件,调用相应的回调函数,如下文的glfwInput函数
//Process Input
ProcessInput(window);
//Clear screen
glClearColor(0.3f, 0.3f, 0.3f, 1.0f);//渲染颜色到后台缓冲
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);//清除前台缓冲
viewMat = myCamera->GetViewMatrix();//刷新视角
for (size_t i = 1; i <= 2; i++)
{
glm::mat4 modelMat;//模型矩阵
modelMat = glm::translate(modelMat, cubePositions[i]);
modelMat = glm::rotate(modelMat, glm::radians(i*10.0f), glm::vec3(0, 1.0f, 0));
//Uesd ShaderProgram
myShader->Use();
glUniformMatrix4fv(glGetUniformLocation(myShader->ID, "modelMat"), 1, GL_FALSE, glm::value_ptr(modelMat));
glUniformMatrix4fv(glGetUniformLocation(myShader->ID, "viewMat"), 1, GL_FALSE, glm::value_ptr(viewMat));
glUniformMatrix4fv(glGetUniformLocation(myShader->ID, "projMat"), 1, GL_FALSE, glm::value_ptr(projMat));
glUniform3f(glGetUniformLocation(myShader->ID, "objColor"), 1.0f, 1.0f, 1.0f);
glUniform3f(glGetUniformLocation(myShader->ID, "ambientColor"), 0.1f, 0.1f, 0.1f);
glUniform3f(glGetUniformLocation(myShader->ID, "CameraPos"), myCamera->Position.x, myCamera->Position.y, myCamera->Position.z);
glUniform3f(glGetUniformLocation(myShader->ID, "lightD.color"), myLightD.color.x, myLightD.color.y, myLightD.color.z);
glUniform3f(glGetUniformLocation(myShader->ID, "lightD.pos"), myLightD.position.x, myLightD.position.y, myLightD.position.z);
glUniform3f(glGetUniformLocation(myShader->ID, "lightD.dirToLight"), myLightD.direction.x, myLightD.direction.y, myLightD.direction.z);
myShader->SetUniform1f("material.shininess", 32);
model.Draw(myShader);
}
glfwSwapBuffers(window);
glfwPollEvents();
}
glfwTerminate();
return 0;
}
#pragma region Input Fuction
// Is called whenever a key is pressed/released via GLFW
void ProcessInput(GLFWwindow* window)//通过监控鼠标来控制相机的移动
{
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, GL_TRUE);
if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
{
myCamera->CameraPosMoveZ(1);
}
else if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
{
myCamera->CameraPosMoveZ( -1);
}
else if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
{
myCamera->CameraPosMoveX(1);
}
else if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
{
myCamera->CameraPosMoveX(-1);
}
else if (glfwGetKey(window, GLFW_KEY_Q) == GLFW_PRESS)
{
myCamera->CameraPosMoveY(1);
}
else if (glfwGetKey(window, GLFW_KEY_E) == GLFW_PRESS)
{
myCamera->CameraPosMoveY(-1);
}
}
bool firstMouse = true;
float lastx = 300, lasty = 400;
void mouse_callback(GLFWwindow* window, double xpos, double ypos)//检查鼠标输入
{
if (firstMouse==true)//第一次调用该函数的时候直接将当前坐标给过去坐标值,已此来防止一开始的时候出现视角的巨大晃动
{
lastx = xpos;
lasty = ypos;
firstMouse = false;
}
float offsetx = xpos - lastx;
float offsety = ypos - lasty;
//std::cout << offsetx << std::endl;
lastx = xpos;
lasty = ypos;
myCamera->CameraViewMove(offsetx,offsety);//调用CameraViewMove函数
}
#pragma endregion
这是效果图:
最后再记录一个我遇到的问题,被坑了很久,就是如果你的OpenGL加载模型的时候出现了贴图只有灰色的效果。
如下图:
那就是你的贴图加载通道出了问题。比如这个就是因为模型的贴图为RGBA的,可是我一开始只给了RGB,所以就成了这样。而且这种贴图还是加载成功了的。所以很难排错,所以你以后还是尽量写成那种自动配置颜色通道的会比较好。
就是这句代码:
glTexImage2D(GL_TEXTURE_2D,0, format,width,height,0, format,GL_UNSIGNED_BYTE,image);