Working with FBX SDK (2)
Working with FBX SDK (2)
僅供個人學習使用,請勿轉載,勿用于任何商業用途
作者:clayman
?
更新2012.5:? *****fbx sdk 2013以后的版本做了大幅更新,大量API都進行了修改和更名,本文不再適用,請參考新版SDK文檔******
?? 上一篇文章介紹了fbx sdk的基本用法,接下來我們繼續討論如何取得normal,tangent,binormal和uv信息。先介紹一些關于KFbxLayer對象的概念。KFbxLayer對象是一個容器,對mesh來說,它包含了除控點,多邊形信息以外大部分數據,比如normal,tangent,vertex color,uv等等。一個mesh可以包含多個KFbxLayer對象,不同layer之間的元素類型,個數通常都不相同。下面是一個簡單的mesh結構關系:
mesh ---- layer 0 { KFbxLayerElementNormal, KFbxLayerElementTangent, KFbxLayerElementUV…..}
????? |
????? |------layer 1 {KFbxLayerElementUV………}
????? |??
????? |-- ………………..
????? |
????? |-- layer n
?
???????? 雖然FBX允許有多層layer,但很多軟件包括maya和max都只處理包含在第一個layer中的normal等數據! 每種保存在KFbxLayer的元素都繼承于KFbxLayerElement,比如KFbxLayerElementNormal對應normal數據,KFbxLayerElementTangent對應tangent的數據??梢酝ㄟ^KFbxLayer中定義的各種Get函數,返回需要的KFbxLayerElement,如果為空,則說明當前layer中沒有這種元素。KFbxLayerElement還中包含了兩個非常重要的屬性KFbxLayerElement::EMappingMode和KFbxLayerElement::EReferenceMode。
???? MappingMode定義了當前類型的元素如何映射到mesh上。舉例來說,對于KFbxLayerElementNormal,eBY_POLYGON_VERTEX表示如果一個頂點被n個多邊形共享,那么這個頂點就有n條法線與之相對應;eBY_CONTROL_POINT則表示每個頂點無論被幾個多邊形共享,都只有一條normal;eBY_POLYGON則表示構成多邊形的n個頂點只對應著一條normal。某些MappingMode只對特定的KFbxLayerElement有效,請詳細參考文檔。通常對于有hard edge的模型來說,MappingMode只能是eBY_POLYGON_VERTEX,而平滑模型則可以是eBY_CONTROL_POINT。
?
????? ReferenceMode定義了如何訪問相關的數據。同樣舉例來說,每個KFbxLayerElement內部通??赡馨瑑蓚€數組,分別稱為DirectArray和IndexArray。如果reference mode為eDIRECT,則第i個控點相對的element元素就在DirectArray的第i位置(第i個控點的normal在KFbxLayerElementNormal.DirectArray[i]中) ,此時IndexArray為空。eINDEX_TO_DIRECT通常和eBY_POLYGON_VERTEX一起使用,因為一個控點可能對應多個值,所以這時必須用多邊形頂點索引(也就是GetPolygonVertexCount()返回的值)來獲得某個多邊形頂點所對應的值: KfbxLayerElement.DirectArray[ IndexArray[vertexIndex]]。下面代碼演示了如何遍歷所有layer,獲得每個頂點/控點對應的法線:
Get normal KFbxLayerElementNormal*?leNormal?=?pMesh->GetLayer(0)->GetNormals();if?(leNormal)
{
????switch?(leNormal->GetMappingMode())
????{
????????case?KFbxLayerElement::eBY_CONTROL_POINT:
????????????switch?(leNormal->GetReferenceMode())
????????????{
????????????case?KFbxLayerElement::eDIRECT:
????????????????KFbxVector4?normal?=?leNormal->GetDirectArray().GetAt(lControlPointIndex));
????????????????break;
????????????case?KFbxLayerElement::eINDEX_TO_DIRECT:
????????????????{
????????????????????int?id?=?leNormal->GetIndexArray().GetAt(lControlPointIndex);
????????????????????KFbxVector4?normal?=?leNormal->GetDirectArray().GetAt(id));
????????????????}
????????????????break;
????????????default:
????????????????break;?//?other?reference?modes?not?shown?here!
????????????}
????????????break;
????????case?KFbxLayerElement::eBY_POLYGON_VERTEX:
????????????{
????????????????//polygonID?=?triange?1,2,3.....n
????????????????//positionId?=?1,2,3?for?triange? ????????????????//vertex!!!!?
????????????????int?vertexIndex?=?pMesh->GetPolygonVertex(polygonID,positionId);
????????????????switch?(leNormal->GetReferenceMode())
????????????????{
????????????????????case?KFbxLayerElement::eDIRECT:
????????????????????case?KFbxLayerElement::eINDEX_TO_DIRECT:
????????????????????????{
????????????????????????????Display2DVector(header,?leUV->GetDirectArray().GetAt(vertexIndex))
????????????????????????}
????????????????????????break;
????????????????????default:
????????????????????????break;?//?other?reference?modes?not?shown?here!
????????????????}
????????????}
????????????break;
????}
}
?
??????? 為了避免混淆,再強調一下控點和頂點的區別。首先,控點只包含位置信息,頂點則包含了位置,法線,紋理坐標等信息。如果mesh中所有layer中的所有元素MappingMode都是eBY_CONTROL_POINT,則控點數量和頂點一一對應。如果是eBY_POLYGON_VERTEX,則有可能需要分裂控點。比如一個控點被n個多邊形共享,則對應著n條法線,需要分裂成n個頂點,但是,控點所對應的n條法線中有些可能是相同的(none hard edge)------所以eBY_POLYGON_VERTEX通常和eINDEX_TO_DIRECT配合使用----- 因此最終分裂出來的頂點數有可能小于n。Tangent,bionormal,vertex color的訪問與此類似,而且一般來說,只需要讀出第一個layer中的數據即可。如何根據不同的normal等信息分裂控點,組合頂點,需要我們自己來實現,這里不詳細討論。
?
???????? UV的訪問方式和上面提到的方法類似,但稍稍有些區別。前面說過,雖然每個mesh都允許多個layer,但通常只會有一組normal,tangent等數據,uv則可能有多組(比如一組uv用于普通貼圖,另外一組用于lightmap),并且有可能保存在同一layer中,也有可能分別保存在多個layer中。但是fbx文件中有一個奇怪的問題,很多模型雖然只有一組UV,但會被識別出多組UV出現在不同layer中,并且不是每個layer中存在的數據都相同或者有效!!
????????
?????? 上圖中,第一個文件是正確的,2組UV分別在兩個layer中;下面的文件則多出了2層只含UV的layer,注意多余的uv名稱都是map1.?FBX論壇上好像有人也遇到了同樣的問題,不過都沒有官方的解釋,文檔中也沒有討論。解決方法是我們可以通過檢查每組UV的名稱來確定某組UV是否是重復:
代碼 foreach?layer{
????int?uvSetCount?=?layer->GetUVSetCount();
????if(uvSetCount?>?0)
????{
????????//iterate?all?uv?channel?indexed?by?element_texture_type
????????for?(int?textureIndex?=?KFbxLayerElement::eDIFFUSE_TEXTURES;textureIndex<KFbxLayerElement::eLAST_ELEMENT_TYPE;textureIndex++)
??????? {
??????????? KFbxLayerElement?*uvElement?=?layer->GetUVs(KFbxLayerElement::ELayerElementType(textureIndex));
??????????? if(!uvElement)
??? ????????????continue;
??????????? uvSetsName?=??uvElement->GetName();
??????????? if(!CheckUVSetsNameExists(uvSetsName))
??????????? {
????????????? //process?uv?data
??????????? } ???????? }
????} }
?
???????? GetUVs (KFbxLayerElement::ELayerElementType type) 返回對應type類型的UV,不存在則返回NULL。這里的type是KFbxLayerElement::ELayerElementType枚舉中eDIFFUSE_TEXTURES 到eDISPLACEMENT_TEXTURES 之間的值。可以把這個枚舉理解為UV的通道標識符,比如GetUVs(eDIFFUSE_TEXTURES)返回diffuse texture通道的紋理,注意,這里eDIFFUSE_TEXTURES并不指這組UV只能用于diffuse map,而只是一個標識符!對于只有一組uv的模型來說,紋理數據通常都在這個通道中。
?
???????? 我們已經基本解析出模型中的幾何信息。接下來看如何獲得材質,特別是紋理信息。與前面的元素不同,material不保存在layer中,而是保存在node里,一個node可以包含多個材質。SDK文檔中關于材質,紋理之間關系的介紹非常讓人迷惑,有些接口也很常奇怪。雖然Layer中有一個名為GetMaterials()的方法,但其返回的KFbxLayerElementMaterial對象中GetDirectArray()只會返回空值,也就是說無法通過它獲得真正表示材質的KFbxSurfaceMaterial對象。下面的代碼展示了如何取得材質,以及相應的數值類參數。
代碼 for(int?lIndex=0;?lIndex?<?lNode->GetMaterialCount()?lIndex++){
????KFbxSurfaceMaterial?*lMaterial?=?lNode->GetMaterial(lIndex)
????//convert?to?proper?sub?type
????if(lMaterial->GetClassId().Is(KFbxSurfaceLambert::ClassId)?)
????{
????????((KFbxSurfaceLambert?*)lMaterial)->GetAmbientColor()
????????((KFbxSurfaceLambert?*)lMaterial)->GetDiffuseColor()
????????...
????}
????else?if(lMaterial->GetClassId().Is(KFbxSurfacePhong::ClassId))
????{
????????((KFbxSurfacePhong?*)?lMaterial)->GetAmbientColor()
????????((KFbxSurfacePhong?*)?lMaterial)->GetDiffuseColor()
????????...
????}
}?
?
??????? 邏輯上來說,KFbxSurfaceMaterial其實是個抽象類,需要把它轉換為合適的兩個子類,才能得到實際材質參數。紋理則要更特別一些(注意,雖然layer中也有GetTextures(),但我測試的時候總返回空值)。一個材質會包含多個紋理通道,每個通道同樣以KFbxLayerElement::ELayerElementType中關于紋理的枚舉作為標識符,每個通道可以包含多個KFbxTexture或者KFbxLayeredTexture,其中,KFbxTexture就對應著一張紋理,而KFbxLayeredTexture則又包含了多個KFbxTexture對象,類似如下結構:
KFbxSurfaceMaterial : contains one or more textureProperty, identified by KFbxLayerElement::ELayerElementType
textureProperty : contains one or more texture/layerTexture;
layerTexture: contains more than one texture
?
???????? 下面的代碼展示了如何獲得紋理信息。
Get Texture void?FbxImporter::ParseMaterial(KFbxNode*?fbxNode,HamsterEngine::Node?*node){
????//iterate?all?material
????for?(int?i?=?0;i<fbxNode->GetMaterialCount();i++)
????{
????????KFbxSurfaceMaterial?*mat?=?fbxNode->GetMaterial(i);
????????if(mat)
????????{
????????????//iterate?all?texture?channel
????????????for?(int?textureIndex?=?0;textureIndex<?KFbxLayerElement::LAYERELEMENT_TYPE_TEXTURE_COUNT;textureIndex++)
????????????{
????????????????//get?current?texture?channel
????????????????KFbxProperty?property?=?mat->FindProperty(KFbxLayerElement::TEXTURE_CHANNEL_NAMES[textureIndex]);
????????????????//has?channel?
????????????????if(property.IsValid())
????????????????{
????????????????????//layered?texture?
????????????????????if(layerCount?>?property.GetSrcObjectCount(KFbxLayeredTexture::ClassId);)
????????????????????{
????????????????????????//iterate?all?layered?texture
????????????????????????for?(int?layerId=0;layerId<layerCount;layerId++)
????????????????????????{
????????????????????????????KFbxLayeredTexture?*layeredTex?=?KFbxCast<KFbxLayeredTexture>(property.GetSrcObject(KFbxLayeredTexture::ClassId,layerId));
????????????????????????????int?numTex?=?layeredTex->GetSrcObjectCount(KFbxTexture::ClassId);
????????????????????????????//iterate?all?texture?in?this?layer
????????????????????????????for?(int?texId=0;texId<numTex;texId++)
????????????????????????????{
????????????????????????????????KFbxTexture*?tex?=?KFbxCast<KFbxTexture>(layeredTex->GetSrcObject(KFbxTexture::ClassId,texId));
????????????????????????????????if(tex)
????????????????????????????????{
????????????????????????????????????std::cout<<"Texture??name:"<<tex->GetName()
????????????????????????????????????????<<"????fileName:"<<tex->GetFileName()
????????????????????????????????????????<<"????uvSet:"<<tex->UVSet.Get();
????????????????????????????????}
????????????????????????????}
????????????????????????}
????????????????????}
????????????????????else
????????????????????{
????????????????????????int?numTextures?=?property.GetSrcObjectCount(KFbxTexture::ClassId);
????????????????????????////iterate?all?simple?texture
????????????????????????for?(int?texId?=?0;texId<numTextures;texId++)
????????????????????????{
????????????????????????????KFbxTexture?*tex?=?KFbxCast<KFbxTexture>(property.GetSrcObject(KFbxTexture::ClassId,texId));
????????????????????????????if(tex)
????????????????????????????{
????????????????????????????????std::cout<<"Texture??name:"<<tex->GetName()
????????????????????????????????????<<"????fileName:"<<tex->GetFileName()
????????????????????????????????????<<"????uvSet:"<<tex->UVSet.Get();
????????????????????????????}
????????????????????????}
????????????????????}
????????????????}
????????????}
????????}
????}
}
?
??????? KFbxTexture.UVSet.Get()返回當前紋理所綁定的UVSet名稱,可以由此獲得紋理和UV的綁定關系。
?????? 之前所說的KFbxLayerElementMaterial并不是完全沒用,還必須用它獲得mapping mode,對材質來說,最常見的兩個值是:eALL_SAME和eBY_POLYGON,前者表示整個mesh的材質都相同,沒太多可說的;后者表示材質只應用到mesh中的部分多邊形,這就比較麻煩了,上圖中第二個文件就是這種情況。不同材質意味著紋理或者shader改變,我們必須把eBY_POLYGON的mesh根據材質劃分為不同子mesh才能導入到DirectX程序中。幸運的是sdk提供了這樣的函數,讓我們不用自己計算:
KFbxGeometryConverter?lConverter(pSdkManager)lConverter.SplitMeshPerMaterial(lMesh)?
?
?注意:
before and after the call to SplitMeshPerMaterial, you should see a difference in the number: there will be the old mesh, plus one new mesh (node attribute) for each material.
It will work only on mesh that have material mapped “per-face” (Mapping Mode is KFbxLayerElement::eBY_POLYGON). It does NOT work on meshes with material mapped per-vertex/per-edge/etc.It will create as many meshes on output that there are materials applied to it.If one mesh have some polygons with material A, some polygons with material B,and some polygons with NO material, it should create 3 meshes after calling this function.The newly created meshes should be attached to the same KFbxNode that hold the original KFbxMesh.The original KFbxMesh STAY UNCHANGED.Now, the new mesh will have Normals, UVs, vertex color, material and textures.
?
???????? 以上介紹了模型導入時從fbx文件中提取,常見數據的方法,也還有很多方面沒有討論,比如skin info和animation。對skin來說,相應的權重等信息保存在KfbxDeformer對象中,可以通過KFbxNode獲得。至于動畫目前我暫時還沒有時間研究,如果有好心人實現了,不妨也寫篇教程順便告訴我一聲:)。另外最先說過,fbx是一種可擴展的格式,可以通過UserProperties屬性添加很多自定義屬性,這里介紹了如何在maya和max中添加自定義屬性,SDK中的UserProperties sample則介紹了如何取得這些屬性。文章中所涉及的函數只介紹了基本用法,詳細信息請參考文檔。另外文檔中雖然沒有太多示例代碼,但sdk中附帶的ImportScene Sample是一個非常好的例子,展示了解析fbx文件的方方面面,值得仔細研究。
?
?
轉載于:https://www.cnblogs.com/clayman/archive/2010/12/11/1902782.html
總結
以上是生活随笔為你收集整理的Working with FBX SDK (2)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 蘑菇街怎么绑定微信
- 下一篇: c# winform 应用编程代码总结