读取示例
链接:
- 基于FBX SDK的FBX模型解析与加载(一) ——几何网格
- 基于FBX SDK的FBX模型解析与加载 (二)——材质
- 基于FBX SDK的FBX模型解析与加载 (三)——摄像机、灯光、动画
- 基于FBX SDK的FBX模型解析与加载 (四)——骨骼蒙皮动画
初始化KFbxSdkManager、KFbxScene¶
在使用SDK对FBX的处理操作之前,必须先初始化两个FBX对象
- KFbxSdkManager 用来对所有的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));
}
}
- 在FBX的存储中,每个父结点可以包含多个子结点,但每个子结点只有一个根结点,而且这其中的联系是双向的
- 这样很方便,比如在处理Skeleton时就常常需要从子结点中得到父结点的matrix等信息,而这种双向关系使得这些操作很容易实现
【注意】上述代码中有pNode->GetNodeAttribute()
检查操作是必须的,因为并不是所有的结点都有相应的属性(Attribute也是以子结点的方式关联到当前的结点上的,因而可能为空)
加载几何网格¶
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里边得到相应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中的信息,因而增加这样的一个变量来进行记录
读Normal、Tangent、UV等信息的函数其实都差不多
- 首先需要判断有没有相应的Layer关联在当前的Mesh中,若有则获取其地址
- 然后根据不同的映射方式使用不同的方法从内存中读取相应的值即可
读顶点¶
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¶
//ctrlPointIndex点的下标
//
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¶
bool ReadUV(KFbxMesh* pMesh ,
int ctrlPointIndex , //点下标
int textureUVIndex , //纹理下标
int uvLayer , //通道数
D3DXVECTOR2* pUV)
{
if(uvLayer >= 2 || pMesh->GetElementUVCount() <= uvLayer)
{
return false;
}
//得到指定通道的uv值
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;
}
}
加载材质¶
Material是一个模型渲染时必不可少的部分,当然,这些信息也被存到了FBX之中(甚至各种贴图等也可以直接内嵌到FBX内部),就需要从FBX中加载这些信息以完成带有材质的渲染。材质的加载可以与Mesh的加载相结合来完成,但更好的方法是独立进行,这样各模块间的关系更清晰,但这就需要一个额外的操作,那就是关联Mesh与Material。FBX中的材质对象包含了丰富的信息,比如最常规的从Max中可以看到那些材质属性,如ambient、diffuse、specular的color和texture;shininess、opacity值等,更高级一点的属性诸如Effect的参数、源文件等都可以保存。它是尽可能保证从建模工具中导出时不丢失地保存材质信息,但我们在使用时却可以有选择地读取。