Assimp里的一些知识(1)

OpenGL 学习到模型加载的时候,介绍了一个模型导入库(Open Asset Import Library,Assimp)的用法。初学的时候觉得稍微有些复杂,故借由这篇blog来简单地捋一下其中的细节。



首先,当我们使用Assimp导入模型的时候,它通常会将整个模型加载到一个场景(Scene)对象,这个对象包含了导入模型的所有数据。具体结构如下图所示(这个图结构十分重要,需要充分理解):

  1. Scene 场景。Scene场景有3个成员,分别是mRootNode(场景根节点),mMeshes(场景中所有网格Mesh的集合),mMaterials(网格的材质属性)。
  2. Node节点。场景的根节点(Root Node, mRootNode指向)包含了子节点,同时每个节点中有一系列指向场景对象中具体mMeshes成员的索引(Scene下的mMeshes数组储存了真正的Mesh对象,节点中的mMeshes数组保存的只是场景中网格数组的索引)。此外,这个子结构让我们可以使用递归的方式来处理节点(稍后讲到)。
  3. Mesh网格,一个Mesh 网格是可以看做渲染的一个单位,它包含了顶点数据,法线,纹理坐标(只是坐标数据,不是纹理对象),面等数组,以及材质索引(指向Scene中的mMaterials
  4. Face面,面数组由面组成(废话),概念上,一个面表示的是物体的渲染图元(primitive)(三角形,方形,点)。在这里,我们则让它包含了组成图元的顶点索引(也就是指向Mesh里面的顶点数据啦),从而我们可以使用EBO来绘制图形。
  5. Material材质,一个Mesh网格还包括了一个材质索引(非数组),索引指向Scene中的具体材质。

这里基本上就把Assimp的结构讲解完毕了,在这里补充一下关于材质,贴图,纹理等的相关知识(防止混淆):贴图,纹理,材质的区别是什么?

顺便补充一下什么叫Mesh,以及它的图元如何组成Mesh:What is a mesh in OpenGL?



好了,Assimp导入模型到场景之后的数据结构我们已经清楚了,现在我们需要一个类来专门处理这些导入的数据。其实也很简单,数据处理过程与之前绘制箱子的过程基本一致,只是由于未知导入模型的数据量,我们采用了vector模板类来存储相关数据。

首先,一个网格需要的最少数据量就是顶点数据,法线,纹理坐标。用于面绘制的索引。以及纹理形式的材质数据,,因此我们在这里可以定义一些结构体(没定义到的后面Mesh类也会出现的啦):

struct Vertex {
    glm::vec3 Position;
    glm::vec3 Normal;
    glm::vec2 TexCoords;
};
struct Texture {
    unsigned int id;
    string type;  //such as diffuseMap or specularMap
};

定义好结构体之后,就是Mesh类的具体定义了:

class Mesh
{
public:
    vector<Vertex> vertices;   //顶点数据
    vector<unsigned int> indices    //顶点绘制索引,也就是Face里面的indices
    vector<Texture> texture;    //材质(纹理)数据。严格的说是物体的材质贴图。详见上面关于材质纹理贴图的知乎链接啦。

    Mesh(vector<Vertex> vertices, vector<unsigned int> indices, vector<Texture> texture);

    void Draw(Shader shader);   //绘制网格的函数

private:
    unsigned int VAO, VBO, EBO;
    void setupMesh();    //处理网格数据的函数
}

不废话了,直接给setupMesh的源码(其实是懒)

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

这里有一些需要注意的东西。

第一个是传入数据至VBO以及EBO的时候,由于我们使用的是vector模板类,不可以直接用sizeof()计算大小(为什么?),所以通过用.size(),以及结构体的大小来计算数据量。还有我们用的&vertices[0],而不是像之前那样的vertices,是因为之前用数组实现,数组名即为指针,用vector实现,需要手动添加首元素的地址。

实际上因为结构体的存储方式是连续的(sequential),我们得以直接传入结构体的指针(&vertices[0])作为缓冲的数据(的起始点)

其二是OffsetOf函数,顾名思义用来计算偏移量。

然后就是Draw函数了:

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.setFloat(("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);
}

这里我们稍微回顾一下纹理的相关知识:如何把纹理传入fragment shader,首先激活一个对应的纹理单元,然后我们将要绑定的纹理绑定起来,接着将在着色器里面纹理采样器的值设置为对应的纹理单元值(0-15)。上面Draw函数干的就是这事儿,并且最后根据索引(EBO),当前激活的着色器等绘制图元,

Model类下一节讲。

原文地址:https://www.cnblogs.com/zhlabcd/p/11726955.html

时间: 2024-10-29 04:54:26

Assimp里的一些知识(1)的相关文章

Assimp里的一些知识(2)

上一节的Mesh类主要的目标是将加载进来的模型相关数据做处理,然后绘制出来.那么在Mesh类之前,我们还需要一个Model类去加载这些模型,将其转化为数据并对另外的一些数据做一些处理. 话不多说,直接上code: class Model { public: /* 函数 */ Model(char *path) { loadModel(path); } void Draw(Shader shader); private: /* 模型数据 */ vector<Mesh> meshes; strin

codeforces#253 D - Andrey and Problem里的数学知识

这道题是这样的,给主人公一堆事件的成功概率,他只想恰好成功一件. 于是,问题来了,他要选择哪些事件去做,才能使他的想法实现的概率最大. 我的第一个想法是枚举,枚举的话我想到用dfs,可是觉得太麻烦. 于是想是不是有什么规律,于是推导了一下,推了一个出来,写成代码提交之后发现是错的. 最后就没办法了,剩下的时间不够写dfs,于是就放弃了. 今天看thnkndblv的代码,代码很短,于是就想肯定是有什么数学规律,于是看了一下, 果然如此. 是这样的,还是枚举,当然是有技巧的,看我娓娓道来. 枚举的话

烂在肚子里的救命知识!

救命 一.半身不遂(不管脑出血还是栓塞)出现口眼歪钭,马上取缝衣针将双耳垂最下点刺破,各挤出一滴血就能好转,且愈后不留任何后遗症. 二.心脏病刚刚猝死病人发现后,马上脱掉袜子,用缝衣针先分别刺破十个脚趾尖,然后各挤出一滴血,不等挤完十个脚趾尖,病人即清醒过来. 三.不管哮喘还是急性喉炎等,发现病人出不来气,憋得脸红脖子粗,赶紧用缝衣针刺破鼻尖,挤出两滴黑血即愈. 四.抽羊角风(癜痫)后,取出缝衣针刺破人中穴挤出一滴血马上即愈,(人中穴在鼻唇沟的中间).           以上四法均无任何危险,

PMBOK指南里十大知识领域指的什么?

PMBOK十大知识领域是:整合管理.范围管理.时间管理.成本管理.质量管理.人力资源管理.沟通管理.风险管理.采购管理.干系人管理 各用一句话概括项目管理知识体系十大知识领域: 1.整合管理:其作用犹如项链中的那根线: 2.范围管理:做且只做该做的事: 3.时间管理:让一切按既定的项目进度进行: 4.成本管理:算准钱和花好钱: 5.质量管理:目的是满足需求: 6.人力资源管理:让团队成员高效率地一起合作: 7.沟通管理:在合适的时间让合适的人通过合适的方式把合适的信息传达给合适的人: 8.风险管

比特币里的计算机知识

一.人民币.支付宝和比特币有什么区别 (1)人民币 大家都很清楚,人民币就是中国人民银行发行的货币.这些货币流通到市场中,每个人有了钱以后可以去购买各种商品和服务,人民币就会从一个人手里转到另外一个人手里.钱流通到市场中后,具体某一个编号的纸币在某个特定的时候在谁身上是不确定的,国家也不关心.纸币在实际中是很难复制的,复制成本高.风险也很大. (2)支付宝 支付宝,当然包括微信,里面的余额说白了就是一个数据,存储在支付宝和微信公司的服务器上.同人民币不同,数据的复制.修改是不需要成本的.这些电子

世界是数字的重点读书笔记(计算机科普知识)

<世界是数字的>是世界顶尖计算机科学家Brian W.Kernighan写的一本计算机科普类读物,简明扼要但又深入全面地解释了计算机和通信系统背后的秘密,适合计算机初学者和非计算机专业的人读.这真的是一本好书,借Google常务董事长的话: 对计算机.互联网及其背后的奥秘充满好奇的人们,这绝对是一本不容错过的好书. 对于一个计算机已经学了N年的专业人士来说,这本书也许简单了点,不过我还是认真过了一遍,发现也有一定的收货,因为一个人很难掌握本领域里的所有知识,或多或少会有一些欠缺,总会有一些你以

知乎:有哪些让你相见恨晚的 PPT 制作技术或知识?

邵云蛟,倒鸡汤小能手! 谢其伟.影舞狂魔.郭郭郭 等人赞同 作为一个资深的PPT发烧友,让我带你们见识一些足以让你相见恨晚的知识,希望给各位增加点人前装逼的资本! 未经允许,请勿转载!如需转载,请联系我本人,微信号:syj18520239046 另外,本文在知乎和我的公众号旁门左道(ID:pangmenzd)同时发布. -----------------------------------废话就那么多,接下来让我们正式进入主题: 本文分为3大部分:自带功能篇,辅助工具篇和独家经验篇. 先来说第一

科幻小说《霜与火》 by 雷&#183;布雷德伯里

一半夜里,西穆降生了.他躺在洞穴里冰冷的石块上号哭. 他的血液流经全身,每分钟脉搏达一千跳.他不断地长大. 他的母亲用发烫的手把吃的送进他的嘴里,生命的噩梦开始了.他几乎一生下来就露出警惕的眼光,接着也不知道为什么缘故,眼光里充满了惊吓害怕的神色.吃的东西噎住了他的喉咙,他呛着又号哭起来.他漫无目的地环顾四周. 周围是一重浓雾.雾慢慢散开了.洞穴显现了轮廓.一个男人的高大身影出现在他眼前,这人疯疯癫病的,神情狂乱,十分可怕.一张垂死的脸.由于风吹雨打,显得十分苍老,好象在火中烘干了的土坯.这人蹲

JavaScript基础知识3

JavaScript中的函数运行在它们被定义的作用域里,而不是它们被执行的作用域里 JavaScript基础知识3