OpenGL-03 加载模型

一、Assimp

  • Assimp可以将模型导成如下的格式:
  • 首先,该模型被打包成一个scene对象,是aiScene类型的,一般是导出为指向常量的指针。在scene中保存着三个对象:
    • 第一个是一个aiNode类型的指针,即根节点scene->mRootNode。
    • 第二个是一个aiMesh*类型的数组,即scene->mMeshes。保存着所有mesh的信息。
    • 第三个是一个aiMaterial*类型的数组,即scene->mMaterials。aiMaterial保存着一套纹理信息。
  • 其次,在每一个aiNode中,有两种对象
    • 第一个是mMeshes数组,里面保存着aiMesh中的索引;
    • 第二个是mChildren数组,里面保存着aiNode类型的指针,即其他子节点。
  • 此外,在每一个aiMesh中,保存着五种对象:
    • 第一个是mVerticles数组,里面保存着一系列的顶点位置,其数据类型是Assimp自定义的三维矢量,所以需要逐个元素的转移
    • 第二个是mNormals数组,里面保存着一系列的法向量,其位置与顶点是对应的,其数据类型是Assimp自定义的三维矢量,所以需要逐个元素的转移
    • 第三个是mTextureCoords数组,这是一个二维数组,第一个维度表示不同的纹理,第二个维度表示该纹理体系下每一个顶点的纹理坐标,其位置与顶点是对应的,其数据类型是Assimp自定义的二维矢量,所以需要逐个元素的转移;需要注意的是,specular与diffuse贴图是共用纹理坐标的,他们也是属于同一个material。
    • 第四个是mFaces数组,这是一个aiFace类的数组,aiFace类是一个图形类,代表三角形等基本的绘制图形,每一个aiFace对象face都有一个mIndices对象,这是一个对该aiMesh的顶点位置索引数组,表示这图形需要使用这些顶点来绘制,并且按照这个顺序来绘制。
    • 最后一个是mMaterialIndex,这是一个对scene->mMaterials的索引,一个mesh只有一个索引。scene->mMaterials的每一个元素是一个aiMaterial类的对象,aiMaterial类定义了一系列的函数,如GetTextureCount(aiTextureType type)可以返回diffuse或者specular纹理的数量,GetTexture(aiTextureType
      type, unsigned int i, aiString &str)可以返回该类型纹理中第i个纹理的地址,这个地址可能是相对路径也可能是绝对路径,取决于资源自身,基于这两个函数就可以按照之前的的流程定义纹理了。

二、网格

  • 回顾之前的内容可知,绘制在模型部分需要用到VAO,VBO,EBO,Texture,其中VBO需要一个顶点数组,包括顶点位置、法线、纹理坐标,EBO需要一个索引数组,最终表示成unsigned int的ID就可以使用,而纹理同样最终表示成ID,我们只要拿到id就可以使用了,至于纹理本身不需要记录的。我们将mesh作为一个基本的绘制单元,它包含自己的定点信息和纹理等信息,正如我们之前绘制的立方体一样。因此,我们期待将mesh作为一个类,并将绘制流程中所需要的操作和信息集成进去。
  • 除了shader相关的部分,都可以被集成:
  • 首先定义一个Vertex类,可以看出这个类包含着一个顶点的三种信息
struct Vertex {
	glm::vec3 Position;
	glm::vec3 Normal;
	glm::vec3 TexCoords;
};
  • 然后,在Mesh类中定义一个vector成员,是Vertex的容器,这样做的好处在于struct的成员是连续排布的,那么Vertex的vector就是一系列定点信息的连续排布,这是不是跟我们之前定义的float vertices[]有很好的对应了呢。但是,有一个差异在于,float[]的名字是指向数组首地址的指针,可以直接用到glBufferData中,但是vector的名字不是指针,所以需要索引处第一个元素并取址。这样,我们就定义好了第一个CPU数据vertices。
std::vector<Vertex> vertices;
  • 然后,在Mesh类中定义indices,这样我们就定义好了第二个CPU数据indices。
vector<unsigned int> indices;
  • 基于这两个数据,我们就可以生成VAO,VBO,EBO了,因此我们再定义这三个成员。
unsigned int VAO, VBO, EBO;
  • 此外,我们在定义一个纹理类Texture。其中除了最终的纹理id之外,我们还需要记录纹理类型(方便uniform操作),以及纹理对应图像的地址(避免重复定义纹理类对象,减少计算量)
struct Tecture {
	unsigned int id;
	string type;
	string path;
};
  • 并在Mesh中定义vector,来记录所有的纹理;注意,此处的纹理是最终结果,在Mesh中不进行纹理操作。
vector<Texture> textures;
  • 到目前为止,我们定义了纹理,定义了缓冲和其CPU数据,因为没有在这里定义纹理操作,所以还差缓冲操作,就可以完成前期的准备了;我们把它放在构造函数中。可以看到,除了三个数组的初始化之外,还在setupMesh函数中进行了缓冲操作,并定义了VAO VBO EBO。一个有趣的现象是vector的字节数采取了vector长度乘以元素size的方法,另外正如前面所述使用vector首元素的地址作为首地址,另外一个更为有趣的方法是使用offsetof来获得结构体中属性的偏置来作为顶点指针函数的偏置。
Mesh::Mesh(vector<Vertex> _vertices, vector<unsigned int> _indices, vector<Texture> _textures)
{
	vertices = _vertices;
	indices = _indices;
	textures = _textures;

	setupMesh();
}
void Mesh::setupMesh()
{
	glGenBuffers(1, &VBO);
	glGenBuffers(1, &EBO);
	glGenVertexArrays(1, &VAO);

	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);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, vertices.size() * sizeof(Vertex), (void*)0);
	glEnableVertexAttribArray(0);
	glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, vertices.size() * sizeof(Vertex), (void*)(offsetof(Vertex,Normal)));
	glEnableVertexAttribArray(1);
	glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, vertices.size() * sizeof(Vertex), (void*)(offsetof(Vertex, TexCoords)));
	glEnableVertexAttribArray(2);

	glBindVertexArray(0);
}
  • 到此为止,我们获得了绘制需要的东西VAO和纹理,前者表明缓冲操作完成了,但是后者还不完善,因为在绘制时需要将纹理传输进shader中,因此,我们再次定义一个绘制函数Draw,只需要一个Shader类参数就可以完成绘制了。当然,其他的shaderuniform操作不属于Mesh的任务,在Draw之外完成就可以了,但是这也是到目前的内容为止,仅剩的需要补充的内容了。
  • 在此之前,有一个问题需要解决,那就是我们已经在其他某个地方定义了纹理获得了id并激活了对应的位置,这个结果对应的id保存在了我们的vector中,但是我们还需要将纹理在shader中对应的sampler2D进行glUniformi的操作,但是如何保证uniform的结果即shadr中的某个纹理(在程序中发挥着某种着色作用)被正确的绑定到了某个id上呢。按照之前的shader,我们对纹理的区分只有diffuse和specular,那么在这个基础上,我们只要能够区分这两个就可以达到效果了。这也正是为什么在Texture类中加入了type的原因。这样一来,就可以按照textures中的纹理顺序将对应类型的sampler2D有序的注册了,但是这并不能保证对齐,除非textures中的纹理也是按照这个顺序激活的,事实上正是如此,我们将激活与unform赋值同时进行。同时可以看出,在纹理方面我们只剩下了定义以及加载绑定贴图这一部分了。
  • 虽然我们可以按照Texture类的type属性来二分纹理的类型,但是距离glUniformi着色器中的texture还差一步,那便是Mesh作为一个被封装的类,其Draw函数是被写死的,那么其中的glUniformi操作是固定的,这意味着我们能访问的uniform变量必须具有有迹可循的名字,所以需要确定命名规范。这个名字只需要包含三个要素就可以了:texture表明是一个纹理,duffuse或者specular表明type,另外考虑到每种类型的纹理可能不止一个再加一个编号,获得如texture_diffuse1的名字。这样就可以简单的使用是std::string来拼接三个字符串获得名字了。同样的,可以将这一系列变量都封装在Material类中。我们可以多定义几个保证冗余,不使用也没问题,但是这会占用uniform的位置,因为uniform位置是有限的。
struct Material {
	//vec3 ambient;
	sampler2D texture_diffuse1;
	sampler2D texture_diffuse2;
	sampler2D texture_diffuse3;
	sampler2D texture_specular1;
	sampler2D texture_specular2;
	sampler2D texture_specular3;
	float shininess;
};
uniform Material material;
void Mesh::Draw(Shader shader) const
{
	shader.use();

	unsigned int diffuseNr = 1;
	unsigned int specularNr = 1;

	for (int i = 0; i != textures.size(); i++)
	{
		glActiveTexture(GL_TEXTURE0 + i);
		glBindTexture(GL_TEXTURE_2D, textures[i].id);
		string number;
		string name = textures[i].type;
		if (name == "diffuse_texture")
		{
			number = std::to_string(diffuseNr++);
		}
		else if (name == "specular_texture")
		{
			number = std::to_string(specularNr++);
		}
		shader.setInt(("material."+name+number).c_str(), i);
	}

	glBindVertexArray(VAO);
	glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, 0);
	glBindVertexArray(0);
}
  • 到目前为止,我们定义了Mesh类,这个类有两个主要工作,第一个是接受Vertex类的vector和unsigned int的vector来完成VAO,VBO,EBO的定义,这一部分属于绘制前的准备工作,所以放在构造函数中完成,事实上封装到了setupMesh中,另一个是通过接受一个已经绑定了贴图的Texture类数组(包含id),来完成对当前上下文中shader中纹理位置的激活,并实现对应sampler2D对象的位置赋值,这一部分属于绘制过程中每次都需要检查甚至有必要更改的,因此将其封装在Draw函数中,并配合VAO来实现绘制。因此,我们剩下的工作就是如何从模型文件中将三个vector容器提取出来,并实现一系列的Mesh类,有了这个之后就可以以Mesh为基本对象进行绘制了。
#ifndef MESH_H
#define MESH_H

#include <glad/glad.h> 

#include<vector>
#include<string>
using std::vector;
using std::string;

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include "shader.h"

struct Vertex {
	glm::vec3 Position;
	glm::vec3 Normal;
	glm::vec2 TexCoords;
};

struct Texture {
	unsigned int id;
	string type;
	string path;
};

class Mesh {
public:
	vector<Vertex> vertices;
	vector<unsigned int> indices;
	vector<Texture> textures;

	Mesh(vector<Vertex> _vertices, vector<unsigned int> _indices, vector<Texture> _textures);
	void Draw(Shader shader) const;
private:
	unsigned int VAO, VBO, EBO;
	void setupMesh();
};

Mesh::Mesh(vector<Vertex> _vertices, vector<unsigned int> _indices, vector<Texture> _textures)
{
	vertices = _vertices;
	indices = _indices;
	textures = _textures;

	setupMesh();
}

void Mesh::setupMesh()
{
	glGenBuffers(1, &VBO);
	glGenBuffers(1, &EBO);
	glGenVertexArrays(1, &VAO);

	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);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, vertices.size() * sizeof(Vertex), (void*)0);
	glEnableVertexAttribArray(0);
	glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, vertices.size() * sizeof(Vertex), (void*)(offsetof(Vertex,Normal)));
	glEnableVertexAttribArray(1);
	glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, vertices.size() * sizeof(Vertex), (void*)(offsetof(Vertex, TexCoords)));
	glEnableVertexAttribArray(2);

	glBindVertexArray(0);
}

void Mesh::Draw(Shader shader) const
{
	shader.use();

	unsigned int diffuseNr = 1;
	unsigned int specularNr = 1;

	for (int i = 0; i != textures.size(); i++)
	{
		glActiveTexture(GL_TEXTURE0 + i);
		glBindTexture(GL_TEXTURE_2D, textures[i].id);
		string number;
		string name = textures[i].type;
		if (name == "diffuse_texture")
		{
			number = std::to_string(diffuseNr++);
		}
		else if (name == "specular_texture")
		{
			number = std::to_string(specularNr++);
		}
		shader.setInt(("material."+name+number).c_str(), i);
	}

	glBindVertexArray(VAO);
	glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, 0);
	glBindVertexArray(0);
}

#endif

三、模型

  • 我们加载模型的目的是为了获得一系列的mesh,因此首先在Model类中定义vector meshes成员。
vector<Mesh> meshes;
  • 有了mesh就可以对其绘制,因此直接循环绘制就可以了。当然,这种封装方式使得,整个模型的所有mesh只能使用同一个shader,我不知道这样是否合理,但是目前没有问题。
void Model::Draw(Shader shader) const
{
	for (int i = 0; i != meshes.size(); i++)
		meshes[i].Draw(shader);
}
  • 为了获得这些mesh,我们首先需要加载出aiScene。这个可以使用assimp::Import类来实现。该类有一个成员函数ReadFile,只需要给他所有模型素材的文件夹位置就可以将其整理成aiScene对象,这是他的第一个参数,要求是C风格字符串或者string,事实上在后面的操作中可以看到,assimp在内部定义了自己的字符串类型aiString,为了使用它的字符串我们又需要使用它定义的函数C_Str()来转化成char,所以在实现我们自己的中间操作时我们会使用char而非string。他的第二个参数是一系列的对模型的后处理操作,包括:
    • aiProcess_Triangulate:将所有图元转化为三角形
    • aiProcess_FlipUVs:翻转纹理的y轴(我们使用stbimage时也做了翻转)
    • aiProcess_GenNormals:如果没有顶点法线,那么就生成法线
    • aiProcess_SplitLargeMeshes:细分网格
    • aiProcess_OptimizeMeshes:拼接网格
  • 因此,一次模型处理只需要表示成
assimp::Importer importer;
const aiScene* scene = importer.ReadFile(path, aiProcess_Triangulate|aiProcess_FlipUVs);
  • 在完成模型读取之后,我们可以做一些检查:
    • 检查scene是否是空指针,即没有获得aiScene
    • 检查scene->mRootNode是否是空指针,即不存在节点,就无法获得mesh信息了
    • 检查数据的完整性
if(!scene || scene->mFlags & AI_SCENE_FLAGES_IMCOMPLETE || !scene->mRootNode)
{
  std::cout<<"ERROR::ASSIMP::"<<import.GetErrorString()<<endl;
}
  • 之前提起,我们的Mesh类的主要目的是整理绘制所需要的信息以及绘制操作,而Model类的主要目的是获得一系列的mesh以及对他们做绘制。之前我们已经实现了绘制的成员函数,只剩下一个初始化meshes对象的部分了。因此,我们可以使用构造函数来实现这一部分的初始化。首先,我们实现模型加载的部分,由前面可知我们只需要一个文件夹路径参数就可以获得scene了,除此之外我们希望能够保存这个路径,这样方面后续获取纹理时不必重复读取路径(假设纹理的路径是相对路径),因此,将路径作为一个成员。此外,在获得scene之后,通过根节点以递归的方式来读取所有节点,并获得meshes,因此还需要定义一个processNode的递归函数。将这三个部分放在loadModel函数中,被调用于构造函数中。
Model::Model(string path)
{
	loadModel(path);
}

void Model::loadModel(string path)
{
	Assimp::Importer importer;
	const aiScene* scene = importer.ReadFile(path, aiProcess_Triangulate | aiProcess_FlipUVs);

	if (!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode)
	{
		std::cout << "ERROR::ASSIMP::" << importer.GetErrorString() << std::endl;
		return;
	}

	directory = path.substr(0, path.find_last_of('/'));

	processNode(scene->mRootNode, scene);
}
  • processNode函数的实现也很简单,首先遍历node中的所有mesh,对他进行处理(processMesh)并push到meshes中,然后递归他的所有子节点就可以了。
void Model::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处理函数来了,在这个函数中我们将会利用资源创建一个Mesh类。回顾可知,创建Mesh类的对象,需要拿到一个顶点容器,一个索引容器,一个纹理类容器。
  • 其中顶点坐标保存在mesh->mVertices数组中,顶点法线保存在mseh->mNormals数组中,顶点纹理坐标保存在mesh->mTexCoords数组中(二维数组),将他们在一个循环中逐个访问并组成Vertex类,然后push进vector中就可以了。
  • 而索引保存在mesh->mFaces数组中,每一个aiFace都是一个类,可以通过aiFace.mIndices数组来获得顶点编号,因此,外循环遍历mesh的所有aiFace,内循环遍历aiFace的mIndices,将他们push进indices中就可以了。
  • 最后,纹理可以通过mesh->mMaterialIndex索引scene->mMaterials数组来获得aiMaterial对象的指针。aiMaterial可以通过GetTexture来获得某种类型(aiTextureType,包括aiTextureType_DIFFUSE与aiTextureType_SPECULAR两种)的第i个纹理的路径(这里假设是相对路径,相对于文件夹),因此配合文件夹路径与纹理路径就可以实现纹理注册了,将id返回保存在Texture类中,同时保存其他参数。对aiMaterial对两种类型的纹理分别注册,并保存在vector中,然后insert到textures中,便获得了Mesh的纹理参数。同时,要注意的是,纹理注册是一项开销较大的过程,因为不同mesh可能会共用一部分纹理,因此将纹理保存在Model的一个vector中,使用纹理地址来区分不同纹理,如果已经注册过则直接从中取出并push进textures中,避免重复注册。
  • 到目前为止,就获得了Mesh。
Mesh Model::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;
		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.TexCoords = vec;
		}
		else
		{
			vertex.TexCoords = glm::vec2(0.0, 0.0);
		}
		
		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];
		vector<Texture> diffuseMaps = loadMaterialTextures(material, aiTextureType_DIFFUSE, "texture_diffuse");
		textures.insert(textures.end(), diffuseMaps.begin(), diffuseMaps.end());
		vector<Texture> specularMaps = loadMaterialTextures(material, aiTextureType_SPECULAR, "texture_specular");
		textures.insert(textures.end(), specularMaps.begin(), specularMaps.end());
	}

	return Mesh(vertices,indices,textures);
}

vector<Texture> Model::loadMaterialTextures(aiMaterial* material, aiTextureType type, string typeName)
{
	vector<Texture> textures;
	for (unsigned int i = 0; i != material->GetTextureCount(type); i++)
	{
		aiString str;
		material->GetTexture(type, i, &str);
		bool skip = false;

		for (unsigned int j = 0; j != textures_loaded.size(); j++)
		{
			if (strcmp(textures_loaded[j].path.data(), str.C_Str()))
			{
				textures.push_back(textures_loaded[j]);
				skip = true;
				break;
			}
		}
		if (skip)
		{
			Texture texture;
			texture.id = TextureFromFile(str.C_Str(), directory);
			texture.path = str.C_Str();
			texture.type = typeName;
			textures.push_back(texture);
			textures_loaded.push_back(texture);
		}
	}
	return textures;
}

unsigned int Model::TextureFromFile(string pat, string dir)
{
	unsigned int texture;
	glGenTextures(1, &texture);
	glBindTexture(GL_TEXTURE_2D, texture);

	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_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);

	//stbi_set_flip_vertically_on_load(true);
	int width, height, nrChannels;
	unsigned char* data = stbi_load((dir+"/"+pat).c_str(), &width, &height, &nrChannels, 0);
	if (data)
	{
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
		glGenerateMipmap(GL_TEXTURE_2D);
	}
	else
	{
		std::cout << "Failed to load texture1." << std::endl;
	}
	stbi_image_free(data);
	return texture;
}
  • 最终,我们获得了Model类:
#ifndef MODEL_H
#define MODEL_H

#include <string>
#include <vector>
using std::string;
using std::vector;

#include "image.h"

#include <assimp/Importer.hpp>
#include <assimp/scene.h>
#include <assimp/postprocess.h>

#include "mesh.h"

class Model {
public:
   Model(string path);
   void Draw(Shader shader) const;
   void test()const
   {
   	for (int i = 0; i != meshes.size(); i++)
   	{
   		std::cout << meshes[i].textures[0].id <<" "<< meshes[i].textures[0].path<<" "<< meshes[i].textures[0].type << " ";
   		if (meshes[i].textures.size() > 1)
   		{
   			std::cout << meshes[i].textures[1].id <<" "<< meshes[i].textures[1].path<<" " << meshes[i].textures[1].type<< std::endl;
   		}
   		else
   			std::cout << std::endl;
   	}
   }
private:
   vector<Mesh> meshes;
   string directory;
   vector<Texture> textures_loaded;
   void loadModel(string path);
   void processNode(aiNode* node, const aiScene* scene);
   Mesh processMesh(aiMesh* mesh, const aiScene* scene);
   vector<Texture> loadMaterialTextures(aiMaterial* material, aiTextureType type, string name);
   unsigned int TextureFromFile(string pat, string dir);
};

Model::Model(string path)
{
   loadModel(path);
}

void Model::Draw(Shader shader) const
{
   for (unsigned int i = 0; i != meshes.size(); i++)
   	meshes[i].Draw(shader);
}

void Model::loadModel(string path)
{
   Assimp::Importer importer;
   const aiScene* scene = importer.ReadFile(path, aiProcess_Triangulate | aiProcess_GenSmoothNormals | aiProcess_FlipUVs);

   if (!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode)
   {
   	std::cout << "ERROR::ASSIMP::" << importer.GetErrorString() << std::endl;
   	return;
   }

   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* 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 Model::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;
   	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.TexCoords = vec;
   	}
   	else
   	{
   		vertex.TexCoords = glm::vec2(0.0, 0.0);
   	}
   	
   	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];
   	vector<Texture> diffuseMaps = loadMaterialTextures(material, aiTextureType_DIFFUSE, "texture_diffuse");
   	textures.insert(textures.end(), diffuseMaps.begin(), diffuseMaps.end());
   	vector<Texture> specularMaps = loadMaterialTextures(material, aiTextureType_SPECULAR, "texture_specular");
   	textures.insert(textures.end(), specularMaps.begin(), specularMaps.end());
   }

   return Mesh(vertices,indices,textures);
}

vector<Texture> Model::loadMaterialTextures(aiMaterial* material, aiTextureType type, string typeName)
{
   vector<Texture> textures;
   for (unsigned int i = 0; i != material->GetTextureCount(type); i++)
   {
   	aiString str;
   	material->GetTexture(type, i, &str);
   	bool skip = false;

   	for (unsigned int j = 0; j != textures_loaded.size(); j++)
   	{
   		if (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(), directory);
   		texture.path = str.C_Str();
   		texture.type = typeName;
   		textures.push_back(texture);
   		textures_loaded.push_back(texture);
   	}
   }
   return textures;
}

unsigned int Model::TextureFromFile(string pat, string dir)
{
   unsigned int texture;
   glGenTextures(1, &texture);
   glBindTexture(GL_TEXTURE_2D, texture);

   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_MAG_FILTER, GL_LINEAR);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);

   //stbi_set_flip_vertically_on_load(true);
   int width, height, nrChannels;
   unsigned char* data = stbi_load((dir+'/' + pat).c_str(), &width, &height, &nrChannels, 0);
   if (data)
   {
   	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, data);
   	glGenerateMipmap(GL_TEXTURE_2D);
   }
   else
   {
   	std::cout << "Failed to load texture." << std::endl;
   }
   stbi_image_free(data);
   return texture;
}

#endif

  • 画个图
Model ourModel("res/model/nanosuit/nanosuit.obj");
\\渲染
		modelShader.use();

		glm::mat4 model;
		model = glm::translate(model, glm::vec3(0.0,0.0,0.0));
		modelShader.setMat4("model", glm::value_ptr(model));
		glm::mat4 normModel;
		normModel = glm::transpose(glm::inverse(model));
		modelShader.setMat4("normModel", glm::value_ptr(normModel));

		modelShader.setMat4("view", glm::value_ptr(view));
		modelShader.setMat4("projection", glm::value_ptr(projection));

		modelShader.setFloat("material.shininess", 32.0f);
		modelShader.setVec3("viewPos", camera.Position);
		modelShader.setVec3("dirlight.ambient", 0.05f, 0.05f, 0.05f);
		modelShader.setVec3("dirlight.diffuse", 0.4f, 0.4f, 0.4f);
		modelShader.setVec3("dirlight.specular", 0.5f, 0.5f, 0.5f);
		modelShader.setVec3("dirlight.direction", -0.2f, -1.0f, -0.3f);
		modelShader.setVec3("pointlights[0].ambient", ambientColor);
		modelShader.setVec3("pointlights[0].diffuse", diffuseColor);
		modelShader.setVec3("pointlights[0].specular", specularColor);
		modelShader.setVec3("pointlights[0].position", pointLightPositions[0]);
		modelShader.setFloat("pointlights[0].constant", 1.0f);
		modelShader.setFloat("pointlights[0].linear", 0.09f);
		modelShader.setFloat("pointlights[0].quadratic", 0.032f);
		modelShader.setVec3("pointlights[1].ambient", ambientColor);
		modelShader.setVec3("pointlights[1].diffuse", diffuseColor);
		modelShader.setVec3("pointlights[1].specular", specularColor);
		modelShader.setVec3("pointlights[1].position", pointLightPositions[1]);
		modelShader.setFloat("pointlights[1].constant", 1.0f);
		modelShader.setFloat("pointlights[1].linear", 0.09f);
		modelShader.setFloat("pointlights[1].quadratic", 0.032f);
		modelShader.setVec3("pointlights[2].ambient", ambientColor);
		modelShader.setVec3("pointlights[2].diffuse", diffuseColor);
		modelShader.setVec3("pointlights[2].specular", specularColor);
		modelShader.setVec3("pointlights[2].position", pointLightPositions[2]);
		modelShader.setFloat("pointlights[2].constant", 1.0f);
		modelShader.setFloat("pointlights[2].linear", 0.09f);
		modelShader.setFloat("pointlights[2].quadratic", 0.032f);
		modelShader.setVec3("pointlights[3].ambient", ambientColor);
		modelShader.setVec3("pointlights[3].diffuse", diffuseColor);
		modelShader.setVec3("pointlights[3].specular", specularColor);
		modelShader.setVec3("pointlights[3].position", pointLightPositions[3]);
		modelShader.setFloat("pointlights[3].constant", 1.0f);
		modelShader.setFloat("pointlights[3].linear", 0.09f);
		modelShader.setFloat("pointlights[3].quadratic", 0.032f);
		modelShader.setVec3("spotlight.ambient", 0.0f, 0.0f, 0.0f);
		modelShader.setVec3("spotlight.diffuse", 1.0f, 1.0f, 1.0f);
		modelShader.setVec3("spotlight.specular", 1.0f, 1.0f, 1.0f);
		modelShader.setVec3("spotlight.direction", camera.Front);
		modelShader.setVec3("spotlight.position", camera.Position);
		modelShader.setFloat("spotlight.cutOff", glm::cos(glm::radians(12.5f)));
		modelShader.setFloat("spotlight.outerCutOff", glm::cos(glm::radians(15.0f)));
		modelShader.setFloat("spotlight.constant", 1.0f);
		modelShader.setFloat("spotlight.linear", 0.09f);
		modelShader.setFloat("spotlight.quadratic", 0.032f);

		ourModel.Draw(modelShader);

posted @ 2023-05-13 11:59  ETHERovo  阅读(119)  评论(0编辑  收藏  举报