http://blog.csdn.net/bugrunner/article/details/7210511
1. 簡介
FBX是Autodesk的一個用於跨平台的免費三維數據交換的格式(最早不是由Autodesk開發,但后來被其收購),目前被 眾多的標准建模軟件所支持,在游戲開發領域也常用來作為各種建模工具的標准導出格式。Autodesk提供了基於C++(還有Python)的SDK來實現對FBX格式的各種讀寫、修改以及轉換等操作,之所以如此是因為FBX的格式不是公開的,這也是FBX的詬病之一。與FBX相對的則是格式開源的Collada,它的應用也很廣泛。總體來說這兩種格式還是各有優劣不相上下,關於兩種格式在游戲開發中使用的對比與討論也比較多,可見GameDev中的帖子:http://www.gamedev.net/topic/467753-collada-vs-autodesk-fbx , 這里就不再論述了。大多數情況下我們是需要解析模型在程序中渲染使用,因此這里主要討論使用FBX SDK來對FBX模型進行解析與加載(主要包括幾何網格、材質、Light與Camera、Skeleton動畫等),而對於在建模工具中可能涉及到的FBX寫出等則沒有涉及。
2. FBX SDK的配置
首先,在使用前需要下載安裝FBX的SDK,可以從Autodesk的網站上進行獲得最新的版本http://usa.autodesk.com/adsk/servlet/index?siteID=123112&id=7478532(需要填些基本信息注冊下)。安裝之后在VS里邊的配置就跟D3D類似。其中的Samples基本上涵蓋了FBX相關的應用,可以在使用之前好好研究一下。最新的SDK版本(2012版)與之前的版本會在細節上有所不同(有些較大的改動是實現某些功能 的API接口的修改,具體細節可以用2012的Programmer's guide中找到),而且支持最新的FBX格式,因此最好就直接用最新的版本。
3. FBX模型的組織結構
FBX是以scene graph的結構來存儲模型的所有信息(也可以認為是一個多叉樹),類似於OSG中的組織方式,這一點可以從SDK所帶的Sample里邊很清楚的看出來。一個較為典型的模型的組織結構與下圖所示:
整個Scene是從一個空屬性的根結點開始,其中每個結點均是一個KFbxNode的對象,所有對象之間的關聯均是雙向的,比如從子結點可以索引到父結點,從父結點也可以索引到子結點;從單個結點可以索引到整個Scene,從Scene也可以索引到該結點。每個結點都會有一個標記屬性的Enum值,比如eMesh、eLight、eCamera或eSkeleton等,分別用來標記當前結點是Mesh、Light、Camera或Skeleton。在整個結構的遍歷過程中可以通過判斷不同的結點屬性而進行不同的處理操作。
在進行使用SDK進行FBX的處理操作之前需要先初始化兩個必須的FBX對象:KFbxSdkManager和KFbxScene。前者用來對所有的FBX對象進行內在管理,所有使用SDK加載的資源均在此對象的管控之下,最終的資源釋放也由其來完成。有了內存管理器之后再在其上創建一個相關的KFbxScene對象之后即可以進行模型的加截與處理了。KFbxScene其實相當於Manager提供的整個場景對象的一個入口。兩個對象的初始化與配置代碼如下所述:
初始化SDKManager
- bool FBXImporter::Initialize()
- {
- // Create the FBX SDK Manager, destroy the old manager at first
- if(mpFBXSDKManager)
- {
- mpFBXSDKManager->Destroy();
- }
- mpFBXSDKManager = KFbxSdkManager::Create();
- if(mpFBXSDKManager == NULL)
- {
- return false;
- }
- // Create an IOSettings object
- KFbxIOSettings* ios = KFbxIOSettings::Create(mpFBXSDKManager , IOSROOT);
- mpFBXSDKManager->SetIOSettings(ios);
- // Load plug-ins from the executable directory
- KString lExtension = "dll";
- KString lPath = KFbxGetApplicationDirectory();
- mpFBXSDKManager->LoadPluginsDirectory(lPath.Buffer() , lExtension.Buffer());
- // Create the entity that hold the whole Scene
- mpFBXSDKScene = KFbxScene::Create(mpFBXSDKManager , "");
- return true;
- }
FbxScene的初始化
- bool FBXImporter::LoadScene(const char* pSeneName)
- {
- if(mpFBXSDKManager == NULL)
- {
- return false;
- }
- // Get the file version number generate by the FBX SDK.
- KFbxSdkManager::GetFileFormatVersion(mSDKVersion.mMajor , mSDKVersion.mMinor , mSDKVersion.mRevision);
- // Create an importer.
- KFbxImporter* pKFBXImporter = KFbxImporter::Create(mpFBXSDKManager , "");
- // Initialize the importer by providing a filename
- FBXFileVersion fileVersion;
- bool importStatus = pKFBXImporter->Initialize(fileName , -1 , mpFBXSDKManager->GetIOSettings());
- lImporter->GetFileVersion(fileVersion.mMajor , fileVersion.mMinor , fileVersion.mRevision);
- if(!importStatus)
- {
- return false;
- }
- // Import the scene
- mpFBXScene->Clear();
- importStatus = pKFBXImporter->Import(m_mpFBXScene);
- // Destroy the importer.
- pKFBXImporter->Destroy();
- return importStatus;
- }
在完成了對KFbxScene的初始化操作之后即可以從其中得到整個模型的所有信息。由於FBX的是采用了類似於樹的結構來進行存儲,因而就很容易使用類似於樹的遞歸方法來遍歷其中的每個結點,並根據結點的屬性選擇合適的處理操作,下述代碼就完成了從根結點開始的全局遍歷:
- void ProcessNode(KFbxNode* pNode)
- {
- KFbxNodeAttribute::EAttributeType attributeType;
- if(pNode->GetNodeAttribute())
- {
- switch(pNode->GetNodeAttribute()->GetAttributeType())
- {
- case KFbxNodeAttribute::eMESH:
- ProcessMesh(pNode);
- break;
- case KFbxNodeAttribute::eSKELETON:
- ProcessSkeleton(pNode);
- break;
- case KFbxNodeAttribute::eLIGHT:
- ProcessLight(pNode);
- break;
- case KFbxNodeAttribute::eCAMERA:
- ProcessCamera();
- break;
- }
- }
- for(int i = 0 ; i < pNode->GetChildCount() ; ++i)
- {
- ProcessNode(pNode->GetChild(i));
- }
- }
上述代碼比較簡單,直接傳入由KFbxScene中獲得的根結點之后即可遍歷到每一個子結點。在FBX的存儲中,每個父結點可以包含多個子結點,但每個子結點只有一個根結點,而且這其中的聯系是雙向的,這樣很方便,比如在處理Skeleton時就常常需要從子結點中得到父結點的matrix等信息,而這種雙向關系使得這些操作很容易實現。注意,上述代碼中有pNode->GetNodeAttribute()檢查操作是必須的,因為並不是所有的結點都有相應的屬性(Attribute也是以子結點的方式關聯到當前的結點上的,因而可能為空)。
4. 加載幾何網格
FBX對幾何網格支持得還是很好的,Nurbes、Polygon、Triangle等均可以存儲。不過為了簡化加載和處理時的操作,最好直接在FBX導出插件中選擇一種統一的模式。比如可以在導出生成FBX時選中Triangluation的屬性,那么FBX導出插件會自動把所有的Nurbes、Polygon三角化為三角形進行存儲。當然,這個過程也可以在模型進行加載時來進行。這樣在得到的FBX中就只有三角形這樣一種網格模型,方便了加載的操作。模型的幾何數據主要包括以下部分:
- Vertex 組成網格的頂點信息,這一部分是必須的。
- Color 每個頂點的顏色。
- Normal 每個頂點所對應的法向,是由FBX導出插件計算生成,可以是逐面片或逐頂點。
- Tangent 每個頂點所對應的切向,是由FBX導出插件計算生成,可以是逐面片或逐頂點。
- UV每個頂點所對應的貼圖UV值,一般來說,每個UV對應一個Layer,一個頂點可以有多個UV通道,這在讀入的時間需要進行判斷
幾何網格的加載比較簡單,直接遞歸地從根結點來遍歷整個graph,檢測當前的結點是否為eMESH的屬性,若是即處理其中的幾何數據,主要代碼如下所示:
- void ProcessMesh(KFbxNode* pNode)
- {
- KFbxMesh* pMesh = pNode->GetMesh();
- if(pMesh == NULL)
- {
- return;
- }
- D3DXVECTOR3 vertex[3];
- D3DXVECTOR4 color[3];
- D3DXVECTOR3 normal[3];
- D3DXVECTOR3 tangent[3];
- D3DXVECTOR2 uv[3][2];
- int triangleCount = pMesh->GetPolygonCount();
- int vertexCounter = 0;
- for(int i = 0 ; i < triangleCount ; ++i)
- {
- for(int j = 0 ; j < 3 ; j++)
- {
- int ctrlPointIndex = pMesh->GetPolygonVertex(i , j);
- // Read the vertex
- ReadVertex(pMesh , ctrlPointIndex , &vertex[j]);
- // Read the color of each vertex
- ReadColor(pMesh , ctrlPointIndex , vertexCounter , &color[j]);
- // Read the UV of each vertex
- for(int k = 0 ; k < 2 ; ++k)
- {
- ReadUV(pMesh , ctrlPointIndex , pMesh->GetTextureUVIndex(i, j) , k , &(uv[j][k]));
- }
- // Read the normal of each vertex
- ReadNormal(pMesh , ctrlPointIndex , vertexCounter , &normal[j]);
- // Read the tangent of each vertex
- ReadTangent(pMesh , ctrlPointIndex , vertexCounter , &tangent[j]);
- vertexCounter++;
- }
- // 根據讀入的信息組裝三角形,並以某種方式使用即可,比如存入到列表中、保存到文件等...
- }
- }
上述代碼完成了從一個Node里邊讀出相應的網格信息。首先,從Node里邊得到相應KFbxMesh指針,可知,如果該Node不是eMESH屬性的話那么該指針就為空,后繼操作不能再進行。注意其中用triangleCount變量來存儲pMesh->GetPolygonCount()的值,這主要是在前面也提到過了,假定對於所有的FBX模型在存儲時均選定了Triangulation的操作,因而其中存儲的Polygon是三角形,如此一來每個里邊一定只包含3個頂點,依次讀入這3個頂點所對應的各屬性信息即可。在FBX中對於每個頂點所對應的各種額外屬性,比如Normal、Tangent、UV等均可對應多個通道,這可以通過在每個Mesh里邊增加相應屬性的一個Layer即可實現,在使用FBX SDK寫出FBX文件時很容易做到。比如上述代碼中就從FBX中讀出4個UV通道中的值(第一個是正常的貼圖通道,第二層是LightMap的通道)。vertexCounter是記錄已經處理過的頂點的數目,這主要是頂點信息讀取在某些映射模式下(比如下述使用到vertexCounter的eBY_POLYGON_VERTEX等)需要知道其在全局頂ControlPoints中的信息,因而增加這樣的一個變量來進行記錄。
讀入頂點:
- void ReadVertex(KFbxMesh* pMesh , int ctrlPointIndex , D3DXVECTOR3* pVertex)
- {
- KFbxVector4* pCtrlPoint = pMesh->GetControlPoints();
- pVertex->x = pCtrlPoint[ctrlPointIndex].GetAt(0);
- pVertex->y = pCtrlPoint[ctrlPointIndex].GetAt(1);
- pVertex->z = pCtrlPoint[ctrlPointIndex].GetAt(2);
- }
讀入Color:
- void ReadColor(KFbxMesh* pMesh , int ctrlPointIndex , int vertexCounter , D3DXVECTOR4* pColor)
- {
- if(pMesh->GetElementVertexColorCount < 1)
- {
- return;
- }
- KFbxGeometryElementVertexColor* pVertexColor = pMesh->GetElementVertexColor(0);
- switch(pVertexColor->GetMappingMode())
- {
- case KFbxGeometryElement::eBY_CONTROL_POINT:
- {
- switch(pVertexColor->GetReferenceMode())
- {
- case KFbxGeometryElement::eDIRECT:
- {
- pColor->x = pVertexColor->GetDirectArray().GetAt(ctrlPointIndex).mRed;
- pColor->y = pVertexColor->GetDirectArray().GetAt(ctrlPointIndex).mGreen;
- pColor->z = pVertexColor->GetDirectArray().GetAt(ctrlPointIndex).mBlue;
- pColor->w = pVertexColor->GetDirectArray().GetAt(ctrlPointIndex).mAlpha;
- }
- break;
- case KFbxGeometryElement::eINDEX_TO_DIRECT:
- {
- int id = pVertexColor->GetIndexArray().GetAt(ctrlPointIndex);
- pColor->x = pVertexColor->GetDirectArray().GetAt(id).mRed;
- pColor->y = pVertexColor->GetDirectArray().GetAt(id).mGreen;
- pColor->z = pVertexColor->GetDirectArray().GetAt(id).mBlue;
- pColor->w = pVertexColor->GetDirectArray().GetAt(id).mAlpha;
- }
- break;
- default:
- break;
- }
- }
- break;
- case KFbxGeometryElement::eBY_POLYGON_VERTEX:
- {
- switch (pVertexColor->GetReferenceMode())
- {
- case KFbxGeometryElement::eDIRECT:
- {
- pColor->x = pVertexColor->GetDirectArray().GetAt(vertexCounter).mRed;
- pColor->y = pVertexColor->GetDirectArray().GetAt(vertexCounter).mGreen;
- pColor->z = pVertexColor->GetDirectArray().GetAt(vertexCounter).mBlue;
- pColor->w = pVertexColor->GetDirectArray().GetAt(vertexCounter).mAlpha;
- }
- break;
- case KFbxGeometryElement::eINDEX_TO_DIRECT:
- {
- int id = pVertexColor->GetIndexArray().GetAt(vertexCounter);
- pColor->x = pVertexColor->GetDirectArray().GetAt(id).mRed;
- pColor->y = pVertexColor->GetDirectArray().GetAt(id).mGreen;
- pColor->z = pVertexColor->GetDirectArray().GetAt(id).mBlue;
- pColor->w = pVertexColor->GetDirectArray().GetAt(id).mAlpha;
- }
- break;
- default:
- break;
- }
- }
- break;
- }
- }
讀入UV:
- void ReadUV(KFbxMesh* pMesh , int ctrlPointIndex , int textureUVIndex , int uvLayer , D3DXVECTOR2* pUV)
- {
- if(uvLayer >= 2 || pMesh->GetElementUVCount() <= uvLayer)
- {
- return false;
- }
- KFbxGeometryElementUV* pVertexUV = pMesh->GetElementUV(uvLayer);
- switch(pVertexUV->GetMappingMode())
- {
- case KFbxGeometryElement::eBY_CONTROL_POINT:
- {
- switch(pVertexUV->GetReferenceMode())
- {
- case KFbxGeometryElement::eDIRECT:
- {
- pUV->x = pVertexUV->GetDirectArray().GetAt(ctrlPointIndex).GetAt(0);
- pUV->y = pVertexUV->GetDirectArray().GetAt(ctrlPointIndex).GetAt(1);
- }
- break;
- case KFbxGeometryElement::eINDEX_TO_DIRECT:
- {
- int id = pVertexUV->GetIndexArray().GetAt(ctrlPointIndex);
- pUV->x = pVertexUV->GetDirectArray().GetAt(id).GetAt(0);
- pUV->y = pVertexUV->GetDirectArray().GetAt(id).GetAt(1);
- }
- break;
- default:
- break;
- }
- }
- break;
- case KFbxGeometryElement::eBY_POLYGON_VERTEX:
- {
- switch (pVertexUV->GetReferenceMode())
- {
- case KFbxGeometryElement::eDIRECT:
- case KFbxGeometryElement::eINDEX_TO_DIRECT:
- {
- pUV->x = pVertexUV->GetDirectArray().GetAt(textureUVIndex).GetAt(0);
- pUV->y = pVertexUV->GetDirectArray().GetAt(textureUVIndex).GetAt(1);
- }
- break;
- default:
- break;
- }
- }
- break;
- }
- }
讀入Normal:
- void ReadNormal(KFbxMesh* pMesh , int ctrlPointIndex , int vertexCounter , D3DXVECTOR3* pNormal)
- {
- if(pMesh->GetElementNormalCount() < 1)
- {
- return;
- }
- KFbxGeometryElementNormal* leNormal = pMesh->GetElementNormal(0);
- switch(leNormal->GetMappingMode())
- {
- case KFbxGeometryElement::eBY_CONTROL_POINT:
- {
- switch(leNormal->GetReferenceMode())
- {
- case KFbxGeometryElement::eDIRECT:
- {
- pNormal->x = leNormal->GetDirectArray().GetAt(ctrlPointIndex).GetAt(0);
- pNormal->y = leNormal->GetDirectArray().GetAt(ctrlPointIndex).GetAt(1);
- pNormal->z = leNormal->GetDirectArray().GetAt(ctrlPointIndex).GetAt(2);
- }
- break;
- case KFbxGeometryElement::eINDEX_TO_DIRECT:
- {
- int id = leNormal->GetIndexArray().GetAt(ctrlPointIndex);
- pNormal->x = leNormal->GetDirectArray().GetAt(id).GetAt(0);
- pNormal->y = leNormal->GetDirectArray().GetAt(id).GetAt(1);
- pNormal->z = leNormal->GetDirectArray().GetAt(id).GetAt(2);
- }
- break;
- default:
- break;
- }
- }
- break;
- case KFbxGeometryElement::eBY_POLYGON_VERTEX:
- {
- switch(leNormal->GetReferenceMode())
- {
- case KFbxGeometryElement::eDIRECT:
- {
- pNormal->x = leNormal->GetDirectArray().GetAt(vertexCounter).GetAt(0);
- pNormal->y = leNormal->GetDirectArray().GetAt(vertexCounter).GetAt(1);
- pNormal->z = leNormal->GetDirectArray().GetAt(vertexCounter).GetAt(2);
- }
- break;
- case KFbxGeometryElement::eINDEX_TO_DIRECT:
- {
- int id = leNormal->GetIndexArray().GetAt(vertexCounter);
- pNormal->x = leNormal->GetDirectArray().GetAt(id).GetAt(0);
- pNormal->y = leNormal->GetDirectArray().GetAt(id).GetAt(1);
- pNormal->z = leNormal->GetDirectArray().GetAt(id).GetAt(2);
- }
- break;
- default:
- break;
- }
- }
- break;
- }
- }
讀入Tangent:
- void ReadTangent(KFbxMesh* pMesh , int ctrlPointIndex , int vertecCounter , D3DXVECTOR3* pTangent)
- {
- if(pMesh->GetElementTangentCount() < 1)
- {
- return;
- }
- KFbxGeometryElementTangent* leTangent = pMesh->GetElementTangent(0);
- switch(leTangent->GetMappingMode())
- {
- case KFbxGeometryElement::eBY_CONTROL_POINT:
- {
- switch(leTangent->GetReferenceMode())
- {
- case KFbxGeometryElement::eDIRECT:
- {
- pTangent->x = leTangent->GetDirectArray().GetAt(ctrlPointIndex).GetAt(0);
- pTangent->y = leTangent->GetDirectArray().GetAt(ctrlPointIndex).GetAt(1);
- pTangent->z = leTangent->GetDirectArray().GetAt(ctrlPointIndex).GetAt(2);
- }
- break;
- case KFbxGeometryElement::eINDEX_TO_DIRECT:
- {
- int id = leTangent->GetIndexArray().GetAt(ctrlPointIndex);
- pTangent->x = leTangent->GetDirectArray().GetAt(id).GetAt(0);
- pTangent->y = leTangent->GetDirectArray().GetAt(id).GetAt(1);
- pTangent->z = leTangent->GetDirectArray().GetAt(id).GetAt(2);
- }
- break;
- default:
- break;
- }
- }
- break;
- case KFbxGeometryElement::eBY_POLYGON_VERTEX:
- {
- switch(leTangent->GetReferenceMode())
- {
- case KFbxGeometryElement::eDIRECT:
- {
- pTangent->x = leTangent->GetDirectArray().GetAt(vertecCounter).GetAt(0);
- pTangent->y = leTangent->GetDirectArray().GetAt(vertecCounter).GetAt(1);
- pTangent->z = leTangent->GetDirectArray().GetAt(vertecCounter).GetAt(2);
- }
- break;
- case KFbxGeometryElement::eINDEX_TO_DIRECT:
- {
- int id = leTangent->GetIndexArray().GetAt(vertecCounter);
- pTangent->x = leTangent->GetDirectArray().GetAt(id).GetAt(0);
- pTangent->y = leTangent->GetDirectArray().GetAt(id).GetAt(1);
- pTangent->z = leTangent->GetDirectArray().GetAt(id).GetAt(2);
- }
- break;
- default:
- break;
- }
- }
- break;
- }
- }
上述幾個Normal、Tangent、UV等信息讀取的函數的實現其實都差不多,首先需要判斷有沒有相應的Layer關聯在當前的Mesh中,若有則獲取其地址,然后根據不同的映射方式使用不同的方法從內存中讀取相應的值即可。
完成了這些基本幾何信息的讀取之后即可以使用其進行渲染了: