Assimp库
3d资产的格式种类非常之多,文件的细节与标准也千差万别,我们需要一个专门的库来为我们处理这些资产文件.Assimp库就是这样一个库,它可以读取各种3d资产文件,以便我们加载3d模型.
你可以点击这里访问Assimp库的GitHub页面,下载最新版本的Assimp库.
由于Assimp库实际上较为庞大,考虑到编译Assimp库需要的时间较长,我们选用Assimp库的预编译版本.你可以点击这里下载
我们将库安装在我们的3rd目录下.然后更改我们的CMakeLists.txt文件,添加以下内容:
include_directories(${CMAKE_SOURCE_DIR}/3rd/assimp/include) link_libraries(${CMAKE_SOURCE_DIR}/3rd/assimp/lib/x64)
|
别忘了在Source中的CMakeLists.txt中链接Assimp库:
target_link_libraries(BickRenderer ${CMAKE_SOURCE_DIR}/3rd/assimp/lib/x64)
|
我们inclue assimp并调用一个函数后进行编译.Bingo!我们已经成功地将Assimp库添加到我们的项目中了.
如果无法链接成功,可能是编译器不同的原因.预编译的Assimp库是使用MSVC编译的,如果你的编译器不是MSVC而是MinGW或其他,则需要自行编译Assimp库.你可以查阅官方文档
以下是一些建议:
- BUILD_SHARED_LIBS = OFF
- ASSIMP_BUILD_TESTS = OFF
- ASSIMP_WARNINGS_AS_ERRORS = OFF
- ASSIMP_BUILD_SAMPLES = OFF
- ASSIMP_BUILD_ASSIMP_TOOLS = OFF
- ASSIMP_INSTALL = ON
Mesh类
3d模型的核心是Mesh类,它包含了3d模型的顶点、法线、纹理坐标、索引等信息.我们需要将Assimp库的输出转换为Mesh类.
首先我们定义一个Vertex
struct Vertex { glm::vec3 position; glm::vec3 normal; glm::vec2 uv; };
|
我们定义一个Texture
enum class TextureType { }; struct Texture { std::string name; unsigned int glID; TextureType type; };
|
定义我们的Mesh
class Mesh{ public: std::vector<Vertex> *vertices; std::vector<unsigned int> *indices; std::vector<Texture> *textures;
private: unsigned int VAO, VBO, EBO;
public: Mesh(std::vector<Vertex> vertices, std::vector<unsigned int> indices, std::vector<Texture> textures); ~Mesh(){};
void InitializeMesh(); void DrawMesh(Shader &shader); };
|
Model类
一个模型中会含有多个Mesh,我们需要一个model来管理这些Mesh.
定义我们的Model
#pragma once
#include <vector> #include "assimp/Importer.hpp" #include "assimp/scene.h" #include "assimp/postprocess.h" #include "Object/Object.hpp" #include "Mesh/Mesh.hpp"
class Model : public Object { public: std::vector<Mesh> meshes; std::string directory;
public: Model(std::string const path,MatFactory *matFactory,Transform transform); ~Model();
public: void Draw();
private: void LoadModel(std::string const path,MatFactory *matFactory); void ProcessNode(aiNode *node, const aiScene *scene,MatFactory *matFactory); Mesh ProcessMesh(aiMesh *mesh, const aiScene *scene,MatFactory *matFactory); };
|
#include "Mesh/Model.hpp" #include "Material/MatFactory.hpp"
Model::Model(std::string const path,MatFactory *matFactory,Transform transform) :Object(transform) { std::cout <<"开始加载模型..."<<"路径:"<<path << std::endl; LoadModel(path, matFactory); std::cout <<"模型加载完成!"<<"路径:"<<path << std::endl; }
Model::~Model(){}
void Model::Draw() { for(auto &mesh : meshes) mesh.DrawMesh(transform); }
void Model::LoadModel(std::string const path,MatFactory *matFactory) { Assimp::Importer importer; const aiScene *scene = importer.ReadFile(path, aiProcess_Triangulate | aiProcess_FlipUVs | aiProcess_GenNormals);
if(!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE ||!scene->mRootNode) { std::cout << "ERROR::ASSIMP::"<<"草了,模型加载失败!\n错误:" << importer.GetErrorString() << std::endl<<std::endl; return; } directory = path.substr(0, path.find_last_of('/'));
ProcessNode(scene->mRootNode, scene, matFactory); }
void Model::ProcessNode(aiNode* node, const aiScene* scene, MatFactory *matFactory) { for(unsigned int i = 0; i < node->mNumMeshes; i++) { aiMesh *mesh = scene->mMeshes[node->mMeshes[i]]; meshes.push_back(ProcessMesh(mesh, scene, matFactory)); } for(unsigned int i = 0; i < node->mNumChildren; i++) { ProcessNode(node->mChildren[i], scene, matFactory); } }
Mesh Model::ProcessMesh(aiMesh* mesh, const aiScene* scene, MatFactory *matFactory) { std::shared_ptr<std::vector<Vertex>> vertices = std::make_shared<std::vector<Vertex>>(); std::shared_ptr<std::vector<unsigned int>> indices = std::make_shared<std::vector<unsigned int>>();
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;
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.uv = vec; } else { vertex.uv = 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]; auto mat = std::shared_ptr<Material>(matFactory->CreateMaterial(material, directory)); return Mesh(vertices, indices, mat); } else { std::cout << "ERROR::ASSIMP::"<<"麻了,没有材质!" << std::endl; return Mesh( vertices, indices, std::shared_ptr<Material>(matFactory->CreateMaterial(nullptr, directory)) ); }
}
|
Material类
模型的材质是模型的外观,我们需要一个Material类来管理材质以及对应的shader.
定义我们的Material类
#pragma once
#include <memory> #include "glm/glm.hpp" #include "glm/ext/matrix_transform.hpp" #include <glm/gtc/type_ptr.hpp> #include "Shader/Shader.hpp" #include "Object/Object.hpp" #include "Camera/Camera.hpp"
class Material { public: std::shared_ptr<Shader> shader;
public: Material(std::shared_ptr<Shader> shader) : shader(shader) {}
public: virtual void Use(Transform myTransform) = 0; };
|
游戏世界中的材质千变万化,种类繁杂.但也有那些最为通用的材质,比如游戏引擎给我们提供的默认材质.我们也实现一个我们的默认PBR材质.
#pragma once
#include "Material/Material.hpp" #include "Texture/Texture.hpp"
class Mat_StandardPBM_MetallicWorkFlow : public Material { public: static const std::string defaultAlbedoPath; static const std::string defaultMetallicPath; static const std::string defaultRoughnessPath; static const std::string defaultNormalPath; static const std::string defaultHeightPath; static const std::string defaultEmissionPath; static const std::string defaultAOPath;
public: std::shared_ptr<Texture> albedo ; std::shared_ptr<Texture> metallic ; std::shared_ptr<Texture> roughness; std::shared_ptr<Texture> normalMap; std::shared_ptr<Texture> heightMap; std::shared_ptr<Texture> emission ; std::shared_ptr<Texture> ao ;
public: Mat_StandardPBM_MetallicWorkFlow(std::shared_ptr<Shader> shader) : Material(shader) { albedo = std::make_shared<Texture>(Texture(defaultAlbedoPath, GL_TEXTURE0)); metallic = std::make_shared<Texture>(Texture(defaultMetallicPath, GL_TEXTURE1)); roughness = std::make_shared<Texture>(Texture(defaultRoughnessPath, GL_TEXTURE2)); normalMap = std::make_shared<Texture>(Texture(defaultNormalPath, GL_TEXTURE3)); heightMap = std::make_shared<Texture>(Texture(defaultHeightPath, GL_TEXTURE4)); emission = std::make_shared<Texture>(Texture(defaultEmissionPath, GL_TEXTURE5)); ao = std::make_shared<Texture>(Texture(defaultAOPath, GL_TEXTURE6)); } Mat_StandardPBM_MetallicWorkFlow(std::shared_ptr<Texture> albedo, std::shared_ptr<Texture> metallic, std::shared_ptr<Texture> roughness, std::shared_ptr<Texture> normalMap, std::shared_ptr<Texture> heightMap, std::shared_ptr<Texture> emission, std::shared_ptr<Texture> ao, std::shared_ptr<Shader> shader)
: Material(shader), albedo(albedo), metallic(metallic), roughness(roughness), normalMap(normalMap), heightMap(heightMap), emission(emission), ao(ao) { }
~Mat_StandardPBM_MetallicWorkFlow() {}
public: void Use(Transform myTransform) override;
};
|
材质种类数量过于庞大,不同材质构造方法又千差万别,为了解决这个问题我们使用工厂模式.
定义一个MatFactory类
#pragma once
#include <memory> #include "Material/Material.hpp" #include "Material/Mat_StandardPBM_MetallicWorkFlow.hpp" #include "assimp/material.h"
class MatFactory { public: std::shared_ptr<Shader> shader; public: MatFactory() {} virtual ~MatFactory() {}
public: virtual Material *CreateMaterial(aiMaterial* mat, std::string resRootPath) = 0; };
|
并添加标准材质的工厂
class MatFactory_StandardPBM_MetallicWorkFlow : public MatFactory { public: std::string stdVertexShaderPath = "Source/GLSL_Shaders/Standard/Standard_VS_RM.glsl"; std::string stdGeometryShaderPath = "Source/GLSL_Shaders/Standard/Standard_GS_RM.glsl"; std::string stdFragmentShaderPath = "Source/GLSL_Shaders/Standard/Standard_FS_RM.glsl";
MatFactory_StandardPBM_MetallicWorkFlow(std::string vsPath, std::string gsPath, std::string fsPath) : stdVertexShaderPath(vsPath), stdGeometryShaderPath(gsPath), stdFragmentShaderPath(fsPath) { std::string name = vsPath.substr(vsPath.find_last_of("/") + 1); shader = std::make_shared<Shader>(stdVertexShaderPath, stdGeometryShaderPath, stdFragmentShaderPath, name); }
MatFactory_StandardPBM_MetallicWorkFlow(std::string vsPath, std::string fsPath) : stdVertexShaderPath(vsPath), stdFragmentShaderPath(fsPath) { std::string name = vsPath.substr(vsPath.find_last_of("/") + 1); shader = std::make_shared<Shader>(stdVertexShaderPath, "", stdFragmentShaderPath, name); }
MatFactory_StandardPBM_MetallicWorkFlow() { std::string name = stdVertexShaderPath.substr(stdVertexShaderPath.find_last_of("/") + 1); shader = std::make_shared<Shader>(stdVertexShaderPath, stdFragmentShaderPath, name); }
~MatFactory_StandardPBM_MetallicWorkFlow() {}
public: Material *CreateMaterial(aiMaterial* mat, std::string resRootPath) override; };
|
默认Shader
接下来我们需要写我们的默认shader.
vs:
#version 330 core
layout(location = 0) in vec3 aPos; layout(location = 1) in vec3 aNormal; layout(location = 2) in vec2 aTexCoord;
uniform mat4 model; uniform mat4 view; uniform mat4 projection;
uniform sampler2D normalMap;
out vec2 uv; out vec3 normal; out vec4 gl_Position;
void main() { gl_Position = projection * view * model * vec4(aPos, 1.0); uv = aTexCoord;
normal = texture(normalMap, aTexCoord).rgb * 2.0 - 1.0; }
|
fs:
#version 330 core
in vec2 uv; in vec3 normal;
uniform sampler2D albedo; uniform sampler2D metallic; uniform sampler2D roughness; uniform sampler2D emission; uniform sampler2D ao;
out vec4 FragColor;
void main () { vec4 color = texture(albedo, uv); vec3 metallic_value = texture(metallic, uv).rgb; float roughness_value = texture(roughness, uv).r; vec3 emission_value = texture(emission, uv).rgb; float ao_value = texture(ao, uv).r; vec3 N = normalize(normal); vec3 V = normalize(-vec3(0,0,1)); vec3 L = normalize(vec3(1,1,1)); vec3 H = normalize(L + V); float NdotL = max(dot(N, L), 0.0); float NdotH = max(dot(N, H), 0.0); float VdotH = max(dot(V, H), 0.0); vec3 F0 = vec3(0.04); F0 = mix(F0, color.rgb, metallic_value.r); float alpha = roughness_value * roughness_value; vec3 F = F0 + (1.0 - F0) * pow(1.0 - VdotH, 5.0); vec3 kS = F; vec3 kD = 1.0 - kS; kD *= 1.0 - metallic_value.r; vec3 diffuse = color.rgb * (kD * ao_value + emission_value); vec3 specular = color.rgb * (kS * pow(NdotH, 5.0) * ao_value); vec3 color_final = diffuse + specular; FragColor = vec4(color_final, 1.0); }
|
这样我们就得到了结果,Bingo!箱子拜拜!!

但是,效果好像不尽如人意.是因为我们没有光,在下一节中,我们为我们的渲染器加上光照支持.