Working with FBX SDK (1)

http://www.cnblogs.com/clayman/archive/2010/12/10/1901779.html


Working with FBX SDK (1)

仅供个人学习使用,请勿转载,勿用于任何商业用途

作者:clayman

 

更新2012.5:  *****fbx sdk 2013以后的版本做了大幅更新,大量API都进行了修改和更名,请参考新版SDK文档******
 

      模型导入是所有3D程序最基本的功能,但常常也是让很多新手最头疼的问题之一。DirectX虽然提供了直接加载.x文件的功能,不幸的是多年以来,很少有主流建模软件提供了对它的直接支持,各种各样的格式转换程序之间又多多少有些小bug存在,加上近年来ms也逐渐不再使用.x文件,因此,为了将来程序开发更加灵活方便,任何稍有规模的程序都必须重新发明轮子,自己实现模型导入。


         当选择支持什么类型的模型文件时,最重要的因素就是交换性----即这种格式是否能被大多数三维软件支持,是否能方便的和其他格式转换;有良好定义以及可扩展性。在各种模型文件中,目前最能满足这三个条件的就是fbx和Collada,本文主要讨论前者。需要说明的是,无论选择什么格式,这些格式都不应该是图形引擎直接读取的格式。虽然我们也可以这么做,但无论fbx,collada或者其他很多格式都是以数据交换为目的而设计的,比如collada本质就是xml文件,因此不适合游戏引擎这类对性能有较高要求的程序。理想的解决方案是把这些格式作为数据来源,通过预处理转换为为特定引擎设计的格式,最后引擎直接读取特有的自定义格式。如果熟悉XNA的话,XNA中的content pipeline就是完成了这样的工作,把模型,纹理转换为特殊设计的xnb格式,加速运行时的读取速度。引擎只需要有读取一种文件的能力即可,而另外有一些列的importer/converter可以把其他格式的文件在预处理阶段,转换为引擎可识别的格式。

 

         如何设计适合自己引擎的文件格式超了本文讨论范围,不过这里举一个小小的例子,说明自定义格式的必要性。以fbx文件为例,假设用记事本打开一个只包含一个mesh模型文件,可以看到数据大概是按以下方式组织的:

vertex  { position data…..}

normal  {normal data……}

UV  {UV data……}

 

         引擎如果直接读取这样的文件,需要在运行时把数据重新解析组织为硬件可以直接使用的格式:从不同位置抽出position,normal,uv数据合成顶点,再把顶点组织为数组,最后放入vertex buffer中。而如果自定义文件话,可以直接就把数据以vertex array的格式保存,比如:

vertexData{ (pos,nol,uv),(pos,nol,uv),……}

 这样在读取文件之后,可以直接把数据放入vertex buffer,效率自然是前者不可比的。当然,实际的文件不仅包含顶点数据,还会有很多其他内容。

 

         说了那么多,现在回到正题。本文不会,也不可能详细讨论解析fbx的所有数据,只重点讨论如果解析出游戏引擎最常用到的信息:如何访问mesh,读取相应的顶点,材质以及模型结构(Hierarchy)信息。

         先介绍一点关于fbx的基本知识,fbx是Autodesk开发的文件格式,其开发目的就是为了实现Autodesk旗下软件之间的数据交换。鉴于Autodesk已经把主流建模软件公司买的差不多了(maya,3ds max,softimage,motionbuilder…..),几乎所有主流三维建模软件都能导出\导入fbx文件,Autodesk也提供了的专门的软件fbx convert可以把其他流行格式(包括collada)转换为fbx文件。Fbx文件格式本身是不公开的,而是通过FBX SDK实现对fbx文件的读取以及写入,这也是我选择fbx的一个重要原因,作为开发者可以不必关心实际的数据储存细节(用记事本打开ascii码的fbx文件,还是能大概了解实际的数据格式),把文件看做一个数据源对象,通过特定函数就能访问数据源中的特定数据。而稍后我们就会看到,fbx sdk设计的也非常易用。

 

         我们要做的第一步就是从autodesk网站下载FBX SDK(需要先填写一个简单的表格才能下载,嗯嗯,可以乱填),最新版本是2011.3,windows下的安装包大约有450m。安装之后,需要在工程里进行一些简单的设置才能使用。对于visual Studio来说,请**仔细**按照文档Downloading and installing部分的介绍进行配置,除17以外,其他都是必须的,特别注意在16步时,选择正确的lib文件。特别提醒,虽然2011.3包含了的vs2010下的lib,但是有重大bug,会在导入某些fbx文件时,出现” debug assertion failed”错误(坑爹啊,浪费了我两天),推荐在vs2005/2008下开发。

 

          接下来,就可以动手写代码了。使用fbx sdk时,最先遇到的两个对象就是KFbxSdkManage和KFbxScene。Fbx sdk中大部分类的命名都以KFbx开头(为什么是k呢….?)。KFbxSdkManage是sdk中的中心类,负责了整个sdk内部状态的管理,很多其他对象创建也依赖于KFbxSdkManage,程序中只需要有一个KFbxSdkManage类的实例即可。KFbxScene如其名所示,代表了一个场景,而这里的场景就是fbx文件中包含的所有信息,fbx文件导入以后,在程序中就是一个KFbxScene对象。可以用以下代码完成这两个对象的创建。

复制代码
Init
init sdk 
KFbxScene 
* scene;
KFbxSdkManager 
* sdkManager;

void  FbxImporter::Init()
{
    sdkManager 
=  KFbxSdkManager::Create();
    KFbxIOSettings
*  ios  =  KFbxIOSettings::Create(sdkManager,IOSROOT);
    sdkManager
-> SetIOSettings(ios);
    scene 
=  KFbxScene::Create(sdkManager, "" );
}

复制代码

 

        注意,示例代码省略了必要的错误检查。上面代码中出现了KFbxIOSettings类,这是一个用来配置KFbxSdkManage的对象,可以通过这个对象设置一些导入导出时的行为,比如可以选择不导入材质,动画等等。有了这两个对象之后,下一步就可以导入fbx文件了,这需要用到KFbxImporter对象,他会自动解析fbx文件中的数据,并保存到KFbxScene对象中。实际上除fbx以外KFbxImporter还能导入一些其他格式的文件。实例代码如下:

复制代码
Load file
void  FbxImporter::LoadScene( const   char *  fileName)
{
    KFbxImporter
*  sceneImporter  =  KFbxImporter::Create( this -> sdkManager, "" );
    sceneImporter
-> Initialize(fileName, - 1 , this -> sdkManager -> GetIOSettings());
    sceneImporter
-> Import(scene);
    sceneImporter
-> Destroy();
}
复制代码

 

         文件加载之后,接下来就是用相应的方法,找出我们需要的数据。这里要稍微补充一点fbx组织数据的方式。前面说过,当用sdk来处理fbx文件时,它更像是一个数据源或者说一个对象,所以你应该以对象的方式来看待fbx,而不是文件的角度。如果你对scene graph/tree有所了解的话,fbx其实就是一个scene graph/tree!KFbxScene是根节点,包含了一系列子节点KFbxNode,每个KFbxNode又有其自己的子节点。KFbxNode包含了坐标变换信息,可以通过一系列get函数取得,其他数据作为KFbxNodeAttribute对象,包含在KFbxNode内部,这里的其他数据是指mesh,Nurbs,skeletion,camara,light等定义在KFbxNodeAttribute::EAttributeType中的类型。一个KFbxNode可以有多个子KFbxNode,但只能有一个KFbxNodeAttribute对象,可以通过KFbxNodeAttribute的GetAttributeType()方法,确定当前node的所包含的实际数据类型:

更正:又仔细看了文档,KFbxNode可以有多个KFbxNodeAttribute对象,GetNodeAttribute()返回默认的attribute对象。

visit node

 
         说到这里,我们已经解决了第一个问题:获得场景结构信息。所有KFbxNode构成的树就是场景结构。而其中KFbxNodeAttribute为skeletion的节点组成的树,可能就是某个模型的骨骼。下图是解析两个不同文件得到的节点关系:

 

 

        根据模型师建模习惯的不同,导出节点顺序是不一样的,比如上面的文件把骨骼单独作为一个树,下面的文件则用了一种混排的方式,一个node下同时有子骨骼节点和mesh节点。  接下来,看如何读出顶点信息,注意下面仅以mesh为例,介绍一些常见操作。首先,用以下代码获得一个node中所包含的mesh数据:

复制代码
mesh info
void  ProcessMesh(KFbxNodeAttribute *  nodeAtt)
{
   
if (nodeAtt -> GetAttributeType()  ==  KFbxNodeAttribute::eMESH)
   {
    KFbxMesh 
* mesh  =  dynamic_cast < KFbxMesh *> (nodeAtt);
    
if ( ! mesh -> IsTriangleMesh())
    {
        KFbxGeometryConverter converter(sdkManager);
        
//  #1
        converter.TriangulateInPlace(fbxNode);
        mesh 
=  dynamic_cast < KFbxMesh *> (fbxNode -> GetNodeAttribute());
        
//  #2
        
// mesh = converter.TriangulateMesh(mesh);
    }
        
    std::cout
<< “TriangleCount: "  <<mesh->GetPolygonCount()
         << "   VertexCount: " << mesh -> GetControlPointsCount()
        
<< "   IndexCount: " << mesh -> GetPolygonVertexCount()
        
<< "    Layer: " << mesh -> GetLayerCount()
        
<< "   DeformerCount: " << mesh -> GetDeformerCount(KFbxDeformer::eSKIN)
        
<< "   MaterialCount: " <<  fbxNode -> GetMaterialCount();
   }     
}
复制代码

 

           Fbx文件中包含的mesh不一定是由三角形组成,还可能是四边形,五边形等等,因此,要做的第一步,就是三角化mesh,可以用以上两种方法实现。TriangulateMesh和TriangulateInPlace区别在于前者返回一个三角化之后的新mesh,后者则是对当前数据进行三角化。注意TriangulateInPlace之后需要重新获取mesh指针,否则代码会出错。Mesh类的大部分成员函数用途都一目了然,只是有一些概念需要注意:

1. GetPolygonCount() 返回三角形数量;

2. GetControlPointsCount() 返回控点数量,这里控点的概念和DirectX中常说的顶点非常类似,但不完全一样,更像是只包含了position的顶点。也就是说如果这个顶点被n个多边形共享(比如立方体八个角的点),而在每个多边形上又有不同的纹理坐标或者法线,那么稍后将分裂或者说生成n个包含position,normal,uvs等信息的顶点;

3. GetControlPoints () 返回控点数组指针;

4. GetPolygonVertexCount() 这是个迷惑人的名字,这个函数返回的其实是大家熟悉的vertex index count,对triange list来说,其实就是GetPolygonCount() * 3;

5. GetPolygonVertices() 返回索引数组指针;

 

      下面的代码演示了如何把从fbx文件中读取的顶点,索引数据保存到一个非常简单的文件中:

save data

 

    下面的XNA代码演示了从刚才保存的文件中读出数据并渲染:

render model

 

     目前我们已经从fbx文件中导出了最基本的信息,下次继续讨论如何获取noraml,uv,material等信息.........

 


你可能感兴趣的:(Working with FBX SDK (1))