Linux OpenGL 实践篇-9 模型

  之前一直渲染箱子,显得有点单调。这一次我们绘制一个用艺术家事先用建模工具创建的模型。

  本次实践参考:https://learnopengl-cn.github.io/03%20Model%20Loading/01%20Assimp/

  在之前我们的OpenGL实践中,绘制图形的过程是先定义顶点的位置、法线、纹理坐标(UV)等信息,按一定的规则组织后传给着色器处理,最终绘制到屏幕上。现在使用艺术家构建的模型,绘制的过程并没有变,只不过顶点和使用的贴图信息从原来我们自己定义变为从已构建好的模型中提取,所以导入模型的重点是解析模型文件中的顶点、贴图等信息,然后按照OpenGL的规则组织读取的数据。

  艺术家构建模型时使用率高的几个构建工具有:blender,3ds max, maya。这些工具提供良好的界面和操作方式,艺术家可以非常方便顺畅的构建想要的模型,同时也屏蔽了模型文件保存的细节,使得他们并不需要关心他们构建的模型数据如何保存。但如果你想把这些文件中的数据导入到OpenGL中就必须了解这些文件的格式。然而,现在模型文件的格式有很多中,每一种的结构并不相同。比如比较简单的Wevefront 的.obj格式和基于xml的比较复杂的collada文件格式,我们如果想支持他们的导入,就需要为它们都写一个导入模块,幸运的是现在有一个叫Assimp的库专门处理这个问题,我们直接使用即可。在使用Assimp之前,我们推荐自己编译Assimp库,一般的与编译库使用起来会有比较多的问题或者根本就不能使用。

Assimp

  Assimp全称是Open Asset Import Library的缩写,是一个非常流行的模型导入库。它解析模型文件,把顶点数据等信息按它设定的格式组织起来,我们的程序只需要按照它的格式使用数据即可,文件格式的差异性由Assimp处理。

  Assimp使用的数据结构在概念上可以分为场景(scene)、节点(Node)、网格(Mesh)。

  场景:一个模型文件导入之后通常就一个场景对象,这个场景对象包含所有的模型数据;

  节点:场景对象的数据组织结构是树,通常有一个根节点(root node),每个节点下会包含其他的节点;

  网格:网格存储所有的渲染数据,包括顶点位置、UV、面、材质等信息。在节点中有一个mMeshes的数组,存储的只是网格数据中索引。

  具体的数据结构关系如下图:

导入模型

  使用Assimp之前,我们先引用所需的Assimp的头文件:

  #include "assimp/Importer.hpp"
  #include "assimp/scene.h"      
  #include "assimp/postprocess.h"

  根据Assimp的数据结构,我们在程序中也设计两个数据结构来使用Assimp的数据——Model和Mesh。其中Model对应的是模型的概念,Mesh对应的是网格的概念。Model和Mesh的关系是一对多。

  Model的定义:

  

class Model
{
        public:
                vector<Texture> textures_loaded;
                vector<Mesh> meshes;
                string directory;                                                

                Model(const string &path)
                {
                        LoadModel(path);
                }                                                                

                Model(const Model &other) = delete;
                void operator=(const Model &other) = delete;                     

                ~Model()
                {                                                                

                }                                                                

                void Draw(std::shared_ptr<Shader> shader);                       

        private:
                void LoadModel(string path);
                void ProcessNode(aiNode *node, const aiScene *scene);
                Mesh ProcessMesh(aiMesh *mesh, const aiScene *scene);
                vector<Texture> LoadMaterialTextures(aiMaterial *mat,aiTextureTyp
type,string typeName);
};                                                                               

  在Model中,我们需要做的事情就是解析文件,并把解析出来的数据Mesh中使用。

  Mesh包括顶点的位置、法线、UV、贴图信息(这些是我们现阶段暂时定义的,你也可以按你的想法添加额外的数据,不过要记得在Model中给相应的数据赋值)。

  

struct Vertex{                                    Vector3 position;        Vector3 normal;        Vector2 texcoords;};

struct Texture{                               unsigned int id;        string type;        string path;};      

class Mesh
{
        public:
                /*网格数据*/
                vector<Vertex> vertices;
                vector<unsigned int> indices;
                vector<Texture> textures;

                Mesh(vector<Vertex> vertices,vector<unsigned int> indices, vector<Texture> textures);
                void Draw(std::shared_ptr<Shader> shader);
        private:
                /*渲染数据*/
                unsigned int VAO,VBO,EBO;
                void SetupMesh();
};

  

  在Model中核心是LoadMoel的处理,读取出scene对象后,调用ProcessNode处理,ProcessNode是一个递归调用。

void Model::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)
        {
                std::cout<<"error:assimp:"<<import.GetErrorString()<<std::endl;
                return;
        }

        directory = path.substr(0,path.find_last_of(‘/‘));

        ProcessNode(scene->mRootNode,scene);}
void Model::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);
        }
}                                                                 

Mesh Model::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;                                    

                 Vector3 v;
                 v.x = mesh->mVertices[i].x;
                 v.y = mesh->mVertices[i].y;
                 v.z = mesh->mVertices[i].z;
                 vertex.position = v;                                                 

                 v.x = mesh->mNormals[i].x;
                 v.y = mesh->mNormals[i].y;
                 v.z = mesh->mNormals[i].z;
                 vertex.normal = v;                                                   

                 if(mesh->mTextureCoords[0])
                 {
                         Vector2 vec;
                         vec.x = mesh->mTextureCoords[0][i].x;
                         vec.y = mesh->mTextureCoords[0][i].y;
                         vertex.texcoords = vec;
                 }
                 else
                 {
                         vertex.texcoords = Vector2(0.0f,0.0f);
                 }                                                                    

                 /*//tangent
                 v.x = mesh->mTangents[i].x;
                v.y = mesh->mTangents[i].y;
                v.z = mesh->mTangents[i].z;
                vertex.tangent = v;                                                                                  

                //bitangent
                v.x = mesh->mBitangents[i].x;
                v.y = mesh->mBitangents[i].y;
                v.z = mesh->mBitangents[i].z;
                vertex.bitangent=v;*/                                                                                

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

        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的处理主要就是使用OpenGL的数据缓存对象组织这些数据:

 void Mesh::SetupMesh()
 {
         glGenVertexArrays(1,&VAO);
         glGenBuffers(1,&VBO);
         glGenBuffers(1,&EBO);

         glBindVertexArray(VAO);
         glBindBuffer(GL_ARRAY_BUFFER,VBO);

         glBufferData(GL_ARRAY_BUFFER,this->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), (GLvoid*)0);
         //顶点法线
         glEnableVertexAttribArray(1);
         glVertexAttribPointer(1,3,GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*)(offsetof(Vertex,normal)));
         //顶点纹理坐标
         glEnableVertexAttribArray(2);
         glVertexAttribPointer(2,2,GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*)(offsetof(Vertex,texcoords)));

         glBindVertexArray(0);
}

  在这里要注意的是纹理的使用,在本次的实践过程中,我是在着色器中使用固定的前缀和序号来组织纹理的,如漫反射贴图使用texture_diffuseN来表示,texture_specularN表示镜面反射,在着色器中预先定义好要使用的最大贴图数量,如3张漫反射贴图(texture_diffuse1,texture_diffuse2……) 。

  这种做法比较简单,你可能有其它更好的解决方案,但这些对现在来说没有关系。我们暂时这么用它。

void Mesh::Draw(std::shared_ptr<Shader> shader)
{
        unsigned int diffuseNr = 1;
        unsigned int specularNr = 1;                                                   

        for(unsigned int i = 0; i < textures.size(); ++i)
        {
                glActiveTexture(GL_TEXTURE0 + i); //在绑定之前激活相应纹理单元
                //获取纹理号
                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((name + number).c_str(),i);
                glBindTexture(GL_TEXTURE_2D,textures[i].id);
        }                                                                              

        //绘制网格
        glBindVertexArray(VAO);
        glDrawElements(GL_TRIANGLES,indices.size(),GL_UNSIGNED_INT,0);
        glBindVertexArray(0);                                          

        glActiveTexture(GL_TEXTURE0);
 }

  最终效果:

  

原文地址:https://www.cnblogs.com/xin-lover/p/8975963.html

时间: 2024-10-02 06:09:14

Linux OpenGL 实践篇-9 模型的相关文章

Linux OpenGL 实践篇-6 光照

经典光照模型 经典光照模型通过单独计算光源成分得到综合光照效果,然后添加到物体表面特定点,这些成分包括:环境光.漫反射光.镜面光. 环境光:是指不是来特定方向的光,在经典光照模型中基本是个常量. 漫反射光:是散射在各个方向上均匀的表面特定光源.物体表面通过光照照亮,即使这个表面没有将光源直接反射到你的眼睛中.漫反射与眼睛的方向没有关系,但与光源的方向有关,当表面直接面向光源的时候会表现的亮一些,而倾斜的时候则暗一些,因为在现实中倾斜的表面接受的光要少一些.在经典光照模型中,我们使用表面的法向量来

Linux OpenGL 实践篇-1

本次实践所使用环境为CentOS 7. 参考:http://www.xuebuyuan.com/1472808.html OpenGL开发环境搭建: 1.opengl库安装 opengl库使用mesa库,安装命令: yum intall mesa* mesa库是一个开源的三维计算机图形库,以开源的形式实现了opengl应用程序接口.具体介绍:https://www.mesa3d.org/intro.html. 2.glut安装 下载freeglut,下载地址为: https://github.c

Linux OpenGL 实践篇-3 framebuffer

GLEW说明 GLEW(OpenGL Extension Wrangler) 是OpenGL的另一个辅助库,主要封装了从OpenGL库中获取函数地址的过程,还包含了一些可以跨平台使用的OpenGL编程方法. 本次实践是使用数据缓存绘制两个三角形,重点是缓存的创建和数据输入.数据输入后,根据数据使用方式可分为非基于索引绘制和基于索引绘制,使用的方法分别为glDrawArray和glDrayElements. 首先,明确OpenGL缓存使用步骤: glGenBuffer glBindBuffer g

Linux OpenGL 实践篇-2 创建一个窗口

OpenGL 作为一个图形接口,并没有包含窗口的相关内容,但OpenGL使用必须依赖窗口,即必须在窗口中绘制.这就要求我们必须了解一种窗口系统,但不同的操作系统提供的创建窗口的API都不相同,如果我们在学习OpenGL时要去学习一整套的窗口系统,这将带来很多的不便,所以出现了GLUT.GLUT全称OpenGL Utility Toolkit,是一套和窗口系统无关的软件包,为我们提供了窗口创建,用户输入输出处理等功能.优点是:简小,精悍.注意GLUT并不是一个功能特别全面的窗口系统工具包,所以构建

Linux OpenGL 实践篇-12-ProceduralTexturing

程序式纹理 简单的来说程序式纹理就是用数学公式描述物体表面的纹路 .而实现这个过程的着色器我们称之为程序纹理着色器,通常在这类着色器中我们能使用的输入信息也就是顶点坐标和纹理坐标. 程序式纹理的优点 1.程序式纹理的内存占用比预存纹理要低的多:因为程序式纹理主要是算法的实现,数据都是通过计算产生的: 2.程序生成的纹理没有固定的面积和分辨率,可以随意的应用到不同大小的物体,而不用担心精度不够的问题: 3.程序式纹理可以写入一些算法的关键参数,可以方便的供程序修改从而创建出有趣的效果,而预存的纹理

Linux OpenGL 实践篇-14-多实例渲染

多实例渲染 OpenGL的多实例渲染是一种连续执行多条相同的渲染命令的方法,并且每条命令产生的结果都有轻微的差异,通常用于渲染大量的几何物体. 设想一个场景,比如太空,我们需要渲染数以万记的星球,如果我们使用常规的做法,渲染的过程应该是是:绘制第一个星球glBindVertexArray--glDrawArrays或glDrawElements,然后使用同样的流程绘制其它的星球.但这种方式非常容易达到计算机的性能瓶颈,就算是渲染的物体是最简单的面片,因为在绘制的整个过程中,绘制物体的时间其实非常

linux总线设备驱动模型

本篇文章通过平台总线设备模型为例,讲解总线设备驱动模型: platform_device_register的作用: 1.把device放入bus的device链表 2.从bus的driver链表中取出每一个driver,用bus的match函数判断driver能否支持这个device 3.若可以支持,调用driver的probe函数 platform_driver_register的作用: 1.将driver放入bus的driver链表 2.从bus的device链表中取出每一个device,用

Linux网络I/O模型简介

一.I/O基础入门 java1.4之前的早期版本,java对I/O的支持并不完善,开发人员在开发高性能I/O程序的时候,会面临一些巨大的挑战和困难,主要问题如下. 1.没用数据缓冲区,I/O性能存在问题: 2.没有C或者C++中的Channel概念,只有输入和输出流: 3.同步阻塞式I/O通信(BIO),通常会导致通信线程被长时间阻塞: 4.支持字符集有限,硬件可移植性不好. 二.Linux网络I/O模型简介 Linux的内核讲所有外部设备都看做一个文件来操作,对一个文件的读写操作会调用内核提供

Linux五种IO模型

Linux五种IO模型 转载:http://blog.csdn.net/jay900323/article/details/18141217 Linux五种IO模型性能分析 目录(?)[-] 概念理解 Linux下的五种IO模型 阻塞IO模型 非阻塞IO模型 IO复用模型 信号驱动IO 异步IO模型 个IO模型的比较 selectpollepoll简介 1. 概念理解 在进行网络编程时,我们常常见到同步(Sync)/异步(Async),阻塞(Block)/非阻塞(Unblock)四种调用方式: