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導出插件計算生成,可以是逐面片或逐頂點。
- UV 每個頂點所對應的法向,是由FBX導出插件計算生成,可以是逐面片或逐頂點。
- Tangent 每個頂點所對應的貼圖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中,若有則獲取其地址,然后根據不同的映射方式使用不同的方法從內存中讀取相應的值即可。
完成了這些基本幾何信息的讀取之后即可以使用其進行渲染了:
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之間的連接關系,代碼如下:
- void ConnectMaterialToMesh(KFbxMesh* pMesh , int triangleCount , int* pTriangleMtlIndex)
- {
- // Get the material index list of current mesh
- KFbxLayerElementArrayTemplate<int>* pMaterialIndices;
- KFbxGeometryElement::EMappingMode materialMappingMode = KFbxGeometryElement::eNONE;
- if(pMesh->GetElementMaterial())
- {
- pMaterialIndices = &pMesh->GetElementMaterial()->GetIndexArray();
- materialMappingMode = pMesh->GetElementMaterial()->GetMappingMode();
- if(pMaterialIndices)
- {
- switch(materialMappingMode)
- {
- case KFbxGeometryElement::eBY_POLYGON:
- {
- if(pMaterialIndices->GetCount() == triangleCount)
- {
- for(int triangleIndex = 0 ; triangleIndex < triangleCount ; ++triangleIndex)
- {
- int materialIndex = pMaterialIndices->GetAt(triangleIndex);
- pTriangleMtlIndex[triangleIndex] = materialIndex;
- }
- }
- }
- break;
- case KFbxGeometryElement::eALL_SAME:
- {
- int lMaterialIndex = pMaterialIndices->GetAt(0);
- for(int triangleIndex = 0 ; triangleIndex < triangleCount ; ++triangleIndex)
- {
- int materialIndex = pMaterialIndices->GetAt(triangleIndex);
- pTriangleMtlIndex[triangleIndex] = materialIndex;
- }
- }
- }
- }
- }
- }
其中上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
當然,在實際應用中這些屬性並不一定需要全部讀取,可以根據情況選擇讀取即可。材質的讀取代碼如下所述(簡略版):
- void LoadMaterial(KFbxMesh* pMesh)
- {
- int materialCount;
- KFbxNode* pNode;
- if(pMesh && pMesh->GetNode())
- {
- pNode = pMesh->GetNode();
- materialCount = pNode->GetMaterialCount();
- }
- if(materialCount > 0)
- {
- for(int materialIndex = 0 ; materialIndex < materialCount ; materialIndex++)
- {
- KFbxSurfaceMaterial* pSurfaceMaterial = pNode->GetMaterial(materialIndex);
- LoadMaterialAttribute(pSurfaceMaterial);
- }
- }
- }
- void LoadMaterialAttribute(KFbxSurfaceMaterial* pSurfaceMaterial)
- {
- // Get the name of material
- pSurfaceMaterial->GetName();
- // Phong material
- if(pSurfaceMaterial->GetClassId().Is(KFbxSurfacePhong::ClassId))
- {
- // Ambient Color
- fbxDouble3 = ((KFbxSurfacePhong*)pSurfaceMaterial)->Ambient;
- // ...
- // Diffuse Color
- fbxDouble3 =((KFbxSurfacePhong*)pSurfaceMaterial)->Diffuse;
- // ...
- // Specular Color
- fbxDouble3 =((KFbxSurfacePhong*)pSurfaceMaterial)->Specular;
- // ...
- // Emissive Color
- fbxDouble3 =((KFbxSurfacePhong*)pSurfaceMaterial)->Emissive;
- // ...
- // Opacity
- fbxDouble1 =((KFbxSurfacePhong*)pSurfaceMaterial)->TransparencyFactor;
- // ...
- // Shininess
- fbxDouble1 =((KFbxSurfacePhong*)pSurfaceMaterial)->Shininess;
- // ...
- // Reflectivity
- fbxDouble1 =((KFbxSurfacePhong*)pSurfaceMaterial)->ReflectionFactor;
- // ...
- return;
- }
- // Lambert material
- if(pSurfaceMaterial->GetClassId().Is(KFbxSurfaceLambert::ClassId))
- {
- // Ambient Color
- fbxDouble3=((KFbxSurfaceLambert*)pSurfaceMaterial)->Ambient;
- // ...
- // Diffuse Color
- fbxDouble3 =((KFbxSurfaceLambert*)pSurfaceMaterial)->Diffuse;
- // ...
- // Emissive Color
- fbxDouble3 =((KFbxSurfaceLambert*)pSurfaceMaterial)->Emissive;
- // ...
- // Opacity
- fbxDouble1 =((KFbxSurfaceLambert*)pSurfaceMaterial)->TransparencyFactor;
- // ...
- return;
- }
- }
上述代碼就可以完成對普通屬性加載。另外,材質中關聯的Texture也需要進行加載,這個操作一般與一個紋理管理器結合起來進行,以便對所有的Texture與Material之間形成合理的關聯,這一步的操作一般如下代碼所述:
- void LoadMaterialTexture(KFbxSurfaceMaterial* pSurfaceMaterial)
- {
- int textureLayerIndex;
- KFbxProperty pProperty;
- int texID;
- MaterialTextureDesc::MtlTexTypeEnum texType;
- for(textureLayerIndex = 0 ; textureLayerIndex < KFbxLayerElement::LAYERELEMENT_TYPE_TEXTURE_COUNT ; ++textureLayerIndex)
- {
- pProperty = pSurfaceMaterial->FindProperty(KFbxLayerElement::TEXTURE_CHANNEL_NAMES[textureLayerIndex]);
- if(pProperty.IsValid())
- {
- int textureCount = pProperty.GetSrcObjectCount(KFbxTexture::ClassId);
- for(int j = 0 ; j < textureCount ; ++j)
- {
- KFbxTexture* pTexture = KFbxCast<KFbxTexture>(pProperty.GetSrcObject(KFbxTexture::ClassId,j));
- if(pTexture)
- {
- // Use pTexture to load the attribute of current texture...
- }
- }
- }
- }
- }
5.3 硬件相關的材質與Effect
有過建模經驗的童鞋都知道,在3D Max或Maya中可以為某些材質指定特定的Shader來完成特定的效果,這些模型在保存時也會保存相應的硬件相關的Shader到FBX模型中,因而 針對這樣屬性的材質也需要特別的代碼來進行加載。FBX里邊支持嵌入CG、HLSL、GLSL等主流着色語言,而着色語言的類型在解析時也很容易得到。
- void LoadMaterialAttribute(KFbxSurfaceMaterial* pSurfaceMaterial)
- {
- KFbxImplementation* pImplementation;
- KString implemenationType;
- pImplementation = GetImplementation(pSurfaceMaterial , ImplementationHLSL);
- KString implemenationType = "HLSL";
- if(pImplementation)
- {
- LoadMaterialEffect(pSurfaceMaterial , pImplementation , &implemenationType);
- }
- }
上述代碼可以與前面的Material屬性讀取的代碼合 並。FBX一般通過一個類型為KFbxImplementation的對象將硬件相關的Shader與Material進行關聯,可以使用如上的代碼實現 兩者之間關聯的情況的獲取,其中ImplementationHLSL為一個標識HLSL類型Shader的宏,若是CG則用 ImplementationCGFX。如果當前Material中包含了HLSL類型Shader之后,那么就可以得到一個不為空的 KFbxImplementation類型的指針,在其中就可以解析該Shader的屬性,否則,則該指針為空,說明些材質關聯了其它類似的Shader 或是不包含Shader。通過KFbxImplementation來獲取Effect對應的屬性的代碼如下所示:
- void LoadMaterialEffect(KFbxSurfaceMaterial* pSurfaceMaterial , const KFbxImplementation* pImplementation , KString* pImplemenationType)
- {
- KFbxBindingTable const* lRootTable = pImplementation->GetRootTable();
- fbxString lFileName = lRootTable->DescAbsoluteURL.Get();
- fbxString lTechniqueName = lRootTable->DescTAG.Get();
- // Name of the effect file
- lFileName.Buffer();
- KFbxBindingTable const* pBTable = pImplementation->GetRootTable();
- size_t entryCount = pBTable->GetEntryCount();
- for(size_t i = 0 ; i < entryCount ; ++i)
- {
- const KFbxBindingTableEntry& btEntry = pBTable->GetEntry(i);
- const char* pEntrySrcType = btEntry.GetEntryType(true);
- KFbxProperty fbxProperty;
- // Name of Parameter
- btEntry.GetDestination();
- // Semantic of Parameter
- btEntry.GetDestination();
- if(strcmp(KFbxPropertyEntryView::sEntryType , pEntrySrcType) == 0)
- {
- fbxProperty = pSurfaceMaterial->FindPropertyHierarchical(btEntry.GetSource());
- if(!fbxProperty.IsValid())
- {
- fbxProperty = pSurfaceMaterial->RootProperty.FindHierarchical(btEntry.GetSource());
- }
- }
- else
- {
- if(strcmp(KFbxConstantEntryView::sEntryType , pEntrySrcType) == 0)
- {
- fbxProperty = pImplementation->GetConstants().FindHierarchical(btEntry.GetSource());
- }
- }
- if(fbxProperty.IsValid())
- {
- if(fbxProperty.GetSrcObjectCount(FBX_TYPE(KFbxTexture)) > 0)
- {
- // Texture Parameter
- for(int j = 0 ; j < fbxProperty.GetSrcObjectCount(FBX_TYPE(KFbxFileTexture)) ; ++j)
- {
- KFbxFileTexture* pFileTexture = fbxProperty.GetSrcObject(FBX_TYPE(KFbxFileTexture) , j);
- }
- for(int j = 0 ; j < fbxProperty.GetSrcObjectCount(FBX_TYPE(KFbxLayeredTexture)) ; ++j)
- {
- KFbxLayeredTexture* pLayeredTexture = fbxProperty.GetSrcObject(FBX_TYPE(KFbxLayeredTexture) , j);
- }
- for(int j = 0 ; j < fbxProperty.GetSrcObjectCount(FBX_TYPE(KFbxProceduralTexture)) ; ++j)
- {
- KFbxProceduralTexture* pProceduralTexture = fbxProperty.GetSrcObject(FBX_TYPE(KFbxProceduralTexture) , j);
- }
- }
- else
- {
- // Common Parameter
- KFbxDataType dataType = fbxProperty.GetPropertyDataType();
- // Bool value
- if(DTBool == dataType)
- {
- bool boolValue = KFbxGet<bool>(fbxProperty);
- }
- // Integer value
- if(DTInteger == dataType || DTEnum == dataType)
- {
- int intValue = KFbxGet<int>(fbxProperty);
- }
- // Float
- if(DTFloat == dataType)
- {
- float floatValue = KFbxGet<float>(fbxProperty);
- }
- // Double
- if(DTDouble == dataType)
- {
- double doubleValue = (float)KFbxGet<double>(fbxProperty);
- }
- // Double2
- if(DTDouble2 == dataType)
- {
- fbxDouble2 lDouble2 = KFbxGet<fbxDouble2>(fbxProperty);
- D3DXVECTOR2 double2Value = D3DXVECTOR2((float)lDouble2[0] , (float)lDouble2[1]);
- }
- // Double3
- if(DTDouble3 == dataType || DTVector3D == dataType || DTColor3 == dataType)
- {
- fbxDouble3 lDouble3 = KFbxGet<fbxDouble3>(fbxProperty);
- D3DXVECTOR3 double3Value = D3DXVECTOR3((float)lDouble3[0] , (float)lDouble3[1] , (float)lDouble3[2]);
- }
- // Double4
- if(DTDouble4 == dataType || DTVector4D == dataType || DTColor4 == dataType)
- {
- fbxDouble4 lDouble4 = KFbxGet<fbxDouble4>(fbxProperty);
- D3DXVECTOR4 double4Value = D3DXVECTOR4((float)lDouble4[0] , (float)lDouble4[1] , (float)lDouble4[2] , (float)lDouble4[3]);
- }
- // Double4x4
- if(DTDouble44 == dataType)
- {
- fbxDouble44 lDouble44 = KFbxGet<fbxDouble44>(fbxProperty);
- D3DXMATRIX double4x4Value;
- for(int i = 0 ; i < 4 ; ++i)
- {
- for(int j = 0 ; j < 4 ; ++j)
- {
- double4x4Value.m[i][j] = (float)lDouble44[i][j];
- }
- }
- }
- // String
- if(DTString == dataType || DTUrl == dataType || DTXRefUrl == dataType)
- {
- char* pStringBuffer =(KFbxGet<fbxString>(fbxProperty)).Buffer();
- }
- }
- }
- }
- }
可以解析到的Effect的主要屬性包括Shader所對應的源文件、Shader中提供的各種外部參數的初始設定等(比如在3D Max中通過UI控件所調節的參數的數值)。具體的方法代碼里邊已經比較明確了,這里就不在贅述了。后續的一些操作就要看整個材質與Effect部分的數據結構如何組織以及如何與你自己的代碼整合。
5.4 根據材質優化Mesh
通過FBX導出之后得到的FBX模型在存儲時一般會以幾何屬性為首要考量因素來生成整個文件的Scene graph,因此上述解析得到的幾何網格與Material之間的映射關系可能並不適合於直接進行繪制,一般需要重新再組織。比如其間的映射關系可能是
- Triangle0 -> Material1
- Triangle1 -> Material0
- Triangle2 -> Material1
- ...
如果一個應用的渲染流程使用了Material之間的最少切換次數來作為渲染的首要考慮的話,那么就不能直接使用Triangle的順序來生成渲染Buffer,而需要根據Material對其進行再排序並重新組織幾何數據間的次序。
完成上述加載之后即可實現帶有材質的渲染效果:
6. 加載Camera和Light
在FBX模型中除了幾何數據外較為常用的信息可能就是Camera和Light,雖然在游戲中一般不直接從模型中得到這兩部分信息,而是由引擎來提供,但是FBX中提供了對這些信息保存的支持。其實單純加載這兩部分的信息很簡單,就像之前介紹的在整個Scene Graph中對每個Node遍歷過程中,判斷得到當前結點是Camera或Light時調用相應的ProcessCamera或ProcessLight來完成相關的處理操作即可。
如果對於當前結點判斷得到其是一個Camera結點,那么可以直接調用GetCamera來得到一個KFbxCamera類型的指針,之后就可以通過該指針來完成Camera屬性的獲取。
- void ProcessCamera(KFbxNode* pNode)
- {
- KFbxCamera* pCamera = pNode->GetCamera();
- // 調用相應的接口獲取Camera的屬性即可
- }
對於Light結點的處理與Camera類似。至於Camera與Light結點所具有的屬性可以直接在SDK中看kfbxcamera與kfbxlight的類型定義即可。
- void ProcessLight(KFbxNode* pNode)
- {
- KFbxLight* pLight = pNode->GetLight();
- // 調用相應的接口獲取Light的屬性即可
- }
7. 加載動畫
動畫信息是模型數據中非常重要 的一部分,也是一個渲染或游戲引擎最基本的需求之一。FBX對Animation的良好支持也成為其與.obj等靜態模型最主要區別之一,而且最新的 SDK中也提供了對Animation很豐富與簡便的操作接口,包括自定義寫入與讀出等。接下來介紹一下如何使用FBX SDK來加載FBX中存儲的動畫信息。
7.1 動畫數據讀取
在FBX中實現對於動畫數據的存儲主要通過以下三個對象層來實現:Animaiton Stack、 Animation Layer、Animation Node,其層次關系為
Animation Stack -> Animation Layer -> Animation Node,圖示化結構為(圖片來自於FBX SDKRef):
其中的Animation Stack為FBX動畫管理的最高層,其中包含着與之相關聯的Animation Layer等;每個Animation Stack對應着一套動作過程。每個Stack中包含一個或多個Animation Layer(當用來做blend時就需要多個Layer,但一般是一個)。在每個Layer中又通過一個KFbxAnimCurveNode的結點使 Layer與具體的動畫數據發生關系。一般情況下可以根據自己的需要情況或引擎的動畫實現方式來讀取FBX中的動畫數據,例如本人在實現時從FBX中讀取 數據的方法就可以抽像化為如下圖所示的結構:
其中對每個Node判斷其是否有對應的動畫數據,若有則讀取其Curve中的數據並存儲以供渲染更新使用,代碼如下所述:
- void LoadNodeCurve(KFbxAnimLayer* pAnimationLayer , KFbxNode* pNode , StackTimeSpan& timeSpan)
- {
- KTime keyTimer;
- unsigned long millseconds;
- for(UINT i = 0 ; i < timeSpan.mKeyNums ; ++i)
- {
- millseconds = timeSpan.mStart + (float)i * timeSpan.mStep;
- keyTimer.SetMilliSeconds(millseconds);
- // 計算得到當前結點在當前時刻下所對應的空間局部和全局矩陣
- // 局部矩陣對於Skeleton是必需的,因需要使用它來計算父子Skeleton之間的空間關系
- KFbxXMatrix curveKeyLocalMatrix = pNode->EvaluateLocalTransform(keyTimer);
- KFbxXMatrix curveKeyGlobalMatrix = pNode->EvaluateGlobalTransform(keyTimer);
- }
- }
代碼中的timeSpan是一 個自定義的結構,其中包含了整個FBX對象動畫信息的相關數據,比如幀數、起始時間、幀間時差等;在讀取時需將其中的信息轉換為一個KTime類型的對象 (keyTimer)以供FBX SDK的API使用。上述操作加載了動畫數據中直接相關的空間Matrix信息,這是普通模型對象的基本動畫信息。但是對於Camera或Light等對 象而言,動畫不僅包含着位置或空間信息的變化而且還包含着一些其它的屬性變化如Camera的FOV,Light的Direction,Color等,這 些信息也導出FBX時被存儲到了FBX中。而這些信息的獲取就是通過KFbxCurveNode來實現,其關聯具體的Curve到相應的Property 上,進而從中獲得對應的動畫信息。比如我們熟悉的Camera實現中有一個常用的屬性PixelAspectRatio,用來描述視口Width與Height之間的比值,對於某些動畫效果這個Ratio可能是時變的,因而在建模時就會將該信息同樣以動畫的信息進行存儲,現在我們想要得到這一部分動畫數據。通過查看kfbxcamera.h可以發現在KFbxCamera的定義中含有
KFbxTypedProperty<fbxDouble1> PixelAspectRatio
的一個成員變量,這即是PixelAspcetRatio動畫數據所存儲的位置;而在ProcessCamera時已經由當前Node的指針得到了Camera對應的指針,之后該部分讀取代碼基本上如下所述:
- void LoadCameraCurve(KFbxAnimLayer* pAnimationLayer , KFbxCamera* pCamera , StackTimeSpan& timeSpan)
- {
- if(pCamera == NULL)
- {
- return;
- }
- // 通過FBX的屬性對象而獲取其所對應的Animation Curve
- KFbxAnimCurve* pCameraAttriAnimCurve = pCamera->PixelAspectRatio.GetCurve<KFbxAnimCurve>(pAnimationLayer);
- // 判斷當前的屬性是否含有可變的Animation值
- if(pCameraAttriAnimCurve)
- {
- KTime keyTimer;
- unsigned long millseconds;
- for(UINT i = 0 ; i < timeSpan.mKeyNums ; ++i)
- {
- millseconds = timeSpan.mStart + (float)i * timeSpan.mStep;
- keyTimer.SetMilliSeconds(millseconds);
- // 計算Camera的某屬性在當前時刻所對應的值
- pCameraAttriAnimCurve->Evaluate(keyTimer);
- }
- }
- }
上述代碼通過PixelAspectRatio的屬性對象加載了其不同時刻下的動畫值,其它的屬性的動畫讀取也可以用類似的操作實現。
7.2 動畫驅動
加載了上述的動畫數據以后,即 可以使用其來驅動模型中的直接動畫相關部分,如Camera、Light、Skeleton等。由之前的代碼可知,在加載動畫數據時我們使用了當前 Node的指針,因而就可以用它在加載動畫時存儲其它的一些額外信息使這些動畫數據與對應的Camera、Light、Skeleton等部件進行關聯 (比如Node的指針,或是Node的Name等),從而可以從動畫管理器中隨時查得到某結點在指定時刻位置上的動畫數據。該部分可以根據具體的實現采取 適宜的操作即可完成。
最后,帶有動畫驅動的Skeleton渲染效果如下列圖所示(Camera,Light的動畫效果木有繪出):
8. 骨骼蒙皮動畫
骨骼蒙皮動畫是當前游戲引擎中最常用的一種動畫方式,關 於其基本原理網絡上的資料較多,關於到涉及的其它較復雜操作,如插值、融合等在這里也就先不再討論了,而且其實現方式也與具體引擎的動作管理系統相關;在 這里就主要簡單介紹一下如何從FBX里加載骨骼以及蒙皮信息並完成最基本的蒙皮動畫效果。骨骼動畫的實現主要包括骨骼的驅動和蒙皮兩部分操作,骨骼的驅動 在前一篇中介紹動畫數據的加載時已經完成了,接下來就是對於Mesh與Skeleton之間的Skinning操作。
我們知道,骨骼動畫其實就是通過更新較少量的 Skeleton,進而實現對關聯到這些骨骼上的Mesh的更新,在每幀間都進行這樣的更新並做合適的插值與融合就可以得到平滑流暢的動作效果了。通過前 面基本幾何和動畫數據(Skeleton和Mesh)的加載已經有了這兩部分必要信息,接下來就需要對兩者進行關聯從而實現Skinning時的正確映 射。這一部分數據的讀取其實還是以Mesh為單位進行的,其層次關系結構圖如下所示:
其中的Mesh可從當前屬性為eMESH的Node結點 中獲得(與讀取幾何網格數據相同),其可能是構成整個模型的網格的一小部分(Sub-Mesh)。若當前的Mesh中含有相應的蒙皮動畫數據,則可以從其 中讀取出全部的Vertex到Skeleton的映射信息。Mesh中的蒙皮數據由一個或多個KFbxDeformer來管理,KFbxDeformer 是類型為KFbxTakeNodeContainer的一個對象。每個Deformer管理當前Mesh中的部分頂點到Skeleton的映射關系,而這 種映射關系的組織方式又分為兩種不同的形式,因而就有了派生自Deformer的KFbxSkin和KFbxVertexCacheDeformer(一 般情況下只需考慮KFbxSkin的方式)。每個Skin(Deformer)中可能對應到多個頂點,這些頂點又可能映射到多個Skeleton,在 Skin(Deformer)中的每個Skeleton對應着一個Cluster。如此一來,通過在每個Cluster(->Skeleton)中 尋找其所影響到的Vertex,得到相應的聯接信息如映射Matrix、骨骼Weight等並做相應的存儲即可完成Skeleton到Mesh之間的映射 蒙皮。另外注意:Vertex和Skeleton之間的關系是多對多,即一個Vertex可能受多個Skeleton影響,而一個Skeleton又可能 影響到多個Vertex;這些關系在設計數據結構時就應該有所注意。該部分的代碼大體如下所述:
- void AssociateSkeletonWithCtrlPoint(KFbxMesh* pMesh , CSkeletonMgr* pSkeletonMgr , List<VertexSkeletonList>& ctrlPointSkeletonList)
- {
- if(!pMesh || !pSkeletonMgr)
- {
- return;
- }
- int ctrlPointCount = pMesh->GetControlPointsCount();
- int deformerCount = pMesh->GetDeformerCount();
- // 初始化相應的列表
- ctrlPointSkeletonList.SetCapacity(ctrlPointCount);
- ctrlPointSkeletonList.setListSize(ctrlPointCount);
- KFbxDeformer* pFBXDeformer;
- KFbxSkin* pFBXSkin;
- for(int i = 0 ; i < deformerCount ; ++i)
- {
- pFBXDeformer = pMesh->GetDeformer(i);
- if(pFBXDeformer == NULL)
- {
- continue;
- }
- // 只考慮eSKIN的管理方式
- if(pFBXDeformer->GetDeformerType() != KFbxDeformer::eSKIN)
- {
- continue;
- }
- pFBXSkin = (KFbxSkin*)(pFBXDeformer);
- if(pFBXSkin == NULL)
- {
- continue;
- }
- AssociateSkeletonWithCtrlPoint(pFBXSkin , pSkeletonMgr , ctrlPointSkeletonList);
- }
- }
- void AssociateSkeletonWithCtrlPoint(KFbxSkin* pSkin , CSkeletonMgr* pSkeletonMgr , List<VertexSkeletonList>& ctrlPointSkeletonList)
- {
- if(!pSkin || !pSkeletonMgr)
- {
- return;
- }
- KFbxCluster::ELinkMode linkMode = KFbxCluster::eNORMALIZE;
- KFbxCluster* pCluster;
- KFbxNode* pLinkNode;
- int skeletonIndex;
- CSkeleton* pSkeleton;
- KFbxXMatrix transformMatrix , transformLinkMatrix;
- int clusterCount = pSkin->GetClusterCount();
- // 處理當前Skin中的每個Cluster(對應到Skeleton)
- for(int i = 0 ; i < clusterCount ; ++i)
- {
- pCluster = pSkin->GetCluster(i);
- if(!pCluster)
- {
- continue;
- }
- pLinkNode = pCluster->GetLink();
- if(!pLinkNode)
- {
- continue;
- }
- // 通過Skeleton管理器搜索到當前Cluster所對應的Skeleton,並與Cluster進行關聯
- skeletonIndex = pSkeletonMgr->FindSkeleton(pLinkNode->GetName());
- // ... //關聯Skeleton與Cluster
- if(skeletonIndex < 0)
- {
- continue;
- }
- pSkeleton = pSkeletonMgr->GetSkeleton(skeletonIndex);
- // 得到每個Cluster(Skeleton)所對應的Transform和TransformLink矩陣,后面具體說明
- pCluster->GetTransformMatrix(transformMatrix);
- pCluster->GetTransformLinkMatrix(transformLinkMatrix);
- // 其它適宜的操作,將Transform、TransformLink轉換為映射矩陣並存儲到相應的Skeleton中
- // ...
- int associatedCtrlPointCount = pCluster->GetControlPointIndicesCount();
- int* pCtrlPointIndices = pCluster->GetControlPointIndices();
- double* pCtrlPointWeights = pCluster->GetControlPointWeights();
- int ctrlPointIndex;
- // 遍歷當前Cluster所影響到的每個Vertex,並將對相應的信息做記錄以便Skinning時使用
- for(int j = 0 ; j < associatedCtrlPointCount ; ++j)
- {
- ctrlPointIndex = pCtrlPointIndices[j];
- ctrlPointSkeletonList[ctrlPointIndex].AddSkeleton(skeletonIndex , pCtrlPointWeights[j]);
- }
- }
- }
上述代碼只是完整代碼的一部分,因其中涉及的大多數操作都與具體的實現系統相關,這里只列出部分以供參考而己。其中有兩個操作
pCluster->GetTransformMatrix(transformMatrix);
pCluster->GetTransformLinkMatrix(transformLinkMatrix);
需要特別說明一下,兩個操作分別得到兩個Matrix,前者transformMatrix(記為Mt)用來描述當前映射時刻(初始的映射狀態下)Mesh的變換矩陣(頂點的變換矩陣),
后者transformLinkMatrix(記為Mtl)用來描述當前映射時刻Bone的變換矩陣(可以參考kfbxcluster.h中的說明)。假設通過當前的Cluster可以關聯頂點V和骨骼B,而其對應的空間變換矩陣分別為MV、MB,因而有
MV = Mt; MB = Mtl
而在Mesh到Skeleton的蒙皮中需要由Skeleton的空間位置變換得到Mesh(頂點)的空間位置,所以就需要這樣一個變換矩陣M使得
通過簡單的變換即可得到
該M在動畫更新時就可以用來做Skeleton到Mesh之間的映射計算。
然后,即可以通過Skeleton的更新而完成對Mesh的更新,進而得到對整個模型的動畫。比如下列圖所示的一套動作:
9. 其它一些問題
雖然FBX SDK提供了對FBX模型的很友好的操作接口,但是目前的發布版本也有一些相應的問題:
- FBX SDK提供的FBXImporter目前不支持中文路徑,因而提供的fbx源文件地址中應不含有中文字符。
- 3D Max或Maya中的FBX導出插件計算得到的Tangent會一些問題,特別是在那些具有對稱屬性UV的部位。
- 導出的具有Smooth特性的Normal也會在某些網格接口處出現不平滑的現象。
后兩個問題某些情況下的影響會比較嚴重,但是既然已經將原始的幾何數據加載到自己的引擎中了,因而也就可以在引擎中對Tangent與Normal進行再計算。
前述內容介紹了使用FBX SDK來對FBX進行加載時涉及到的比較常見的操作,如加載網格、材質以及動畫等,也給出了部分實現的代碼,但畢竟不同的系統對各種資源(如 Animation、Skeleton、Material等)有不同的管理方法,代碼也不能完全直接使用,適宜地修改是必不可少的。而且其中的錯誤也是難 免的,所以上述介紹內容只作為參考,具體的實現還需要好好研究與參考Autodesk的相關doc。
最后,歡迎交流與討論~~