1. 程式人生 > >基於FBX SDK的FBX模型解析與載入

基於FBX SDK的FBX模型解析與載入

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的網站上進行獲得最新的版本安裝之後在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

  1. bool FBXImporter::Initialize() 
  2.     // Create the FBX SDK Manager, destroy the old manager at first
  3.     if(mpFBXSDKManager) 
  4.     { 
  5.        mpFBXSDKManager->Destroy(); 
  6.     } 
  7.     mpFBXSDKManager = KFbxSdkManager::Create(); 
  8.     if(mpFBXSDKManager == NULL) 
  9.     { 
  10.        returnfalse
  11.     } 
  12.     // Create an IOSettings object
  13.     KFbxIOSettings* ios = KFbxIOSettings::Create(mpFBXSDKManager , IOSROOT); 
  14.     mpFBXSDKManager->SetIOSettings(ios); 
  15.     // Load plug-ins from the executable directory
  16.     KString lExtension = "dll"
  17.     KString lPath = KFbxGetApplicationDirectory(); 
  18.     mpFBXSDKManager->LoadPluginsDirectory(lPath.Buffer() , lExtension.Buffer()); 
  19.     // Create the entity that hold the whole Scene
  20.     mpFBXSDKScene = KFbxScene::Create(mpFBXSDKManager , ""); 
  21.     returntrue

FbxScene的初始化

  1. bool FBXImporter::LoadScene(constchar* pSeneName) 
  2.     if(mpFBXSDKManager == NULL) 
  3.     { 
  4.        returnfalse
  5.     } 
  6.     // Get the file version number generate by the FBX SDK.
  7.     KFbxSdkManager::GetFileFormatVersion(mSDKVersion.mMajor , mSDKVersion.mMinor , mSDKVersion.mRevision); 
  8.     // Create an importer.
  9.     KFbxImporter* pKFBXImporter = KFbxImporter::Create(mpFBXSDKManager ,""); 
  10.     // Initialize the importer by providing a filename
  11.     FBXFileVersion fileVersion; 
  12.     bool importStatus = pKFBXImporter->Initialize(fileName , -1 , mpFBXSDKManager->GetIOSettings()); 
  13.     lImporter->GetFileVersion(fileVersion.mMajor , fileVersion.mMinor , fileVersion.mRevision); 
  14.     if(!importStatus) 
  15.     { 
  16.         returnfalse
  17.     } 
  18.     // Import the scene
  19.     mpFBXScene->Clear(); 
  20.     importStatus = pKFBXImporter->Import(m_mpFBXScene); 
  21.     // Destroy the importer.
  22.     pKFBXImporter->Destroy(); 
  23.     return importStatus; 

在完成了對KFbxScene的初始化操作之後即可以從其中得到整個模型的所有資訊。由於FBX的是採用了類似於樹的結構來進行儲存,因而就很容易使用類似於樹的遞迴方法來遍歷其中的每個結點,並根據結點的屬性選擇合適的處理操作,下述程式碼就完成了從根結點開始的全域性遍歷:

  1. void ProcessNode(KFbxNode* pNode) 
  2.     KFbxNodeAttribute::EAttributeType attributeType; 
  3.     if(pNode->GetNodeAttribute()) 
  4.     { 
  5.         switch(pNode->GetNodeAttribute()->GetAttributeType()) 
  6.         { 
  7.         case KFbxNodeAttribute::eMESH: 
  8.             ProcessMesh(pNode); 
  9.             break
  10.         case KFbxNodeAttribute::eSKELETON: 
  11.             ProcessSkeleton(pNode); 
  12.             break
  13.         case KFbxNodeAttribute::eLIGHT: 
  14.             ProcessLight(pNode); 
  15.             break
  16.         case KFbxNodeAttribute::eCAMERA: 
  17.             ProcessCamera(); 
  18.             break
  19.         } 
  20.     } 
  21.     for(int i = 0 ; i < pNode->GetChildCount() ; ++i) 
  22.     { 
  23.         ProcessNode(pNode->GetChild(i)); 
  24.     } 

上述程式碼比較簡單,直接傳入由KFbxScene中獲得的根結點之後即可遍歷到每一個子結點。在FBX的儲存中,每個父結點可以包含多個子結點,但每個子結點只有一個根結點,而且這其中的聯絡是雙向的,這樣很方便,比如在處理Skeleton時就常常需要從子結點中得到父結點的matrix等資訊,而這種雙向關係使得這些操作很容易實現。注意,上述程式碼中有pNode->GetNodeAttribute()檢查操作是必須的,因為並不是所有的結點都有相應的屬性(Attribute也是以子結點的方式關聯到當前的結點上的,因而可能為空)。

4. 載入幾何網格

FBX對幾何網格支援得還是很好的,Nurbes、Polygon、Triangle等均可以儲存。不過為了簡化載入和處理時的操作,最好直接在FBX匯出外掛中選擇一種統一的模式。比如可以在匯出生成FBX時選中Triangluation的屬性,那麼FBX匯出外掛會自動把所有的Nurbes、Polygon三角化為三角形進行儲存。當然,這個過程也可以在模型進行載入時來進行。這樣在得到的FBX中就只有三角形這樣一種網格模型,方便了載入的操作。模型的幾何資料主要包括以下部分:

  • Vertex       組成網格的頂點資訊,這一部分是必須的。
  • Color         每個頂點的顏色,一般不需要。
  • Normal     每個頂點所對應的法向,是由FBX匯出外掛計算生成,可以是逐面片或逐頂點。
  • UV             每個頂點所對應的法向,是由FBX匯出外掛計算生成,可以是逐面片或逐頂點。
  • Tangent   每個頂點所對應的貼圖UV值,一般來說,每個UV對應一個Layer,一個頂點可以有多個UV通道,這在讀入的時間需要進行判斷

幾何網格的載入比較簡單,直接遞迴地從根結點來遍歷整個graph,檢測當前的結點是否為eMESH的屬性,若是即處理其中的幾何資料,主要程式碼如下所示:

  1. void ProcessMesh(KFbxNode* pNode) 
  2.     KFbxMesh* pMesh = pNode->GetMesh(); 
  3.     if(pMesh == NULL) 
  4.     { 
  5.         return
  6.     } 
  7.     D3DXVECTOR3 vertex[3]; 
  8.     D3DXVECTOR4 color[3]; 
  9.     D3DXVECTOR3 normal[3]; 
  10.     D3DXVECTOR3 tangent[3]; 
  11.     D3DXVECTOR2 uv[3][2]; 
  12.     int triangleCount = pMesh->GetPolygonCount(); 
  13.     int vertexCounter = 0; 
  14.     for(int i = 0 ; i < triangleCount ; ++i) 
  15.     { 
  16.         for(int j = 0 ; j < 3 ; j++) 
  17.         { 
  18.             int ctrlPointIndex = pMesh->GetPolygonVertex(i , j); 
  19.             // Read the vertex
  20.             ReadVertex(pMesh , ctrlPointIndex , &vertex[j]); 
  21.             // Read the color of each vertex
  22.             ReadColor(pMesh , ctrlPointIndex , vertexCounter , &color[j]); 
  23.             // Read the UV of each vertex
  24.             for(int k = 0 ; k < 2 ; ++k) 
  25.             { 
  26.                 ReadUV(pMesh , ctrlPointIndex , pMesh->GetTextureUVIndex(i, j) , k , &(uv[j][k])); 
  27.             } 
  28.             // Read the normal of each vertex
  29.             ReadNormal(pMesh , ctrlPointIndex , vertexCounter , &normal[j]); 
  30.             // Read the tangent of each vertex
  31.             ReadTangent(pMesh , ctrlPointIndex , vertexCounter , &tangent[j]); 
  32.             vertexCounter++; 
  33.         } 
  34.         // 根據讀入的資訊組裝三角形,並以某種方式使用即可,比如存入到列表中、儲存到檔案等...
  35.     } 

上述程式碼完成了從一個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中的資訊,因而增加這樣的一個變數來進行記錄。

讀入頂點:

  1. void ReadVertex(KFbxMesh* pMesh ,int ctrlPointIndex , D3DXVECTOR3* pVertex) 
  2.     KFbxVector4* pCtrlPoint = pMesh->GetControlPoints(); 
  3.     pVertex->x = pCtrlPoint[ctrlPointIndex].GetAt(0); 
  4.     pVertex->y = pCtrlPoint[ctrlPointIndex].GetAt(1); 
  5.     pVertex->z = pCtrlPoint[ctrlPointIndex].GetAt(2); 

讀入Color:

  1. void ReadColor(KFbxMesh* pMesh ,int ctrlPointIndex ,int vertexCounter , D3DXVECTOR4* pColor) 
  2.     if(pMesh->GetElementVertexColorCount < 1) 
  3.     { 
  4.         return
  5.     } 
  6.     KFbxGeometryElementVertexColor* pVertexColor = pMesh->GetElementVertexColor(0); 
  7.     switch(pVertexColor->GetMappingMode()) 
  8.     { 
  9.     case KFbxGeometryElement::eBY_CONTROL_POINT: 
  10.         { 
  11.             switch(pVertexColor->GetReferenceMode()) 
  12.             { 
  13.             case KFbxGeometryElement::eDIRECT: 
  14.                 { 
  15.                     pColor->x = pVertexColor->GetDirectArray().GetAt(ctrlPointIndex).mRed; 
  16.                     pColor->y = pVertexColor->GetDirectArray().GetAt(ctrlPointIndex).mGreen; 
  17.                     pColor->z = pVertexColor->GetDirectArray().GetAt(ctrlPointIndex).mBlue; 
  18.                     pColor->w = pVertexColor->GetDirectArray().GetAt(ctrlPointIndex).mAlpha; 
  19.                 } 
  20.                 break
  21.             case KFbxGeometryElement::eINDEX_TO_DIRECT: 
  22.                 { 
  23.                     int id = pVertexColor->GetIndexArray().GetAt(ctrlPointIndex); 
  24.                     pColor->x = pVertexColor->GetDirectArray().GetAt(id).mRed; 
  25.                     pColor->y = pVertexColor->GetDirectArray().GetAt(id).mGreen; 
  26.                     pColor->z = pVertexColor->GetDirectArray().GetAt(id).mBlue; 
  27.                     pColor->w = pVertexColor->GetDirectArray().GetAt(id).mAlpha; 
  28.                 } 
  29.                 break
  30.             default
  31.                 break
  32.             } 
  33.         } 
  34.         break
  35.     case KFbxGeometryElement::eBY_POLYGON_VERTEX: 
  36.         { 
  37.             switch (pVertexColor->GetReferenceMode()) 
  38.             { 
  39.             case KFbxGeometryElement::eDIRECT: 
  40.                 { 
  41.                     pColor->x = pVertexColor->GetDirectArray().GetAt(vertexCounter).mRed; 
  42.                     pColor->y = pVertexColor->GetDirectArray().GetAt(vertexCounter).mGreen; 
  43.                     pColor->z = pVertexColor->GetDirectArray().GetAt(vertexCounter).mBlue; 
  44.                     pColor->w = pVertexColor->GetDirectArray().GetAt(vertexCounter).mAlpha; 
  45.                 } 
  46.                 break
  47.             case KFbxGeometryElement::eINDEX_TO_DIRECT: 
  48.                 { 
  49.                     int id = pVertexColor->GetIndexArray().GetAt(vertexCounter); 
  50.                     pColor->x = pVertexColor->GetDirectArray().GetAt(id).mRed; 
  51.                     pColor->y = pVertexColor->GetDirectArray().GetAt(id).mGreen; 
  52.                     pColor->z = pVertexColor->GetDirectArray().GetAt(id).mBlue; 
  53.                     pColor->w = pVertexColor->GetDirectArray().GetAt(id).mAlpha; 
  54.                 } 
  55.                 break
  56.             default
  57.                 break
  58.             } 
  59.         } 
  60.         break
  61.     } 

讀入UV:

  1. void ReadUV(KFbxMesh* pMesh ,int ctrlPointIndex ,int textureUVIndex ,int uvLayer , D3DXVECTOR2* pUV) 
  2.     if(uvLayer >= 2 || pMesh->GetElementUVCount() <= uvLayer) 
  3.     { 
  4.         returnfalse
  5.     } 
  6.     KFbxGeometryElementUV* pVertexUV = pMesh->GetElementUV(uvLayer); 
  7.     switch(pVertexUV->GetMappingMode()) 
  8.     { 
  9.     case KFbxGeometryElement::eBY_CONTROL_POINT: 
  10.         { 
  11.             switch(pVertexUV->GetReferenceMode()) 
  12.             { 
  13.             case KFbxGeometryElement::eDIRECT: 
  14.                 { 
  15.                     pUV->x = pVertexUV->GetDirectArray().GetAt(ctrlPointIndex).GetAt(0); 
  16.                     pUV->y = pVertexUV->GetDirectArray().GetAt(ctrlPointIndex).GetAt(1); 
  17.                 } 
  18.                 break
  19.             case KFbxGeometryElement::eINDEX_TO_DIRECT: 
  20.                 { 
  21.                     int id = pVertexUV->GetIndexArray().GetAt(ctrlPointIndex); 
  22.                     pUV->x = pVertexUV->GetDirectArray().GetAt(id).GetAt(0); 
  23.                     pUV->y = pVertexUV->GetDirectArray().GetAt(id).GetAt(1); 
  24.                 } 
  25.                 break
  26.             default
  27.                 break
  28.             } 
  29.         } 
  30.         break
  31.     case KFbxGeometryElement::eBY_POLYGON_VERTEX: 
  32.         { 
  33.             switch (pVertexUV->GetReferenceMode()) 
  34.             { 
  35.             case KFbxGeometryElement::eDIRECT: 
  36.             case KFbxGeometryElement::eINDEX_TO_DIRECT: 
  37.                 { 
  38.                     pUV->x = pVertexUV->GetDirectArray().GetAt(textureUVIndex).GetAt(0); 
  39.                     pUV->y = pVertexUV->GetDirectArray().GetAt(textureUVIndex).GetAt(1); 
  40.                 } 
  41.                 break
  42.             default
  43.                 break
  44.             } 
  45.         } 
  46.         break
  47.     } 

讀入Normal:

  1. void ReadNormal(KFbxMesh* pMesh ,int ctrlPointIndex ,int vertexCounter , D3DXVECTOR3* pNormal) 
  2.     if(pMesh->GetElementNormalCount() < 1) 
  3.     { 
  4.         return
  5.     } 
  6.     KFbxGeometryElementNormal* leNormal = pMesh->GetElementNormal(0); 
  7.     switch(leNormal->GetMappingMode()) 
  8.     { 
  9.     case KFbxGeometryElement::eBY_CONTROL_POINT: 
  10.         { 
  11.             switch(leNormal->GetReferenceMode()) 
  12.             { 
  13.             case KFbxGeometryElement::eDIRECT: 
  14.                 { 
  15.                     pNormal->x = leNormal->GetDirectArray().GetAt(ctrlPointIndex).GetAt(0); 
  16.                     pNormal->y = leNormal->GetDirectArray().GetAt(ctrlPointIndex).GetAt(1); 
  17.                     pNormal->z = leNormal->GetDirectArray().GetAt(ctrlPointIndex).GetAt(2); 
  18.                 } 
  19.                 break
  20.             case KFbxGeometryElement::eINDEX_TO_DIRECT: 
  21.                 { 
  22.                     int id = leNormal->GetIndexArray().GetAt(ctrlPointIndex); 
  23.                     pNormal->x = leNormal->GetDirectArray().GetAt(id).GetAt(0); 
  24.                     pNormal->y = leNormal->GetDirectArray().GetAt(id).GetAt(1); 
  25.                     pNormal->z = leNormal->GetDirectArray().GetAt(id).GetAt(2); 
  26.                 } 
  27.                 break
  28.             default
  29.                 break
  30.             } 
  31.         } 
  32.         break
  33.     case KFbxGeometryElement::eBY_POLYGON_VERTEX: 
  34.         { 
  35.             switch(leNormal->GetReferenceMode()) 
  36.             { 
  37.             case KFbxGeometryElement::eDIRECT: 
  38.                 { 
  39.                     pNormal->x = leNormal->GetDirectArray().GetAt(vertexCounter).GetAt(0); 
  40.                     pNormal->y = leNormal->GetDirectArray().GetAt(vertexCounter).GetAt(1); 
  41.                     pNormal->z = leNormal->GetDirectArray().GetAt(vertexCounter).GetAt(2); 
  42.                 } 
  43.                 break
  44.             case KFbxGeometryElement::eINDEX_TO_DIRECT: 
  45.                 { 
  46.                     int id = leNormal->GetIndexArray().GetAt(vertexCounter); 
  47.                     pNormal->x = leNormal->GetDirectArray().GetAt(id).GetAt(0); 
  48.                     pNormal->y = leNormal->GetDirectArray().GetAt(id).GetAt(1); 
  49.                     pNormal->z = leNormal->GetDirectArray().GetAt(id).GetAt(2); 
  50.                 } 
  51.                 break
  52.             default
  53.                 break
  54.             } 
  55.         } 
  56.         break
  57.     } 

讀入Tangent:

  1. void ReadTangent(KFbxMesh* pMesh ,int ctrlPointIndex ,int vertecCounter , D3DXVECTOR3* pTangent) 
  2.     if(pMesh->GetElementTangentCount() < 1) 
  3.     { 
  4.         return
  5.     } 
  6.     KFbxGeometryElementTangent* leTangent = pMesh->GetElementTangent(0); 
  7.     switch(leTangent->GetMappingMode()) 
  8.     { 
  9.     case KFbxGeometryElement::eBY_CONTROL_POINT: 
  10.         { 
  11.             switch(leTangent->GetReferenceMode()) 
  12.             { 
  13.             case KFbxGeometryElement::eDIRECT: 
  14.                 { 
  15.                     pTangent->x = leTangent->GetDirectArray().GetAt(ctrlPointIndex).GetAt(0); 
  16.                     pTangent->y = leTangent->GetDirectArray().GetAt(ctrlPointIndex).GetAt(1); 
  17.                     pTangent->z = leTangent->GetDirectArray().GetAt(ctrlPointIndex).GetAt(2); 
  18.                 } 
  19.                 break
  20.             case KFbxGeometryElement::eINDEX_TO_DIRECT: 
  21.                 { 
  22.                     int id = leTangent->GetIndexArray().GetAt(ctrlPointIndex); 
  23.                     pTangent->x = leTangent->GetDirectArray().GetAt(id).GetAt(0); 
  24.                     pTangent->y = leTangent->GetDirectArray().GetAt(id).GetAt(1); 
  25.                     pTangent->z = leTangent->GetDirectArray().GetAt(id).GetAt(2); 
  26.                 } 
  27.                 break
  28.             default
  29.                 break
  30.             } 
  31.         } 
  32.         break
  33.     case KFbxGeometryElement::eBY_POLYGON_VERTEX: 
  34.         { 
  35.             switch(leTangent->GetReferenceMode()) 
  36.             { 
  37.             case KFbxGeometryElement::eDIRECT: 
  38.                 { 
  39.                     pTangent->x = leTangent->GetDirectArray().GetAt(vertecCounter).GetAt(0); 
  40.                     pTangent->y = leTangent->GetDirectArray().GetAt(vertecCounter).GetAt(1); 
  41.                     pTangent->z = leTangent->GetDirectArray().GetAt(vertecCounter).GetAt(2); 
  42.                 } 
  43.                 break
  44.             case KFbxGeometryElement::eINDEX_TO_DIRECT: 
  45.                 { 
  46.                     int id = leTangent->GetIndexArray().GetAt(vertecCounter); 
  47.                     pTangent->x = leTangent->GetDirectArray().GetAt(id).GetAt(0); 
  48.                     pTangent->y = leTangent->GetDirectArray().GetAt(id).GetAt(1); 
  49.                     pTangent->z = leTangent->GetDirectArray().GetAt(id).GetAt(2); 
  50.                 } 
  51.                 break
  52.             default
  53.                 break
  54.             } 
  55.         } 
  56.         break
  57.     } 

上述幾個Normal、Tangent、UV等資訊讀取的函式的實現其實都差不多,首先需要判斷有沒有相應的Layer關聯在當前的Mesh中,若有則獲取其地址,然後根據不同的對映方式使用不同的方法從記憶體中讀取相應的值即可。

完成了這些基本幾何資訊的讀取之後即可以使用其進行渲染了:

5. 載入材質

Material是一個模型渲染時必不可少的部分,當然,這些資訊也被存到了FBX之中(甚至各種貼圖等也可以直接內嵌到FBX內部),就需要從FBX中載入這些資訊以完成帶有材質的渲染。材質的載入可以與Mesh的載入相結合來完成,但更好的方法是獨立進行,這樣各模組間的關係更清晰,但這就需要一個額外的操作,那就是關聯Mesh與Material。FBX中的材質物件包含了豐富的資訊,比如最常規的從Max中可以看到那些材質屬性,如ambient、diffuse、specular的color和texture;shininess、opacity值等,更高階一點的屬性諸如Effect的引數、原始檔等都可以儲存。它是儘可能保證從建模工具中匯出時不丟失地儲存材質資訊,但我們在使用時卻可以有選擇地讀取。

5.1 關聯Mesh與材質

對於Material與Mesh獨立載入的系統而言,首先需要讀取相關的資訊將兩者關聯起來,這些資訊其實對也都儲存在KFbxMesh之內(屬於幾何資訊的一部分吧)。每個帶有材質的Mesh結點上都會包含有一個型別為KFbxGeometryElementMaterial的結點(若不含有材質則該結點為空),該結點中記錄了Mesh中的多邊形(這裡全部為三角形)與每個材質的對應關係,讀取該結點中的資訊建立Mesh與Material之間的連線關係,程式碼如下:

  1. void ConnectMaterialToMesh(KFbxMesh* pMesh ,int triangleCount ,int* pTriangleMtlIndex) 
  2.     // Get the material index list of current mesh
  3.     KFbxLayerElementArrayTemplate<int>* pMaterialIndices; 
  4.     KFbxGeometryElement::EMappingMode   materialMappingMode = KFbxGeometryElement::eNONE; 
  5.     if(pMesh->GetElementMaterial()) 
  6.     { 
  7.         pMaterialIndices    = &pMesh->GetElementMaterial()->GetIndexArray(); 
  8.         materialMappingMode = pMesh->GetElementMaterial()->GetMappingMode(); 
  9.         if(pMaterialIndices) 
  10.         { 
  11.             switch(materialMappingMode) 
  12.             { 
  13.             case KFbxGeometryElement::eBY_POLYGON: 
  14.                 { 
  15.                     if(pMaterialIndices->GetCount() == triangleCount) 
  16.                     { 
  17.                         for(int triangleIndex = 0 ; triangleIndex < triangleCount ; ++triangleIndex) 
  18.                         { 
  19.                             int materialIndex = pMaterialIndices->GetAt(triangleIndex); 
  20.                             pTriangleMtlIndex[triangleIndex] = materialIndex; 
  21.                         } 
  22.                     } 
  23.                 } 
  24.                 break
  25.             case KFbxGeometryElement::eALL_SAME: 
  26.                 { 
  27.                     int lMaterialIndex = pMaterialIndices->GetAt(0); 
  28.                     for(int triangleIndex = 0 ; triangleIndex < triangleCount ; ++triangleIndex) 
  29.                     { 
  30.                         int materialIndex = pMaterialIndices->GetAt(triangleIndex); 
  31.                         pTriangleMtlIndex[triangleIndex] = materialIndex; 
  32.                     } 
  33.                 } 
  34.             } 
  35.         } 
  36.     } 

其中上triangleCount即為從pMesh中讀取得到的三角形的數量,pTriangleMtlIndex是一個長度為triangleCount的陣列,主要用來儲存讀取到的三角形對應的材質索引。注意:這裡考慮的情況是對於一個三角形只對應一個材質,而一般情況下也是這樣(如果是對應多個材質的話需要些許修改此處的程式碼)。完成Mesh的索引讀取之後即可以將pTriangleMtlIndex中的值以合適的方式轉儲到對應的三角形列表中(或以其它的方式對應)以便在渲染時使用。

5.2 普通材質

FBX中實際儲存材質資訊的位置是每個Mesh中對應的一個型別為KFbxSurfaceMaterial的結點,其裡邊儲存了普通材質的典型資訊,主要包括以下屬性(有一些沒有列出):

  •   ShadingModel                 材質的光照模型,一般為兩種典型的區域性光照模型:Phong、Lambert
  •   Emissive                          Emissive屬性
  •   EmissiveFactor
  •   Ambient                           Ambient屬性
  •   AmbientFactor
  •   Diffuse                             Diffuse屬性
  •   DiffuseFactor
  •   Specular                           Specular屬性
  •   SpecularFactor
  •   Shininess                         Sepcular的Shininess屬性
  •   Bump                               Normal Map相關的屬性
  •   NormalMap
  •   BumpFactor
  •   TransparentColor             Transparent屬性
  •   TransparencyFactor
  •   Reflection                        Reflection屬性
  •   ReflectionFactor

當然,在實際應用中這些屬性並不一定需要全部讀取,可以根據情況選擇讀取即可。材質的讀取程式碼如下所述(簡略版):

  1. void LoadMaterial(KFbxMesh* pMesh) 
  2.     int materialCount; 
  3.     KFbxNode* pNode; 
  4.     if(pMesh && pMesh->GetNode()) 
  5.     { 
  6.         pNode         = pMesh->GetNode(); 
  7.         materialCount = pNode->GetMaterialCount(); 
  8.     } 
  9.     if(materialCount > 0) 
  10.     { 
  11.         for(int materialIndex = 0 ; materialIndex < materialCount ; materialIndex++) 
  12.         { 
  13.             KFbxSurfaceMaterial* pSurfaceMaterial = pNode->GetMaterial(materialIndex); 
  14.             LoadMaterialAttribute(pSurfaceMaterial); 
  15.         } 
  16.     } 
  1. void LoadMaterialAttribute(KFbxSurfaceMaterial* pSurfaceMaterial) 
  2.     // Get the name of material
  3.     pSurfaceMaterial->GetName(); 
  4.     // Phong material
  5.     if(pSurfaceMaterial->GetClassId().Is(KFbxSurfacePhong::ClassId)) 
  6.     { 
  7.         // Ambient Color
  8.         fbxDouble3 = ((KFbxSurfacePhong*)pSurfaceMaterial)->Ambient; 
  9.         // ...
  10.         // Diffuse Color
  11.         fbxDouble3 =((KFbxSurfacePhong*)pSurfaceMaterial)->Diffuse; 
  12.         // ...
  13.         // Specular Color
  14.         fbxDouble3 =((KFbxSurfacePhong*)pSurfaceMaterial)->Specular; 
  15.         // ...
  16.         // Emissive Color
  17.         fbxDouble3 =((KFbxSurfacePhong*)pSurfaceMaterial)->Emissive; 
  18.         // ...
  19.         // Opacity
  20.         fbxDouble1 =((KFbxSurfacePhong*)pSurfaceMaterial)->TransparencyFactor; 
  21.         // ...
  22.         // Shininess
  23.         fbxDouble1 =((KFbxSurfacePhong*)pSurfaceMaterial)->Shininess; 
  24.         // ...
  25.         // Reflectivity
  26.         fbxDouble1 =((KFbxSurfacePhong*)pSurfaceMaterial)->ReflectionFactor; 
  27.         // ...
  28.         return
  29.     } 
  30.     // Lambert material
  31.     if(pSurfaceMaterial->GetClassId().Is(KFbxSurfaceLambert::ClassId)) 
  32.     { 
  33.         // Ambient Color
  34.         fbxDouble3=((KFbxSurfaceLambert*)pSurfaceMaterial)->Ambient; 
  35.         // ...
  36.         // Diffuse Color
  37.         fbxDouble3 =((KFbxSurfaceLambert*)pSurfaceMaterial)->Diffuse; 
  38.         // ...
  39.         // Emissive Color
  40.         fbxDouble3 =((KFbxSurfaceLambert*)pSurfaceMaterial)->Emissive; 
  41.         // ...
  42.         // Opacity
  43.         fbxDouble1 =((KFbxSurfaceLambert*)pSurfaceMaterial)->TransparencyFactor; 
  44.         // ...
  45.         return
  46.     } 

上述程式碼就可以完成對普通屬性載入。另外,材質中關聯的Texture也需要進行載入,這個操作一般與一個紋理管理器結合起來進行,以便對所有的Texture與Material之間形成合理的關聯,這一步的操作一般如下程式碼所述:

  1. void LoadMaterialTexture(KFbxSurfaceMaterial* pSurfaceMaterial) 
  2.     int textureLayerIndex; 
  3.     KFbxProperty pProperty; 
  4.     int texID; 
  5.     MaterialTextureDesc::MtlTexTypeEnum texType; 
  6.     for(textureLayerIndex = 0 ; textureLayerIndex < KFbxLayerElement::LAYERELEMENT_TYPE_TEXTURE_COUNT ; ++textureLayerIndex) 
  7.     { 
  8.         pProperty = pSurfaceMaterial->FindProperty(KFbxLayerElement::TEXTURE_CHANNEL_NAMES[textureLayerIndex]); 
  9.         if(pProperty.IsValid()) 
  10.         { 
  11.             int textureCount = pProperty.GetSrcObjectCount(KFbxTexture::ClassId); 
  12.             for(int j = 0 ; j < textureCount ; ++j) 
  13.             { 
  14.                 KFbxTexture* pTexture = KFbxCast<KFbxTexture>(pProperty.GetSrcObject(KFbxTexture::ClassId,j)); 
  15.                 if(pTexture) 
  16.                 { 
  17.                     // Use pTexture to load the attribute of current texture...
  18.                 } 
  19.             } 
  20.         } 
  21.     } 

5.3 硬體相關的材質與Effect

有過建模經驗的童鞋都知道,在3D Max或Maya中可以為某些材質指定特定的Shader來完成特定的效果,這些模型在儲存時也會儲存相應的硬體相關的Shader到FBX模型中,因而針對這樣屬性的材質也需要特別的程式碼來進行載入。FBX裡邊支援嵌入CG、HLSL、GLSL等主流著色語言,而著色語言的型別在解析時也很容易得到。

  1. void LoadMaterialAttribute(KFbxSurfaceMaterial* pSurfaceMaterial) 
  2.     KFbxImplementation* pImplementation; 
  3.     KString implemenationType; 
  4.     pImplementation = GetImplementation(pSurfaceMaterial , ImplementationHLSL); 
  5.     KString implemenationType = "HLSL"
  6.     if(pImplementation) 
  7.     { 
  8.         LoadMaterialEffect(pSurfaceMaterial , pImplementation , &implemenationType); 
  9.     } 

上述程式碼可以與前面的Material屬性讀取的程式碼合併。FBX一般通過一個型別為KFbxImplementation的物件將硬體相關的Shader與Material進行關聯,可以使用如上的程式碼實現兩者之間關聯的情況的獲取,其中ImplementationHLSL為一個標識HLSL型別Shader的巨集,若是CG則用ImplementationCGFX。如果當前Material中包含了HLSL型別Shader之後,那麼就可以得到一個不為空的KFbxImplementation型別的指標,在其中就可以解析該Shader的屬性,否則,則該指標為空,說明些材質關聯了其它類似的Shader或是不包含Shader。通過KFbxImplementation來獲取Effect對應的屬性的程式碼如下所示:

  1. void LoadMaterialEffect(KFbxSurfaceMaterial* pSurfaceMaterial ,const KFbxImplementation* pImplementation , KString* pImplemenationType) 
  2.     KFbxBindingTable const* lRootTable = pImplementation->GetRootTable(); 
  3.     fbxString lFileName                = lRootTable->DescAbsoluteURL.Get(); 
  4.     fbxString lTechniqueName           = lRootTable->DescTAG.Get(); 
  5.     // Name of the effect file
  6.     lFileName.Buffer(); 
  7.     KFbxBindingTable const* pBTable = pImplementation->GetRootTable(); 
  8.     size_t entryCount = pBTable->GetEntryCount(); 
  9.     for(size_t