(自用)learnOpenGL学习总结-模型加载-mesh and model

assimp库 

这是一个可以可以把obj文件传入我们系统的库。


mesh 网格

我们新建一个mesh类用来表示我们之前的那些模型数据,网格是那么需要一系列的顶点vertex的,而顶点需要有哪些数据呢,位置、向量、纹理坐标。

把这些信息归到顶点类中。

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:
	Mesh(float vertices[]);
	
	//网格数据
	std::vector<Vertex> vertices;
	std::vector<unsigned int> indices;
	std::vector<Texture> textures;
	//函数
	Mesh(std::vector<Vertex> vertices,std::vector<unsigned int> indices,std::vector<Texture> textures);
	void Draw(Shader* shader);



private:
	//渲染数据
	unsigned int VAO, VBO, EBO;
	void setupMesh();

};

在class里面有一个setup函数,是用来初始化缓冲的,也就是把之前那些VAO初始化和设置顶点数据的工作放到这个里面。

然后用draw来绘制网格,也就是之前在render loop中最后draw的那块。draw()是传入了一个shader着色器。

void Mesh::setupMesh(){
	//VAO 生成绑定
	glGenVertexArrays(1, &VAO);
	glBindVertexArray(VAO);
	//VBO 生成绑定填充数据
	glGenBuffers(1, &VBO);
	glBindBuffer(GL_ARRAY_BUFFER,VBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex)*vertices.size(), &vertices[0], GL_STATIC_DRAW);
	//EBO 生成绑定填充数据
	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);
	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);
};

有两个和之前的不同:

一个是在glbufferdata中,第二个参数,顶点数据的大小,这里是vertices.size() * sizeof(Vertex),顶点个数*每个顶点的字节大小(每个顶点32字节=8个float*4byte)

第二个是glVertexAttribPointer的最后一个参数,这里面用了个offsetof来定义偏移量,它的第一个参数是一个结构体,第二个参数是这个结构体中变量的名字。这个宏会返回那个变量距结构体头部的字节偏移量(Byte Offset)。这样我们就不用自己去算偏移量了。直接给名字就可以


model模型

在前面已经把mesh网格做完了,现在要接着去做model,一个模型里面会有多个mesh,更大一点的说,在一个场景中有多个模型,每个模型又有多个mesh。

#pragma once


#include"Mesh.h"
#include<string>
#include<vector>

class Model
{
public:
	//函数
	Model(std::string path);
	void Draw(Shader* shader);

private:
	//模型数据
	std::vector<Mesh> meshes;
	std::string directory;
	//函数
	void loadModel(std::string path);
	void processNode(aiNode* node,const aiScene*scene);
	Mesh processMesh(aiMesh* mesh, const aiScene* scene);
	std::vector<Texture> loadMaterialTextures(aiMaterial* mat, aiTextureType type,
		std::string typeName);
};

首先看一下.h,这里有个mesh的数组,会通过loadmodel来加载文件,draw就很简单,遍历网格然后画出来

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

导入3d模型导opengl

首先我们要知道一个在assimp中的数据结构scene

有了这个场景我们就可以去访问里面的模型了。通过这样的声明就可以去获取场景对象了。

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

	if (!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode) {
		std::cout << "Assimp error" << std::endl;
		return;
	}
	directory = path.substr(0, path.find_last_of('\\'));
	//std::cout <<"success!"<< directory << std::endl;
	processNode(scene->mRootNode, scene);
}

directory = path.substr(0, path.find_last_of('\\'));这条函数很有趣,是用来定位文件路径的,直到组后一个\,然后把我们需要的那些模型文件放到exe同名文件下。可以试着输出一下directory。

得到文件目录了之后就是要去遍历场景中的所有节点了,就是scene的那个数据机构中的所有节点了。

这里就和链表中的深度优先算法一样,不断处理节点的所有子节点,然后递归后返回。

void Model::processNode(aiNode* node, const aiScene* scene)
{
	//std::cout << node->mName.data << std::endl;
	//处理节点的所有mesh
	for (unsigned int i = 0; i < node->mNumMeshes; i++) {
		aiMesh*curMesh = scene->mMeshes[node->mMeshes[i]];
		meshes.push_back(processMesh(curMesh, scene));
	}
	//然后处理节点
	for (unsigned int i = 0; i < node->mNumChildren; i++) {
		processNode(node->mChildren[i], scene);
	}
}

这里理清一下思路,对于processNode传入两个参数,当前的node和整个场景。

先处理当前的node,先获得当前节点的mesh然后用processMesh来把当前的网格参数存到meshes数组中。然后在对他的子节点做同样处理。

然后就是processMesh函数

他的功能是把传进来的网格参数变成我们需要的网格参数,就是在mesh中的三个(顶点属性、索引、纹理)

  • 7
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值