模型
通过assimp加载模型并转换为多个上一节创建的Mesh对象。
首先构建Model类
class Model
{
public:
Model(char *path)
{
loadModel(path);
}
void Draw(Shader shader);
private:
/* 模型数据 */
vector<Texture> textures_loaded;
vector<Mesh> meshes;
string directory;
/* 函数 */
void loadModel(string path);
void processNode(aiNode *node, const aiScene *scene);
Mesh processMesh(aiMesh *mesh, const aiScene *scene);
vector<Texture> loadMaterialTextures(aiMaterial *mat, aiTextureType type,
string typeName);
};
在构造器中加载模型,在私有函数中处理assimp导入模型的这一过程。同时我们还会将已加载的纹理路径存储起来,以便重复使用。
Draw函数通过调用Mesh中的绘制函数实现。
void Draw(Shader shader)
{
for(unsigned int i = 0; i < meshes.size(); i++)
meshes[i].Draw(shader);
}
导入3D模型到OpenGL
添加assimp头文件
#include <assimp/Importer.hpp>
#include <assimp/scene.h>
#include <assimp/postprocess.h>
首先需要实现的是loadModel(path)函数,模型通过assimp会加载到一个scene对象中,而有了这个对象,我们就能够拿到模型中的所有数据了。
Assimp很棒的一点在于,它抽象掉了加载不同文件格式的所有技术细节,只需要一行代码就能完成所有的工作:
Assimp::Importer importer;
const aiScene *scene = importer.ReadFile(path, aiProcess_Triangulate | aiProcess_FlipUVs);
这两步就能实现加载,在ReadFile函数中,还可以进行一些限制以及计算等,通过设定aiProcess_Triangulate,我们告诉Assimp,如果模型不是(全部)由三角形组成,它需要将模型所有的图元形状变换为三角形。aiProcess_FlipUVs将在处理的时候翻转y轴的纹理坐标(在OpenGL中大部分的图像的y轴都是反的,所以这个后期处理选项将会修复这个)。
加载模型
void loadModel(string path)
{
Assimp::Importer import;
const aiScene *scene = import.ReadFile(path, aiProcess_Triangulate | aiProcess_FlipUVs);
if(!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode)
{
cout << "ERROR::ASSIMP::" << import.GetErrorString() << endl;
return;
}
directory = path.substr(0, path.find_last_of('/'));
processNode(scene->mRootNode, scene);
}
我们加载了模型之后,我们会检查场景和其根节点不为null,并且检查了它的一个标记(Flag),来查看返回的数据是不是不完整的。如果遇到了任何错误,我们都会通过导入器的GetErrorString函数来报告错误并返回。我们也获取了文件路径的目录路径。
如果没有发生错误,则处理场景中的所有节点。而首先我们需要处理根节点mRootNode,根节点又(可能)包含多个子节点,处理完根节点后再处理其子节点,所以这是一个递归的结构。在处理节点时我们定义一个递归函数,并使用不同的参数(节点)调用自身,当所有节点被处理完后退出。
节点处理:
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);
}
}
上半部分是对该节点的处理,获取该节点的所有网格,并通过该节点存储的索引,在scene中找到真正的网格数据。然后对返回的网格进行处理,处理完毕后存储到meshes容器中。
下半部分是对该节点的递归,获取该节点的所有子节点,并调用自身函数递归,当不再有任何子节点时,函数执行完毕。
网格处理:
Mesh processMesh(aiMesh* mesh,const aiScene* scene)
{
std::vector<Vertex> vertices;
std::vector<unsigned int> indices;
std::vector<Texture> textures;
for (unsigned int i = 0; i < mesh->mNumVertices;i++)
{
Vertex vertex;
glm::vec3 vector;
vector.x = mesh->mVertices[i].x;
vector.y = mesh->mVertices[i].y;
vector.z = mesh->mVertices[i].z;
vertex.Position = vector;
if (mesh->HasNormals())
{
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;
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);
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]);
}
}
//处理材质
if (mesh->mMaterialIndex >= 0)
{
aiMaterial* material = scene->mMaterials[mesh->mMaterialIndex];
std::vector<Texture> diffuseMap = loadMaterialTexture(material,aiTextureType_DIFFUSE, "texture_diffuse");
std::vector<Texture> reflectionMap = loadMaterialTexture(material, aiTextureType_SPECULAR, "texture_reflection");
//在textures容器末尾处插入diffuseMap容器中的所有元素。
textures.insert(textures.end(), diffuseMap.begin(), diffuseMap.end());
textures.insert(textures.end(), reflectionMap.begin(), reflectionMap.end());
}
return Mesh(vertices, indices, textures);
}
在这一处理过程中,我们会将真实的顶点、法线、纹理数据存储到Vertex类型的容器vertices中。通
过遍历图元,来获取索引缓冲,并存储到容器indices中。在材质处理中先按照类型加载纹理,再按照类型在容器texture中先插入漫反射纹理,再尾插镜面反射纹理。最后将这三个容器返回。
加载纹理函数:
std::vector<Texture> loadMaterialTexture(aiMaterial* mat, aiTextureType type, const std::string& typeName)
{
std::vector<Texture> textures;
for (unsigned int i = 0;i < mat->GetTextureCount(type);i++)
{
aiString textureFilePosition;
mat->GetTexture(type, i, &textureFilePosition);
bool skip = false;
for (unsigned int j = 0;j < textures_loaded.size();j++)
{
if (std::strcmp(textures_loaded[j].path.data(), textureFilePosition.C_Str()) == 0)
{
textures.push_back(textures_loaded[j]);
skip = true;
break;
}
}
if (!skip)
{
Texture texture;
texture.id = TextureFormFile(textureFilePosition.C_Str(), this->directory);
texture.type = typeName;
texture.path = textureFilePosition.C_Str();
textures.push_back(texture);
textures_loaded.push_back(texture);
}
return textures;
}
}
通过GetTextureCount 获取纹理的数量,并传入一个类型。
接下来定义一个纹理所在textureFilePosition,通过GetTexture函数获取纹理并存入,在设置一个开关,当textures_loaded容器中已经存在加载过得纹理路径与需要加载的纹理路径相同时,就不再重新加载,直接使用已加载的纹理。若未加载过,使用另外一个叫做TextureFromFile的工具函数,它将会(用stb_image.h)加载一个纹理并返回该纹理的ID。
利用stb库加载纹理。
这一部分与之前的纹理加载基本一致。
unsigned int TextureFormFile(const char* path,const std::string& directory)
{
std::string fileName = std::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 << "Texture failed to load at path: " << path << std::endl;
stbi_image_free(data);
}
return textureID;
}
效果图:
![](https://i-blog.csdnimg.cn/blog_migrate/f27cee9e1b3420e3c823fe7254cf752b.png)
加入光照
当加入光照和镜面光反射贴图后,效果明显提升。
![](https://i-blog.csdnimg.cn/blog_migrate/5e447ea9be3525e4e641ef518d476df9.png)
代码:
mesh.h:
#include <glad/glad.h>
#include "vendor/glm/glm/glm.hpp"
#include "vendor/glm/glm/gtc/matrix_transform.hpp"
#include "Shader.h"
#include <string>
#include <vector>
struct Vertex
{
glm::vec3 Position;
glm::vec3 Normal;
glm::vec2 TexCoords;
};
struct Texture
{
unsigned int id;
std::string type;
std::string path;
};
class Mesh
{
public:
std::vector<Vertex> vertices;
std::vector<unsigned int> indices;
std::vector<Texture> textures;
unsigned int VAO;
Mesh(std::vector<Vertex> vertices,std::vector<unsigned int> indices,std::vector<Texture> textures)
{
this->vertices = vertices;
this->indices = indices;
this->textures = textures;
setupMesh();
}
void Draw(Shader &shader)
{
unsigned int diffuseNr = 1;
unsigned int reflectionNr = 1;
for (unsigned int i = 0; i < textures.size(); i++)
{
glActiveTexture(GL_TEXTURE0 + i);
std::string name = textures[i].type;
std::string number;
if (name == "texture_diffuse")
{
number = std::to_string(diffuseNr++);
}
else if (name == "texture_reflection")
{
number = std::to_string(reflectionNr++);
}
shader.setInt(("materials." + name + number).c_str(), i);
glBindTexture(GL_TEXTURE_2D, textures[i].id);
}
glActiveTexture(GL_TEXTURE0);
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
}
private:
unsigned int VBO, EBO;
void setupMesh()
{
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glGenBuffers(1, &EBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
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));
glBindVertexArray(0);
}
};
Model.h:
#include <glad/glad.h>
#include "vendor/glm/glm/glm.hpp"
#include "vendor/glm/glm/gtc/matrix_transform.hpp"
#include "vendor/stb_image/stb_image.h"
#include "assimp/Importer.hpp"
#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>
class Model
{
public:
std::vector<Texture> textures_loaded;
std::vector<Mesh> meshs;
std::string directory;
bool gammaCorrection;
Model(std::string const &path ,bool gamma =false) : gammaCorrection(gamma)
{
loadModel(path);
}
void Draw(Shader &shader)
{
for (unsigned int i = 0;i < meshs.size();i++)
meshs[i].Draw(shader);
}
private:
void loadModel(std::string const &path)
{
Assimp::Importer importer;
const aiScene* scene = importer.ReadFile(path, aiProcess_Triangulate | aiProcess_GenSmoothNormals | aiProcess_FlipUVs | aiProcess_CalcTangentSpace);
if (!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode) // if is Not Zero
{
std::cout << "ERROR::ASSIMP:: " << importer.GetErrorString() << std::endl;
return;
}
directory = path.substr(0, path.find_last_of("/"));//逆序查找字符,返回字符位置,并复制为子字符串,也就是路径所在文件夹。
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]];
meshs.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)
{
std::vector<Vertex> vertices;
std::vector<unsigned int> indices;
std::vector<Texture> textures;
for (unsigned int i = 0; i < mesh->mNumVertices;i++)
{
Vertex vertex;
glm::vec3 vector;
vector.x = mesh->mVertices[i].x;
vector.y = mesh->mVertices[i].y;
vector.z = mesh->mVertices[i].z;
vertex.Position = vector;
if (mesh->HasNormals())
{
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;
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);
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]);
}
}
//处理材质
if (mesh->mMaterialIndex >= 0)
{
aiMaterial* material = scene->mMaterials[mesh->mMaterialIndex];
std::vector<Texture> diffuseMap = loadMaterialTexture(material,aiTextureType_DIFFUSE, "texture_diffuse");
std::vector<Texture> reflectionMap = loadMaterialTexture(material, aiTextureType_SPECULAR, "texture_reflection");
//在textures容器末尾处插入diffuseMap容器中的所有元素。
textures.insert(textures.end(), diffuseMap.begin(), diffuseMap.end());
textures.insert(textures.end(), reflectionMap.begin(), reflectionMap.end());
}
return Mesh(vertices, indices, textures);
}
std::vector<Texture> loadMaterialTexture(aiMaterial* mat, aiTextureType type, const std::string& typeName)
{
std::vector<Texture> textures;
for (unsigned int i = 0;i < mat->GetTextureCount(type);i++)
{
aiString textureFilePosition;
mat->GetTexture(type, i, &textureFilePosition);
bool skip = false;
for (unsigned int j = 0;j < textures_loaded.size();j++)
{
if (std::strcmp(textures_loaded[j].path.data(), textureFilePosition.C_Str()) == 0)
{
textures.push_back(textures_loaded[j]);
skip = true;
break;
}
}
if (!skip)
{
Texture texture;
texture.id = TextureFormFile(textureFilePosition.C_Str(), this->directory);
texture.type = typeName;
texture.path = textureFilePosition.C_Str();
textures.push_back(texture);
textures_loaded.push_back(texture);
}
return textures;
}
}
unsigned int TextureFormFile(const char* path,const std::string& directory)
{
std::string fileName = std::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 << "Texture failed to load at path: " << path << std::endl;
stbi_image_free(data);
}
return textureID;
}
};
顶点着色器:
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoords;
out vec3 Normal;
out vec3 FragPos;
out vec2 TexCoords;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
FragPos = vec3(model * vec4(aPos,1.0f));
Normal = aNormal;
gl_Position = projection * view * model * vec4(aPos, 1.0);
TexCoords = aTexCoords;
}
片段着色器:
#version 330 core
out vec4 FragColor;
in vec2 TexCoords;
in vec3 Normal;
in vec3 FragPos;
struct Material
{
sampler2D texture_diffuse1;
sampler2D texture_diffuse2;
//sampler2D texture_diffuse3;
sampler2D texture_reflection1;
sampler2D texture_reflection2;
float shininess;
};
struct DirLight
{
vec3 direction;
vec3 ambient;
vec3 diffuse;
vec3 reflection;
};
uniform vec3 viewPos;
uniform DirLight dirLight;
uniform Material materials;
vec3 CalcDirLight(DirLight light,vec3 normal,vec3 viewDir)
{
vec3 lightDir = normalize(-light.direction);
float diff = max(dot(lightDir,normal),0.0);
vec3 reflectDir = reflect(-lightDir,normal);
float ref = pow(max(dot(viewDir,reflectDir),0.0),materials.shininess);
vec3 ambient = light.ambient * vec3(texture(materials.texture_diffuse1,TexCoords));
ambient += light.ambient * vec3(texture(materials.texture_diffuse2,TexCoords));
vec3 diffuse = light.diffuse * diff * vec3(texture(materials.texture_diffuse1,TexCoords));
diffuse += light.ambient * vec3(texture(materials.texture_diffuse2,TexCoords));
vec3 reflection = light.reflection * ref * vec3(texture(materials.texture_reflection1,TexCoords));
reflection += light.reflection * ref * vec3(texture(materials.texture_reflection2,TexCoords));
return (ambient + diffuse + reflection);
}
void main()
{
vec3 norm = normalize(Normal);
vec3 viewDir = normalize(viewPos - FragPos);
//定向光计算
vec3 result = CalcDirLight(dirLight,norm,viewDir);
FragColor = vec4(result,1.0);
}
application.cpp中的游戏循环
while (!glfwWindowShouldClose(window))
{
// per-frame time logic
float currentFrame = static_cast<float>(glfwGetTime());
deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame;
// input
processInput(window);
// render
// ------
glClearColor(0.1f, 0.1f, 0.1f, 0.1f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// activate shader
ourShader.use();
ourShader.setVec3("viewPos", camera.Position);
ourShader.setFloat("materials.shininess", 32.0f);
// directional light
ourShader.setVec3("dirLight.direction", 0.2f, -1.0f, -0.3f);
ourShader.setVec3("dirLight.ambient", 0.1f, 0.1f, 0.1f);
ourShader.setVec3("dirLight.diffuse", 0.3f, 0.3f, 0.3f);
ourShader.setVec3("dirLight.reflection", 0.5f, 0.5f, 0.5f);
// view/projection transformations
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);
// render the loaded model
glm::mat4 model = glm::mat4(1.0f);
model = glm::translate(model, glm::vec3(0.0f, 0.0f, 0.0f)); // translate it down so it's at the center of the scene
model = glm::scale(model, glm::vec3(1.0f, 1.0f, 1.0f)); // it's a bit too big for our scene, so scale it down
ourShader.setMat4("model", model);
ourModel.Draw(ourShader);
// glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
// -------------------------------------------------------------------------------
glfwSwapBuffers(window);
glfwPollEvents();
}