zoukankan      html  css  js  c++  java
  • 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,首先将在着色器里面纹理采样器(sampler2D)的值设置为对应的纹理单元值(0-15),然后激活一个对应的纹理单元,接着我们将要绑定的纹理绑定(glBindTexture())起来。上面Draw函数干的就是这事儿,并且最后根据索引(EBO),当前激活的着色器等绘制图元,

    Model类下一节讲。

    原来人类的悲欢并不相通, 我只觉得他们吵闹。
  • 相关阅读:
    intellij idea
    this.getClass().getResource(String) 路径问题
    org.hibernate.AssertionFailure: null id 错误
    Hibernate 映射
    关于idea 在创建maven 骨架较慢问题解决
    常用base.css
    form表单样式
    ul li自适应居中导航
    table-cell实现未知宽高图片,文本水平垂直居中在div
    多行文字水平垂直居中在div
  • 原文地址:https://www.cnblogs.com/zhlabcd/p/11726955.html
Copyright © 2011-2022 走看看