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

/*
* @Author: Vanish
* @Date: 2024-09-19 21:30:00
* @LastEditTime: 2024-09-20 15:06:45
* Also View: http://vanishing.cc
* Copyright@ https://creativecommons.org/licenses/by/4.0/deed.zh-hans
*/
#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);
};
/*
* @Author: Vanish
* @Date: 2024-09-19 21:30:14
* @LastEditTime: 2024-10-11 14:48:09
* Also View: http://vanishing.cc
* Copyright@ https://creativecommons.org/licenses/by/4.0/deed.zh-hans
*/
#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类

/*
* @Author: Vanish
* @Date: 2024-09-20 10:50:34
* @LastEditTime: 2024-09-23 18:59:46
* Also View: http://vanishing.cc
* Copyright@ https://creativecommons.org/licenses/by/4.0/deed.zh-hans
*/
#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材质.

/*
* @Author: Vanish
* @Date: 2024-09-20 10:52:37
* @LastEditTime: 2024-09-23 18:57:39
* Also View: http://vanishing.cc
* Copyright@ https://creativecommons.org/licenses/by/4.0/deed.zh-hans
*/
#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; // 将法线从[0, 1]范围映射到[-1, 1]
}

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!箱子拜拜!!
模型

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