OpenGL学习 跟着官网教程学习(模型加载)
创始人
2025-05-29 04:26:11

0,前言

        本节内容是导入并显示一个别人做好的模型。

        模型库使用:Assimp(具体如何介绍可以看官网)

        下载地址:http://assimp.org/index.php/downloads

1,实现

(1),网格(Mash)结构体的定义

        网格是单个的可绘制的实体。

        一个网格应当包含以下数据:

               1,一系列顶点

               2,索引(也就是EBO)

               3,纹理形式的材质数据 (漫反射。镜面贴图)

        一个顶点又包含以下数据:

                1,顶点的位置

                2,顶点的法向量

                3,纹理坐标

        一个纹理应该包含以下数据:
                1,纹理的ID

                2,纹理的类型

                3,纹理的路径(这个主要用于优化)

        那么我们的定义的结构体应该如下:

        

//顶点
struct Vertex {glm::vec3 Position;glm::vec3 Normal;glm::vec2 TexCoords;
};//纹理
struct Texture {unsigned int id;string type;
};//网格
class Mesh {public:/*  网格数据  */vector vertices;vector indices;vector textures;/*  函数  */Mesh(vector vertices, vector indices, vector textures);void Draw(Shader shader);private:/*  渲染数据  */unsigned int VAO, VBO, EBO;/*  函数  */void setupMesh();
};  

(2),函数实现

1,setupMesh函数(这一部分就是将之前设置GPU数据缓冲和属性的代码抄过来就行)

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);
}  

注意:预处理指令offsetof(s, m),它的第一个参数是一个结构体,第二个参数是这个结构体中变量的名字。这个宏会返回那个变量距结构体头部的字节偏移量(Byte Offset)

2,Draw函数

        首先,我们需要修改一下顶点着色器的代码:

        将材质结构体改为:

//材质
struct Material {sampler2D texture_diffuse1;sampler2D texture_specular1;sampler2D emission; float shininess;
}; 
void Draw(Shader shader) 
{unsigned int diffuseNr = 1;unsigned int specularNr = 1;for(unsigned int i = 0; i < textures.size(); i++){glActiveTexture(GL_TEXTURE0 + i); // 在绑定之前激活相应的纹理单元// 获取纹理序号(diffuse_textureN 中的 N)string number;string name = textures[i].type;if(name == "texture_diffuse")number = std::to_string(diffuseNr++);else if(name == "texture_specular")number = std::to_string(specularNr++);shader.setInt(("material." + 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);
}

(3)mesh构造函数

Mesh(vector vertices, vector indices, vector textures)
{this->vertices = vertices;this->indices = indices;this->textures = textures;setupMesh();
}

(3)Model的结构体

        在定义好了Mesh之后,我们的工作就是将别人写好的模型加载进来,并且将其放到我们新定义的类Model中。Model的定义如下:

class Model 
{public:/*  函数   */Model(char *path){loadModel(path);}void Draw(Shader shader);   private:/*  模型数据  */vector meshes;string directory;/*  函数   */void loadModel(string path);void processNode(aiNode *node, const aiScene *scene);Mesh processMesh(aiMesh *mesh, const aiScene *scene);vector loadMaterialTextures(aiMaterial *mat, aiTextureType type, string typeName);
};

(4)Model函数实现

1,Draw函数

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

2,loadModel函数

        首先,我们需要包含Assimp的头文件:

#include 
#include 
#include 

         LoadModel函数如下:

        Assimp的结构中,每个节点包含了一系列的网格索引,每个索引指向场景对象中的那个特定网格。我们接下来就想去获取这些网格索引,获取每个网格,处理每个网格,接着对每个节点的子节点重复这一过程(processNode函数)。

        通过ReadFile函数的我们可以获取模型的根节点。

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);
}

        注意:

importer.ReadFile(path, aiProcess_Triangulate | aiProcess_FlipUVs);

        ReadFile函数的第二个参数是一些后处理的选项:

        1,aiProcess_Triangulate,我们告诉Assimp,如果模型不是(全部)由三角形组成,它需要将模型所有的图元形状变换为三角形。

        2,aiProcess_FlipUVs将在处理的时候翻转y轴的纹理坐标(你可能还记得我们在纹理教程中说过,在OpenGL中大部分的图像的y轴都是反的,所以这个后期处理选项将会修复这个)。

        3,aiProcess_GenNormals:如果模型不包含法向量的话,就为每个顶点创建法线。

        4,aiProcess_SplitLargeMeshes:将比较大的网格分割成更小的子网格,如果你的渲染有最大顶点数限制,只能渲染较小的网格,那么它会非常有用。

        5,aiProcess_OptimizeMeshes:和上个选项相反,它会将多个小网格拼接为一个大的网格,减少绘制调用从而进行优化。

3,processNode函数:

        由于节点都类似于二叉树的结构,我们可以使用递归的方法遍历每一个节点。

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);}
}

4,processMesh函数

        将Assimp的对象转化为我们自己的模型:

	Mesh processMesh(aiMesh* mesh, const aiScene* scene){vector vertices;vector indices;vector  textures;Vertex vertex;//处理顶点位置,法线和纹理坐标for (unsigned int i = 0; i < mesh->mNumVertices; i++){vertex.Position.x = mesh->mVertices[i].x;vertex.Position.y = mesh->mVertices[i].y;vertex.Position.z = mesh->mVertices[i].z;			//处理索引vertex.Normal.x = mesh->mNormals[i].x;vertex.Normal.y = mesh->mNormals[i].y;vertex.Normal.z = mesh->mNormals[i].z;//处理纹理坐标//纹理坐标的处理也大体相似,但Assimp允许一个模型在一个顶点上有最多8个不同的纹理坐标,我们不会用到那么多,我们只关心第一组纹理坐标if (mesh->mTextureCoords[0])	//网格是否有纹理坐标{glm::vec2 vec;vec.x = mesh->mTextureCoords[0][i].x;vec.y = mesh->mTextureCoords[0][i].y;vertex.TexCoords = vec;}elsevertex.TexCoords = glm::vec2(0.0f, 0.0f);vertices.push_back(vertex);}//处理索引,EBOfor (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 diffuseMap = loadMaterialTextures(material, aiTextureType_DIFFUSE, "texture_diffuse");textures.insert(textures.end(), diffuseMap.begin(), diffuseMap.end());vector specularMap = loadMaterialTextures(material, aiTextureType_SPECULAR, "texture_specular");textures.insert(textures.end(), specularMap.begin(), specularMap.end());}return Mesh(vertices, indices, textures);}

5, loadMaterialTextures函数:

        注意我们这里使用了texture结构体中的path变量,避免多次加载同一个纹理。

vector loadMaterialTextures(aiMaterial *mat, aiTextureType type, string typeName)
{vector textures;for(unsigned int i = 0; i < mat->GetTextureCount(type); i++){aiString str;mat->GetTexture(type, i, &str);bool skip = false;for(unsigned int j = 0; j < textures_loaded.size(); j++){if(std::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.type = typeName;texture.path = str.C_Str();textures.push_back(texture);textures_loaded.push_back(texture); // 添加到已加载的纹理中}}return textures;
}

2,结果

 与官网给的有点区别,没有红色的眼镜,应该是assimp模型有点问题。

 

相关内容

热门资讯

第一部分——简单句——第一章—... 谓语动词的物种变化 (二)情态   情态不变动词 can be/must...
网商银行40亿永续债获批!银行... 本文来源:时代周报 作者:黄宇昆时隔5年,浙江网商银行再次获批发债“补血”。近日,浙江金融监管局发布...
甜粽子和咸粽子都没人吃了? 在端午节的传统美食中,甜粽子和咸粽子曾是人们餐桌上的常客。然而,如今却出现了一种令人惊讶的现象,那就...
超4100只个股下跌 超410... 2025.05.30本文字数:424,阅读时长大约1分钟作者 |一财资讯截至午间收盘,沪指跌0.31...
「午盘」A股早盘弱势震荡收跌,... A股三大股指5月30日集体低开。早盘两市探底回升,三大股指跌势明显,个股呈现普跌态势。从盘面上看,可...
English Learnin... English Learning - L2 语音作业打卡 复习对比 [ɔ:] [ɒ] Day22 2...
Java设计模式 02-工厂模... 工厂模式 一、简单(静态)工厂模式 1、看一个具体的需求 看一个披萨的项目:要便于披萨...
嘉应制药信披违规突遭立案,养天... 5月28日晚,广东嘉应制药股份有限公司(以下简称“嘉应制药”)发布公告,公司收到中国证券监督管理委员...
河北保定蠡县县委书记陈伟已跨市... 澎湃新闻记者从相关方面独家获悉,原任河北保定蠡县县委书记的陈伟近日已跨市调任廊坊三河市委书记。 陈...
4月,全国发行新增债券2534... 2025年4月地方政府债券发行和债务余额情况 一、全国地方政府债券发行情况 (一)当月发行情况。 2...
JFX-A型精子质量分析仪空气... JFX-A型精子质量分析仪空气减震器:为精准医疗保驾护航行 在现代医疗技术中,精子质量分析仪是评估男...
“王健林卖万达广场”话题连续4... "先定个小目标"的王健林,如今却像清仓特卖般疯狂甩卖手里近1/5的万达广场,背后暗藏什么玄机? 短...
AI概念股早盘走弱,人工智能相... AI概念股早盘走弱,中科星图跌超8%,芯原股份、寒武纪-U、光环新网跌超3%。 受盘面影响,人工智能...
原创 稀... 近期,据美国《纽约时报》援引匿名知情人士的话报道称,美国商务部已暂停部分允许美国公司向中国商用飞机有...
一线城市豪宅市场火爆:500亿... “日光盘”接连上演,上海楼市持续高温。 5月27日,上海共有7个楼盘集中入市,其中3个项目实现“日光...
信泰人寿合规之殇:国资入局能否... 信泰人寿从民企转为国企,过程布满荆棘。自2007年成立以来,经历了股东内斗、偿付能力危机、高管贪腐、...
傲农生物“脱险”后,何时恢复盈... 得益于2024年财报的向好表现,福建傲农生物科技集团股份有限公司(简称“傲农生物”)近日被撤销退市风...
Java:分布式RPC框架Ap... 目录一、软件架构的演进过程【了解】二、Dubbo概述【了解】1、Dubbo简介2、Dubb架构三、服...
长肥网络与TCP的长肥管道 本文目录1、简化的理解网络模型2、时延带宽积的定义3、长肥网络与TCP长肥管道的定义4、TCP长肥管...
从NLP视角看电视剧《狂飙》,... 文章目录1、背景2、数据获取3、文本分析与可视化3.1 短评数据预处理3.2 词云图可视化3.3 t...
下一个万亿级蓝海市场,数字能源... 随着全球碳中和共识的形成、能源转型的推进,数字能源作为新一代能源技术的重要组成部分,成为各国政府和企...
【CSS】P9 选择器优先级 选择器优先级抛出一个问题选择器优先级权重!important外部样式与内部样式冲突 抛出一个问题 ...
孙悟空为何成了完美男友? 孙悟空成为完美男友,实乃其独特特质所致。他神通广大,能在女友遇到危险时瞬间现身,如那盖世英雄,给予最...
深市同标的规模最大的证券ETF... 5月30日,A股早盘震荡走低,券商板块回调。相关ETF中,证券ETF(159841)截至发稿跌0.7...
中建投信托地产风险化解仍需时日... 中建投信托仍然被“地产旧伤"拖累。文/每日财报 汇水在信托行业深度转型的2024年,年报数据清晰反...
RTP载荷H265(实战细节) H264与H265协议详解RTP载荷H264(实战细节) RTP载荷H2...
【2023.3.8】数据结构复... 【2023.3.8】数据结构复习笔记 文章目录【2023.3.8】数据结构复习笔记序言一、绪论二、线...
赛力斯应邀出席东盟重要经济论坛... 近年来,伴随着中国汽车产业的全面国际化,中国车企的全球影响力日益提升,就在最近赛力斯被应邀出席东盟-...
聚集更有国际化潜力研发管线 君... 5月29日,上海君实生物医药科技股份有限公司(简称“君实生物”)发布关于部分募投项目子项目变更及金额...
面经-2023-哲库Zeku-... 专栏推荐:2023 数字IC设计秋招复盘——数十家公司笔试题、面试实录 专栏首页:2023 数字IC...