zoukankan      html  css  js  c++  java
  • Assimp里的一些知识(2)

    上一节的Mesh类主要的目标是将加载进来的模型相关数据做处理,然后绘制出来。那么在Mesh类之前,我们还需要一个Model类去加载这些模型,将其转化为数据并对另外的一些数据做一些处理。


    话不多说,直接上code:

    class Model 
    {
        public:
            /*  函数   */
            Model(char *path)
            {
                loadModel(path);
            }
            void Draw(Shader shader);   
        private:
            /*  模型数据  */
            vector<Mesh> meshes;
            string directory;  //文件所在的目录路径,非文件路径。
            /*  函数   */
            void loadModel(string path);
            void processNode(aiNode *node, const aiScene *scene);
            Mesh processMesh(aiMesh *mesh, const aiScene *scene);
            vector<Texture> loadMaterialTextures(aiMaterial *mat, aiTextureType type, 
                                                 string typeName);
    };

    Draw函数没什么好讲的啦,遍历所有的网格(处理过的),并且调用网格各自的Draw函数:

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

    接下来导入(加载)模型:

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

    库函数的意思这里就不详细讲了,直接查文档来得快。说一下功能:加载模型函数,首先ReadFile函数从给定的文件路径中读取模型,并且做一些后期处理(第二个参数枚举类)。接着检测场景以及根节点不为0,并且检查了一个标记(mFlags)来看返回的模型是不是完整的,如果不是就打印错误信息并直接退出loadModel函数。directory变量获取文件路径的目录路径,之后用processNode函数处理各个节点。

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

    processNode函数这里,首先用一个for循环处理第一个节点中mMeshes索引,找到scene中对应的mesh(网格),经过processMesh函数处理后将其添加入Mesh类vector模板的变量。之后由于节点符合递归的条件,所以我们直接使用递归函数对后续的子节点进行同样的操作。

    processMesh()函数:

    Mesh 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.0f, 0.0f);
        
        
           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);
    }    

    这个函数有点长,不过难度并不高。首先处理顶点结构体中的数据(顶点位置,法线,以及纹理坐标)。然后处理索引,最后处理材质。之后返回值是什么呢?是调用Mesh类的构造函数之后的返回值。而我们知道Mesh类构造函数中有一个setupMesh()函数处理这些数据,也就是说会将这些数据处理之后再返回。之后这个返回值就追加(push_back)到了上面的meshes。再然后我们都知道了,meshes调用了Draw函数绘制图形。

    由下至上地完成了这个加载模型,绘制模型的过程。

    这个过程让我感受到了面对对象编程的魅力(XD)。

    还忘了处理材质那里出现的函数:

    vector<Texture> loadMaterialTextures(aiMaterial *mat, aiTextureType type, string typeName)
    {
        vector<Texture> textures;
        for(unsigned int i = 0; i < mat->GetTextureCount(type); i++)
        {
            aiString str;
            mat->GetTexture(type, i, &str);
            Texture texture;
            texture.id = TextureFromFile(str.C_Str(), directory);
            texture.type = typeName;
            texture.path = str;
            textures.push_back(texture);
        }
        return textures;
    }

    loadMaterialTextures函数遍历了给定纹理类型的所有纹理位置,获取了纹理的文件位置,并加载并和生成了纹理,将信息储存在了一个Vertex结构体中。

    这里其实出现了一个问题,就是如果有重复的纹理怎么办?我们上面并没有针对处理重复纹理的代码,这样子会大大降低速度,因此,我们做出如下优化:

    vector<Texture> loadMaterialTextures(aiMaterial *mat, aiTextureType type, string typeName)
    {
        vector<Texture> 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;
    }

    同时,还需要将texture结构体加入path变量

    struct Texture {
        unsigned int id;
        string type;
        aiString path;  // 我们储存纹理的路径用于与其它纹理进行比较
    };
    
    vector<Texture> textures_loaded;

    最后在主函数里面调用Model类就完事儿啦

    ohhhhhh

    原来人类的悲欢并不相通, 我只觉得他们吵闹。
  • 相关阅读:
    关于MySQL5.6配置文件my-default.ini不生效问题
    jQuery学习总结(三)
    jQuery学习总结(二)
    jQuery学习总结(一)
    mysql输出到页面MVC模式
    简单的在jsp页面操作mysql
    mysql5.7的基本使用
    mysq5.7l的下载与配置
    jdk环境变量的配置
    SQL SERVER——给已有数据的表增加唯一值
  • 原文地址:https://www.cnblogs.com/zhlabcd/p/11727712.html
Copyright © 2011-2022 走看看