现代opengl 设计 assimp 3D 模型加载库

本文介绍assimp 库的下载和编译,为我们用opengl 显示复杂3D 做准备。 前面模型库说明部分引用 https://learnopengl-cn.github.io/ 。

到目前为止的所有场景中,我们一直都在滥用我们的箱子朋友,但时间久了甚至是我们最好的朋友也会感到无聊。在日常的图形程序中,通常都会使用非常复杂且好玩的模型,它们比静态的箱子要好看多了。然而,和箱子对象不同,我们不太能够对像是房子、汽车或者人形角色这样的复杂形状手工定义所有的顶点、法线和纹理坐标。我们想要的是将这些模型(Model)导入(Import)到程序当中。模型通常都由3D艺术家在Blender、3DS Max或者Maya这样的工具中精心制作。

这些所谓的3D建模工具(3D Modeling Tool)可以让艺术家创建复杂的形状,并使用一种叫做UV映射(uv-mapping)的手段来应用贴图。这些工具将会在导出到模型文件的时候自动生成所有的顶点坐标、顶点法线以及纹理坐标。这样子艺术家们即使不了解图形技术细节的情况下,也能拥有一套强大的工具来构建高品质的模型了。所有的技术细节都隐藏在了导出的模型文件中。但是,作为图形开发者,我们就必须要了解这些技术细节了。

所以,我们的工作就是解析这些导出的模型文件以及提取所有有用的信息,将它们储存为OpenGL能够理解的格式。一个很常见的问题是,模型的文件格式有很多种,每一种都会以它们自己的方式来导出模型数据。像是Wavefront的.obj这样的模型格式,只包含了模型数据以及材质信息,像是模型颜色和漫反射/镜面光贴图。而以XML为基础的Collada文件格式则非常的丰富,包含模型、光照、多种材质、动画数据、摄像机、完整的场景信息等等。Wavefront的.obj格式通常被认为是一个易于解析的模型格式。建议至少去Wavefront的wiki页面上看看文件格式的信息是如何封装的。这应该能让你认识到模型文件的基本结构。

总而言之,不同种类的文件格式有很多,它们之间通常并没有一个通用的结构。所以如果我们想从这些文件格式中导入模型的话,我们必须要去自己对每一种需要导入的文件格式写一个导入器。很幸运的是,正好有一个库专门处理这个问题。

模型加载库

一个非常流行的模型导入库是Assimp,它是Open Asset Import Library(开放的资源导入库)的缩写。Assimp能够导入很多种不同的模型文件格式(并也能够导出部分的格式),它会将所有的模型数据加载至Assimp的通用数据结构中。当Assimp加载完模型之后,我们就能够从Assimp的数据结构中提取我们所需的所有数据了。由于Assimp的数据结构保持不变,不论导入的是什么种类的文件格式,它都能够将我们从这些不同的文件格式中抽象出来,用同一种方式访问我们需要的数据。

当使用Assimp导入一个模型的时候,它通常会将整个模型加载进一个场景(Scene)对象,它会包含导入的模型/场景中的所有数据。Assimp会将场景载入为一系列的节点(Node),每个节点包含了场景对象中所储存数据的索引,每个节点都可以有任意数量的子节点。Assimp数据结构的(简化)模型如下

现代opengl 设计 assimp 3D 模型加载库_第1张图片

 

  • 和材质和网格(Mesh)一样,所有的场景/模型数据都包含在Scene对象中。Scene对象也包含了场景根节点的引用。
  • 场景的Root node(根节点)可能包含子节点(和其它的节点一样),它会有一系列指向场景对象中mMeshes数组中储存的网格数据的索引。Scene下的mMeshes数组储存了真正的Mesh对象,节点中的mMeshes数组保存的只是场景中网格数组的索引。
  • 一个Mesh对象本身包含了渲染所需要的所有相关数据,像是顶点位置、法向量、纹理坐标、面(Face)和物体的材质。
  • 一个网格包含了多个面。Face代表的是物体的渲染图元(Primitive)(三角形、方形、点)。一个面包含了组成图元的顶点的索引。由于顶点和索引是分开的,使用一个索引缓冲来渲染是非常简单的(见你好,三角形)。
  • 最后,一个网格也包含了一个Material对象,它包含了一些函数能让我们获取物体的材质属性,比如说颜色和纹理贴图(比如漫反射和镜面光贴图)。

所以,我们需要做的第一件事是将一个物体加载到Scene对象中,遍历节点,获取对应的Mesh对象(我们需要递归搜索每个节点的子节点),并处理每个Mesh对象来获取顶点数据、索引以及它的材质属性。最终的结果是一系列的网格数据,我们会将它们包含在一个Model对象中。

构建Assimp

1: 下载assimp源代码

可以到  http://assimp.org/index.php/downloads 选择你要下载的版本,我选择目前最新的,指引我到  https://github.com/assimp/assimp/releases/tag/v4.1.0/

2:安装boost

assimp是依赖boost库的,如果没有boost库只能编译出一个功能受限的版本。

boost官方链接:www.boost.org

我的系统上visual studio 2010,2017 ,那么我该用哪个版本的boost , 都可以,我就下载现在最新发行版 1.69。

assimp没有用到boost需要编译的部分,所以boost只需要下载解压就可以了。

3:assimp view依赖directx sdk

如果需要编译assimp view,需要先安装directx sdk。

你可以从这里下载SDK。

点击exe文件进行安装,安装后,如果不重新启动至少需要注销后,directx sdk的环境配置才会起作用。

4:安装cmake

cmake官方链接:www.cmake.org

运行cmake

 选择assimp源码目录,编译二进制文件的目录可以先建立好,也可以这时输入,我是复制源码目录,后面添加bin或bin7。然后点Add Entry,添加BOOST_ROOT变量,type选STRING,value添加boost的目录(此步骤也可以不做,编译出的assimp可能是功能受限的)。添加后效果如下:

现代opengl 设计 assimp 3D 模型加载库_第2张图片

点Config ,第一次点的时候会问你选择哪个版本的编译器。

再点一次Config, 然点Generate,就产生好了工程。然后Open project, 编译链接(build)。

这需要些时间,对我而言, visual studio 2010总是编译结果为:

========== Rebuild All: 5 succeeded, 4 failed, 6 skipped ==========

也就是不成功。

但是visual studio 2017 却是成功的。既然有个成功的就用了再说。

5: assimp 的使用

  • 使用默认配置构建的Assimp是一个动态库(Dynamic Library),所以我们需要包含所生成的assimp.dll文件以及程序的二进制文件。你可以简单地将DLL复制到我们程序可执行文件的同一目录中。
  • Assimp编译之后,生成的lib库和DLL文件位于code/Debug或者code/Release文件夹中。
  • 接着把编译好的LIB文件和DLL文件拷贝到工程的相应目录下,并在解决方案中链接它们。并且记得把Assimp的头文件也复制到你的include目录中(头文件可以在从Assimp中下载的include目录里找到)。我是把解压后原始的include 目录下的assimp 目录复制到我的include 目录下,同时把编译二进制文件的目录的include 目录下的assimp 目录里的config.h 也复制过去。

做好了这些准备,就可以用assimp 库导入各种3D文件了。

6:c++应用简单例子

建立一个 Assimp::Importer 类的实例,或者做些设置,然后调用l Assimp::Importer::ReadFile(). 就能读取文件并处理数据,处理好的数据放在 aiScene 类指针的对象里。你可以提取需要的数据。importer 管理本身的所有资源。其消失的时候也释放了他所有的资源。所以最简单的办法是本地建立一个实例,利用完其结果后,让他自然超出作用域范围。

C++ 例子如下:

#include       // C++ importer interface
#include            // Output data structure
#include      // Post processing flags
bool DoTheImportThing( const std::string& pFile)
{
  // Create an instance of the Importer class
  Assimp::Importer importer;
  // And have it read the given file with some example postprocessing
  // Usually - if speed is not the most important aspect for you - you'll 
  // propably to request more postprocessing than we do in this example.
  const aiScene* scene = importer.ReadFile( pFile, 
        aiProcess_CalcTangentSpace       | 
        aiProcess_Triangulate            |
        aiProcess_JoinIdenticalVertices  |
        aiProcess_SortByPType);
  
  // If the import failed, report it
  if( !scene)
  {
    DoTheErrorLogging( importer.GetErrorString());
    return false;
  }
  // Now we can access the file's contents. 
  DoTheSceneProcessing( scene);
  // We're done. Everything will be cleaned up by the importer destructor
  return true;
}

 

你可能感兴趣的:(opengl,c++)