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

  本次实践参考: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(,path.find_last_of('/')); ProcessNode(scene->mRootNode,scene);
}
void Model::ProcessNode(aiNode *node, const aiScene *scene)
{
//处理节点所有网格
for(unsigned int i = ; i < node->mNumMeshes; i++)
{
aiMesh *mesh = scene->mMeshes[node->mMeshes[i]];
meshes.push_back(ProcessMesh(mesh,scene));
} //接下来对它的节点重复这一过程
for(unsigned int i = ; 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 = ; 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[])
{
Vector2 vec;
vec.x = mesh->mTextureCoords[][i].x;
vec.y = mesh->mTextureCoords[][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=;i < mesh->mNumFaces;i++)
{
aiFace face = mesh->mFaces[i];
for(unsigned int j = ; 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(,&VAO);
glGenBuffers(,&VBO);
glGenBuffers(,&EBO); glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER,VBO); glBufferData(GL_ARRAY_BUFFER,this->vertices.size() * sizeof(Vertex), &vertices[],GL_STATIC_DRAW); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER,indices.size() * sizeof(unsigned int), &indices[],GL_STATIC_DRAW); //顶点位置
glEnableVertexAttribArray();
glVertexAttribPointer(,,GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*));
//顶点法线
glEnableVertexAttribArray();
glVertexAttribPointer(,,GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*)(offsetof(Vertex,normal)));
//顶点纹理坐标
glEnableVertexAttribArray();
glVertexAttribPointer(,,GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*)(offsetof(Vertex,texcoords))); glBindVertexArray();
}

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

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

void Mesh::Draw(std::shared_ptr<Shader> shader)
{
unsigned int diffuseNr = ;
unsigned int specularNr = ; for(unsigned int i = ; 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,);
glBindVertexArray(); glActiveTexture(GL_TEXTURE0);
}

  最终效果:

  

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

  1. Linux OpenGL 实践篇-6 光照

    经典光照模型 经典光照模型通过单独计算光源成分得到综合光照效果,然后添加到物体表面特定点,这些成分包括:环境光.漫反射光.镜面光. 环境光:是指不是来特定方向的光,在经典光照模型中基本是个常量. 漫反 ...

  2. Linux OpenGL 实践篇-5 纹理

    纹理 在之前的实践中,我们所渲染的物体的表面颜色都是纯色或者根据顶点位置计算出的一个颜色,这种方式在表现物体细节方面是比较吃资源的,因为我们每增加一个细节,我们就需要定义更多的顶点及其属性.所以美术人 ...

  3. Linux OpenGL 实践篇-4 坐标系统

    OpenGL中顶点经过顶点着色器后会变为标准设备坐标系.标准设备坐标系的各坐标的取值范围是[-1,1],超过这个范围的点将会被剔除.而这个变换的过程可描述为顶点在几个坐标系统的变换,这几个坐标系统为: ...

  4. Linux OpenGL 实践篇-11-shadow

    OpenGL 阴影 在三维场景中,为了使场景看起来更加的真实,通常需要为其添加阴影,OpenGL可以使用很多种技术实现阴影,其中有一种非常经典的实现是使用一种叫阴影贴图的实现,在本节中我们将使用阴影贴 ...

  5. Linux OpenGL 实践篇-3 绘制三角形

    本次实践是绘制两个三角形,重点理解顶点数组对象和OpenGL缓存的使用. 顶点数组对象 顶点数组对象负责管理一组顶点属性,顶点属性包括位置.法线.纹理坐标等. OpenGL缓存 OpenGL缓存实质上 ...

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

    OpenGL 作为一个图形接口,并没有包含窗口的相关内容,但OpenGL使用必须依赖窗口,即必须在窗口中绘制.这就要求我们必须了解一种窗口系统,但不同的操作系统提供的创建窗口的API都不相同,如果我们 ...

  7. Linux OpenGL 实践篇-1 OpenGL环境搭建

    本次实践所使用环境为CentOS 7. 参考:http://www.xuebuyuan.com/1472808.html OpenGL开发环境搭建: 1.opengl库安装 opengl库使用mesa ...

  8. Linux OpenGL 实践篇-16 文本绘制

    文本绘制 本文主要射击Freetype的入门理解和在OpenGL中实现文字的渲染. freetype freetype的官网,本文大部分内容参考https://www.freetype.org/fre ...

  9. Linux OpenGL 实践篇-15-图像数据操作

    OpenGL图像数据操作 之前的实践中,我们在着色器中的输入输出都是比较固定的.比如在顶点或片元着色器中,顶点属性的输入和帧缓存的颜色值:虽然我们可以通过纹理或者纹理缓存对象(TBO)来读取任意的内存 ...

随机推荐

  1. maven(一)创建一个maven的web项目

    一.创建项目 1.Eclipse中用Maven创建项目 上图中Next 2.继续Next 3.选maven-archetype-webapp后,next 4.填写相应的信息,Packaged是默认创建 ...

  2. .NETFramework:Encoding

    ylbtech-.NETFramework:Encoding 1.返回顶部 1. #region 程序集 mscorlib, Version=4.0.0.0, Culture=neutral, Pub ...

  3. 让ie支持css3的一些htc文件

    1. Dean Edwards的IE7.js (以及 IE8.js, IE9.js)这个玩意估计是试图让IE支持CSS3属性的鼻祖,还算蛮强大,就是性能开销较大,要解析很多文件脚本,给DOM添加大量的 ...

  4. auto_ptr智能指针

    C++的auto_ptr所做的事情,就是动态分配对象以及当对象不再需要时自动执行清理. 使用std::auto_ptr,要#include <memory>.

  5. 交通规划_dijkstra

    问题描述 G国国王来中国参观后,被中国的高速铁路深深的震撼,决定为自己的国家也建设一个高速铁路系统. 建设高速铁路投入非常大,为了节约建设成本,G国国王决定不新建铁路,而是将已有的铁路改造成高速铁路. ...

  6. js、匿名函数、闭包、回调函数

    234567891011121314151617181920212223242526272829303132333435 闭包 闭包:闭包是指有权访问另一个函数作用域中的变量的函数 函数嵌套一个函数, ...

  7. GCC在C语言中内嵌汇编 asm __volatile__

    2012-11-26 22:20 17958人阅读 评论(2) 收藏 举报  分类: linux(59)  架构管理(24)  C/C++(59)  目录(?)[+] 在内嵌汇编中,可以将C语言表达式 ...

  8. 704. Binary Search

    Given a sorted (in ascending order) integer array nums of n elements and a target value, write a fun ...

  9. CSA Round #50 (Div. 2 only) Min Swaps(模拟)

    传送门 题意 给出一个排列,定义\(value为\sum_{i=1}^{n-1}abs(f[i+1]-f[i])\) \(swap(a[i],a[j])(i≠j)为一次交换\),询问最少的交换次数使得 ...

  10. hdoj5387【模拟】

    题意: 略: 思路: 把所有的角度按照分母的形式写,中间不要约,不要除...(然后我就wa了),本来是想保证结果的正确性,最后会造成约好以后分子很大..>360°: /* 这个案例不错,妈的,随 ...