FBX导入及转化 3dmax导入ue4 fbx插件

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


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

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

vertex { positiondata…..}

normal {normaldata……}

UV {UV data……}

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

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

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

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

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

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

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

Initinitsdk
KFbxScene *scene;
KFbxSdkManager *sdkManager;

voidFbxImporter::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还能导入一些其他格式的文件。实例代码如下:

LoadfilevoidFbxImporter::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,而不是文件的角度。如果你对scenegraph/tree有所了解的话,fbx其实就是一个scenegraph/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对象。

visitnodevoidFbxImporter::WalkHierarchy()
{
KFbxNode*root = scene->GetRootNode();
for (int i=0;i<</SPAN>root->GetChildCount();i++)
{
WalkHierarchy(root->GetChild(i),0,&(this->root));
}
}

voidFbxImporter::WalkHierarchy(KFbxNode*fbxNode, int depth)
{
KFbxNodeAttribute*nodeAtt = fbxNode->GetNodeAttribute();
if(nodeAtt == NULL)
{
ss<<"Name:"<<fbxNode->GetName()<<"NodeType:"<<"None";
}
else
{
switch(nodeAtt->GetAttributeType())
{
caseKFbxNodeAttribute::eMARKER:break;
case KFbxNodeAttribute::eSKELETON:break;
case KFbxNodeAttribute::eMESH:ProcessMesh(nodeAtt) break;
case KFbxNodeAttribute::eCAMERA: break;
case KFbxNodeAttribute::eLIGHT: break;
case KFbxNodeAttribute::eBOUNDARY:break;
case KFbxNodeAttribute::eOPTICAL_MARKER:break;
case KFbxNodeAttribute::eOPTICAL_REFERENCE:break;
case KFbxNodeAttribute::eCAMERA_SWITCHER:break;
case KFbxNodeAttribute::eNULL: break;
case KFbxNodeAttribute::ePATCH: break;
FBX导入及转化 3dmax导入ue4 fbx插件
case KFbxNodeAttribute::eNURB: break;
case KFbxNodeAttribute::eNURBS_SURFACE:break;
case KFbxNodeAttribute::eNURBS_CURVE:break;
case KFbxNodeAttribute::eTRIM_NURBS_SURFACE:break;
case KFbxNodeAttribute::eUNIDENTIFIED:
}

}

//processchildren
for (int i=0;i<</SPAN>fbxNode->GetChildCount();i++)
{
WalkHierarchy(fbxNode->GetChild(i),depth+1);
}
}


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

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

meshinfovoidProcessMesh(KFbxNodeAttribute*nodeAtt)
{
if(nodeAtt->GetAttributeType() == KFbxNodeAttribute::eMESH)
{
KFbxMesh *mesh= dynamic_cast<</SPAN>KFbxMesh*>(nodeAtt);
if(!mesh->IsTriangleMesh())
{
KFbxGeometryConverterconverter(sdkManager);
// #1
converter.TriangulateInPlace(fbxNode);
mesh = dynamic_cast<</SPAN>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,对triangelist来说,其实就是GetPolygonCount() * 3;

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

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

savedatasavedata
save model
vertex = mesh->GetControlPoints();
vertexCount =mesh-> GetControlPointsCount();
..........

voidSaveData(const char *fileName,KFbxVector4* vertex,int vertexCount,int *indices,int indicesCount)
{
//convert kfbxvector4[] to float[],notice weonly use the first 3 element(x,y,z) of akfbxvector4
float *verts = new float[vertexCount*3];
float *pV = verts;
for (int i=0;i<</SPAN>vertexCount;i++)
{
*pV = static_cast<</SPAN>float>(vertex[i][0]);
pV++;
*pV = static_cast<</SPAN>float>(vertex[i][1]);
pV++;
*pV = static_cast<</SPAN>float>(vertex[i][2]);
pV++;
}

//createfile
std::ofstreamfs(fileName,std::ios_base::out|std::ios_base::binary);

//writegeometryInfo: vertex and index count;
int geometryInfo[2] = {vertexCount,indicesCount};
fs.write(reinterpret_cast<</SPAN>const char*>(geometryInfo),sizeof(int)*2);

//writevertex data
fs.write(reinterpret_cast<</SPAN>const char*>(verts),sizeof(float)*vertexCount*3);

short*sIndices = NULL;
//convert to 16 bit index if possible to savememory
if(vertexCount <</SPAN> 65535)
{
sIndices =new short[indicesCount];
short *currentIndex = sIndices;
for (int i=0;i<</SPAN>indicesCount;i++,currentIndex++)
{
*currentIndex = indices[i];
}
//write index data to file
fs.write(reinterpret_cast<</SPAN>const char*>(sIndices),sizeof(short)*indicesCount);
}
else
{
fs.write(reinterpret_cast<</SPAN>const char*>(indices),sizeof(int)*indicesCount);
}

fs.close();
delete[] verts;
if(*sIndices != NULL)
{
delete[] sIndices;
}
}

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

rendermodelreadmodel
classModelReader
{
intvertexCount;
int indexCount;

VertexBuffer mVertexBuffer;
IndexBuffer mIndexBuffer;

publicvoid LoadFile(GraphicsDevicegraphics,stringfileName)
{
//open file
FileStream fs = new FileStream(fileName, FileMode.Open);
BinaryReader br =new BinaryReader(fs);

vertexCount =br.ReadInt32();
indexCount =br.ReadInt32();

//readvertex data
VertexPositionOnly[] verts = new VertexPositionOnly[vertexCount];
for(int i = 0;i <</SPAN>vertexCount; i++)
{
verts[i] =new VertexPositionOnly(new Vector3(
br.ReadSingle(), br.ReadSingle(), br.ReadSingle()));
}
//create vertex buffer
VertexDeclaration vd = new VertexDeclaration(new VertexElement(0,VertexElementFormat.Vector3,VertexElementUsage.Position,0));
mVertexBuffer =new VertexBuffer(graphics, vd, vertexCount,BufferUsage.None);
mVertexBuffer.SetData(verts);

//readindex data
short[] indices = new short[indexCount];
for (int i = 0;i <</SPAN>indexCount; i++)
{
indices[i] =br.ReadInt16();
}
//create index buffer
mIndexBuffer = new IndexBuffer(graphics,IndexElementSize.SixteenBits, indexCount, BufferUsage.None);
mIndexBuffer.SetData(indices);

HashSet<</SPAN>short> hash = new HashSet<</SPAN>short>();
br.Close();
fs.Close();
}

publicvoid Draw(GraphicsDevice graphics)
{
graphics.SetVertexBuffer(mVertexBuffer);
graphics.Indices =mIndexBuffer;
graphics.DrawIndexedPrimitives(PrimitiveType.TriangleList,0, 0,vertexCount, 0,indexCount /3);
}
}

接下来我们继续讨论如何取得normal,tangent,binormal和uv信息。先介绍一些关于KFbxLayer对象的概念。KFbxLayer对象是一个容器,对mesh来说,它包含了除控点,多边形信息以外大部分数据,比如normal,tangent,vertexcolor,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有效,请详细参考文档。通常对于有hardedge的模型来说,MappingMode只能是eBY_POLYGON_VERTEX,而平滑模型则可以是eBY_CONTROL_POINT。

ReferenceMode定义了如何访问相关的数据。同样举例来说,每个KFbxLayerElement内部通常可能包含两个数组,分别称为DirectArray和IndexArray。如果referencemode为eDIRECT,则第i个控点相对的element元素就在DirectArray的第i位置(第i个控点的normal在KFbxLayerElementNormal.DirectArray[i]中),此时IndexArray为空。eINDEX_TO_DIRECT通常和eBY_POLYGON_VERTEX一起使用,因为一个控点可能对应多个值,所以这时必须用多边形顶点索引(也就是GetPolygonVertexCount()返回的值)来获得某个多边形顶点所对应的值:KfbxLayerElement.DirectArray[IndexArray[vertexIndex]]。下面代码演示了如何遍历所有layer,获得每个顶点/控点对应的法线:

Get normalKFbxLayerElementNormal*leNormal=pMesh->GetLayer(0)->GetNormals();
if(leNormal)
{
switch(leNormal->GetMappingMode())
{
caseKFbxLayerElement::eBY_CONTROL_POINT:
switch(leNormal->GetReferenceMode())
{
caseKFbxLayerElement::eDIRECT:
KFbxVector4normal=leNormal->GetDirectArray().GetAt(lControlPointIndex));
break;
caseKFbxLayerElement::eINDEX_TO_DIRECT:
{
intid=leNormal->GetIndexArray().GetAt(lControlPointIndex);
KFbxVector4normal=leNormal->GetDirectArray().GetAt(id));
}
break;
default:
break;//otherreferencemodesnotshownhere!
}
break;

caseKFbxLayerElement::eBY_POLYGON_VERTEX:
{
//polygonID=triange1,2,3.....n
//positionId=1,2,3fortriange//vertex!!!!
intvertexIndex=pMesh->GetPolygonVertex(polygonID,positionId);
switch(leNormal->GetReferenceMode())
{
caseKFbxLayerElement::eDIRECT:
caseKFbxLayerElement::eINDEX_TO_DIRECT:
{
Display2DVector(header,leUV->GetDirectArray().GetAt(vertexIndex))
}
break;
default:
break;//otherreferencemodesnotshownhere!
}
}
break;
}
}

为了避免混淆,再强调一下控点和顶点的区别。首先,控点只包含位置信息,顶点则包含了位置,法线,纹理坐标等信息。如果mesh中所有layer中的所有元素MappingMode都是eBY_CONTROL_POINT,则控点数量和顶点一一对应。如果是eBY_POLYGON_VERTEX,则有可能需要分裂控点。比如一个控点被n个多边形共享,则对应着n条法线,需要分裂成n个顶点,但是,控点所对应的n条法线中有些可能是相同的(nonehard edge)------所以eBY_POLYGON_VERTEX通常和eINDEX_TO_DIRECT配合使用-----因此最终分裂出来的顶点数有可能小于n。Tangent,bionormal,vertexcolor的访问与此类似,而且一般来说,只需要读出第一个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是否是重复:

代码foreachlayer
{
intuvSetCount=layer->GetUVSetCount();
if(uvSetCount>0)
{
//iteratealluvchannelindexedbyelement_texture_type
for(inttextureIndex=KFbxLayerElement::eDIFFUSE_TEXTURES;textureIndex<</SPAN>KFbxLayerElement::eLAST_ELEMENT_TYPE;textureIndex++)
{
KFbxLayerElement*uvElement=layer->GetUVs(KFbxLayerElement::ELayerElementType(textureIndex));
if(!uvElement)
continue;
uvSetsName=uvElement->GetName();
if(!CheckUVSetsNameExists(uvSetsName))
{
//processuvdata
}}
}}

GetUVs (KFbxLayerElement::ELayerElementType type)返回对应type类型的UV,不存在则返回NULL。这里的type是KFbxLayerElement::ELayerElementType枚举中eDIFFUSE_TEXTURES到eDISPLACEMENT_TEXTURES之间的值。可以把这个枚举理解为UV的通道标识符,比如GetUVs(eDIFFUSE_TEXTURES)返回diffusetexture通道的纹理,注意,这里eDIFFUSE_TEXTURES并不指这组UV只能用于diffusemap,而只是一个标识符!对于只有一组uv的模型来说,纹理数据通常都在这个通道中。

我们已经基本解析出模型中的几何信息。接下来看如何获得材质,特别是纹理信息。与前面的元素不同,material不保存在layer中,而是保存在node里,一个node可以包含多个材质。SDK文档中关于材质,纹理之间关系的介绍非常让人迷惑,有些接口也很常奇怪。虽然Layer中有一个名为GetMaterials()的方法,但其返回的KFbxLayerElementMaterial对象中GetDirectArray()只会返回空值,也就是说无法通过它获得真正表示材质的KFbxSurfaceMaterial对象。下面的代码展示了如何取得材质,以及相应的数值类参数。

代码for(intlIndex=0;lIndex<</SPAN>lNode->GetMaterialCount()lIndex++)
{
KFbxSurfaceMaterial*lMaterial=lNode->GetMaterial(lIndex)
//converttopropersubtype
if(lMaterial->GetClassId().Is(KFbxSurfaceLambert::ClassId))
{
((KFbxSurfaceLambert*)lMaterial)->GetAmbientColor()
((KFbxSurfaceLambert*)lMaterial)->GetDiffuseColor()
...
}
elseif(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 moretextureProperty, identified byKFbxLayerElement::ELayerElementType

textureProperty : contains one or moretexture/layerTexture;

layerTexture: contains more than onetexture

下面的代码展示了如何获得纹理信息。

Get TexturevoidFbxImporter::ParseMaterial(KFbxNode*fbxNode,HamsterEngine::Node*node)
{
//iterateallmaterial
for(inti=0;i<</SPAN>fbxNode->GetMaterialCount();i++)
{
KFbxSurfaceMaterial*mat=fbxNode->GetMaterial(i);
if(mat)
{
//iteratealltexturechannel
for(inttextureIndex=0;textureIndex<</SPAN>KFbxLayerElement::LAYERELEMENT_TYPE_TEXTURE_COUNT;textureIndex++)
{
//getcurrenttexturechannel
KFbxPropertyproperty=mat->FindProperty(KFbxLayerElement::TEXTURE_CHANNEL_NAMES[textureIndex]);
//haschannel?
if(property.IsValid())
{
//layeredtexture?
if(layerCount>property.GetSrcObjectCount(KFbxLayeredTexture::ClassId);)
{
//iteratealllayeredtexture
for(intlayerId=0;layerId<</SPAN>layerCount;layerId++)
{
KFbxLayeredTexture*layeredTex=KFbxCast<</SPAN>KFbxLayeredTexture>(property.GetSrcObject(KFbxLayeredTexture::ClassId,layerId));
intnumTex=layeredTex->GetSrcObjectCount(KFbxTexture::ClassId);
//iteratealltextureinthislayer
for(inttexId=0;texId<</SPAN>numTex;texId++)
{
KFbxTexture*tex=KFbxCast<</SPAN>KFbxTexture>(layeredTex->GetSrcObject(KFbxTexture::ClassId,texId));
if(tex)
{
std::cout<<"Texturename:"<<tex->GetName()
<<"fileName:"<<tex->GetFileName()
<<"uvSet: "<<tex->UVSet.Get();
}
}
}
}
else
{
intnumTextures=property.GetSrcObjectCount(KFbxTexture::ClassId);
////iterateallsimpletexture
for(inttexId=0;texId<</SPAN>numTextures;texId++)
{
KFbxTexture*tex=KFbxCast<</SPAN>KFbxTexture>(property.GetSrcObject(KFbxTexture::ClassId,texId));
if(tex)
{
std::cout<<"Texturename:"<<tex->GetName()
<<"fileName:"<<tex->GetFileName()
<<"uvSet:"<<tex->UVSet.Get();
}
}
}
}
}
}
}
}

KFbxTexture.UVSet.Get()返回当前纹理所绑定的UVSet名称,可以由此获得纹理和UV的绑定关系。

之前所说的KFbxLayerElementMaterial并不是完全没用,还必须用它获得mappingmode,对材质来说,最常见的两个值是:eALL_SAME和eBY_POLYGON,前者表示整个mesh的材质都相同,没太多可说的;后者表示材质只应用到mesh中的部分多边形,这就比较麻烦了,上图中第二个文件就是这种情况。不同材质意味着纹理或者shader改变,我们必须把eBY_POLYGON的mesh根据材质划分为不同子mesh才能导入到DirectX程序中。幸运的是sdk提供了这样的函数,让我们不用自己计算:

KFbxGeometryConverterlConverter(pSdkManager)
lConverter.SplitMeshPerMaterial(lMesh)

注意:

before and after the call to SplitMeshPerMaterial, you should see adifference in the number: there will be the old mesh, plus one newmesh (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 workon meshes with material mapped per-vertex/per-edge/etc.It willcreate as many meshes on output that there are materials applied toit.If one mesh have some polygons with material A, some polygonswith material B,and some polygons with NO material, it shouldcreate 3 meshes after calling this function.The newly createdmeshes should be attached to the same KFbxNode that hold theoriginal KFbxMesh.The original KFbxMesh STAY UNCHANGED.Now, the newmesh will have Normals, UVs, vertex color, material andtextures.

以上介绍了模型导入时从fbx文件中提取,常见数据的方法,也还有很多方面没有讨论,比如skininfo和animation。对skin来说,相应的权重等信息保存在KfbxDeformer对象中,可以通过KFbxNode获得。至于动画目前我暂时还没有时间研究,如果有好心人实现了,不妨也写篇教程顺便告诉我一声:)。另外最先说过,fbx是一种可扩展的格式,可以通过UserProperties属性添加很多自定义属性,这里介绍了如何在maya和max中添加自定义属性,SDK中的UserPropertiessample则介绍了如何取得这些属性。文章中所涉及的函数只介绍了基本用法,详细信息请参考文档。另外文档中虽然没有太多示例代码,但sdk中附带的ImportSceneSample是一个非常好的例子,展示了解析fbx文件的方方面面,值得仔细研究。

转载于:http://www.cnblogs.com/clayman/archive/2010/12/11/1902782.html

  

爱华网本文地址 » http://www.aihuau.com/a/25101015/278086.html

更多阅读

“小课题研究”——操作篇 小学语文课题研究题目

“小课题研究”——操作篇关于“小课题研究”的方案和开题、结题报告的撰写要求目录一、“小课题研究”方案的设计与撰写二、案例——《初中数学学困生心理问题表现、成因及转化教育的个案研究》课题实施方案三、课题研究开题

3dmax渲染教程 3dmax渲染技巧

3DMAX→VRAY渲染器正确出图流程,以便大家提高渲图效率及质量!3DMAX→VRAY渲染器正确出图流程,以便大家提高渲图效率及质量! ————为改善许多朋友做图时在流程方面的概念不足导致效果不理想、效率不高等现象---------------------

中心小学教师备课规范要求 中小学教师备课要求

中心小学关于教师备课事宜的若干要求一、备课内容:包括学期教学计划和课时教案。1、学期教学计划包括:①班级情况分析(兼教班级都要写):班级基本情况,学习习惯,学困生情况及转化措施等;②教材简析:全册教授的内容,包含知识点地位和上下联系;③

声明:《FBX导入及转化 3dmax导入ue4 fbx插件》为网友抬头那曙光分享!如侵犯到您的合法权益请联系我们删除