FbxSDK使用总结

Fbx文件结构太复杂!FbxSDK太难理解!Fbx官网文档功能介绍太不清晰!FbxSDK中的示例程序太冷门,太不解决问题!

网络上找不到能够解决我的痛点的方法。

有相同烦恼的不只我一个人,一个叫 Tianyu Lang 的歪果仁也发出抱怨,并怒喊:

I have searched almost all the corners on the Internet to clarify things so that I can have a clear 
mapping from FBX SDK's data to what I need in a game engine. Since I don't think anyone has ever 
posted a clear and thorough tutorial on how to convert FBX files to custom formats, I will do it. 
I hope this will help people.
他的怒喊深得我心。本着同样的目的,我要写下这篇文章。(他的文章在这里: 传送门)

这篇文章不是一个FbxSDK的入门文章,而是一个进阶文章。

在写这篇文章之前,我已经研究FbxSDK一段时间了,也写了如下两篇博文,大家对照着阅读。

1,《编程知识汇总--3D模型文件的通用格式:FBX》,我搜集的值得阅读的文章,传送门

2,《FbxSDK官网文档阅读总结》,在四处碰壁后,我尝试阅读官网文档,传送门



1,ControlPoint 与 PolygonVertex

FbxMesh对象有如下4个函数,

FbxMesh::GetControlPointsCount()
FbxMesh::GetControlPointAt()
FbxMesh::GetPolygonCount()
FbxMesh::GetPolygonVertex()

ControlPoint 与 PolygonVertex 分别是啥东西呢?

举个栗子(例子)就明白了,有一个四边形,它有4个顶点,由2个三角形构成,那么 FbxMesh::GetControlPointsCount() 就返回4,

FbxMesh::GetPolygonCount() 就返回2。

在 ControlPoint 容器中存储了4个顶点的坐标值,存储结构例如:{[x,y,z], [x,y,z], [x,y,z], [x,y,z]}

在 PolygonVertex 容器中存储了6个索引值,存储结构例如:{0,1,2,0,2,3}

所以, ControlPoint 可以理解成“控制点”,四边形中有4个控制点,每个控制点的坐标变化都会使得四边形的形状发生变化;

PolygonVertex 就是”以三角形为基础图元“的图形学中的概念,每个三角形有3个顶点。

如果你已经理解了Fbx中的Layer的概念(例如法线Layer,UV Layer等等),这里可以做一个类比,ControlPoint 容器就好比

Layer中的 DirectArray ,PolygonVertex 容器就好比 IndexArray 。


2,FbxLayerElement::MappingMode()

在提取顶点数据时(例如法线,UV等等),我们经常碰到 FbxLayerElement::MappingMode() 这个函数。

我们只关心 FbxGeometryElement::eByControlPoint 和 FbxGeometryElement::eByPolygonVertex 这两种取值。

举个栗子(例子),立方体中有8个ControlPoint,24个PolygonVertex,我们以法线Layer做示例,

如果每个ControlPoint只对应着一个法线,那么这种 MappingMode 就是 eByControlPoint。

但是,实际情况是,法线应该是与面(三角形)一一对应的,每个ControlPoint参与构建了3个三角形,那么每个ControlPoint

应该对应了3个法线,也即,法线与PolygonVertex是一一对应的,那么这种 MappingMode 就是 eByPolygonVertex。

MappingMode决定了法线了存储方式。


3,FbxLayerElement::ReferenceMode()

Layer中除了MappingMode,还有ReferenceMode。

我们只关心FbxGeometryElement::eDirect 和 FbxGeometryElement::eIndexToDirect 这两种取值。

举个栗子(例子),立方体中有8个ControlPoint,24个PolygonVertex,我们假设法线Layer的MappingMode值为

eByControlPoint,那么法线Layer中有8个法线值,

假设这8个法线值各不相同,那么存储方式类似这样:DirectArray = {法线1,法线2,法线3,法线4,法线5,法线6,法线7,法线8}

那么 ReferenceMode 的值就是 eDirect。

假设法线3,法线4,法线5的值相同,那么Fbx做了优化,把三个值优化成一个值,然后添加索引,存储方式类似这样:

DirectArray = {法线1,法线2,法线3,法线6,法线7,法线8}

IndexArray = {0, 1, 2, 2, 2, 3, 4, 5}

那么 ReferenceMode 的值就是 eIndexToDirect


4,获取动画数据(矩阵数据)有两种方法

在FbxSDK官网文档中提到了 FbxTakeInfo 这个类,然后指出,获取动画矩阵数据有两种方法,

第一种是使用新方法,用递归的方式遍历FbxScene,依次取得 FbxAnimStack ,FbxAnimLayer ,FbxAnimCurve;

第二种是使用旧方法,获取 FbxTakeInfo 对象。

新方法的示例代码:

//----------------------------------------------------------------
void SoFBXManager::ParseAnimation(FbxScene* pSDKScene)
{
	FbxNode* pNode = pSDKScene->GetRootNode();
	const int nStackCount = pSDKScene->GetSrcObjectCount();
	for (int i = 0; i < nStackCount; ++i)
	{
		FbxAnimStack* pStack = pSDKScene->GetSrcObject(i);
		const char* szStackName = pStack->GetName();
		const int nLayerCount = pStack->GetMemberCount();
		for (int j = 0; j < nLayerCount; ++j)
		{
			FbxAnimLayer* pLayer = pStack->GetMember(j);
			const char* szLayerName = pLayer->GetName();
			ParseLayer(pNode, pLayer);
		}
	}
}
//----------------------------------------------------------------
void SoFBXManager::ParseLayer(FbxNode* pNode, FbxAnimLayer* pLayer)
{
	ParseChannel(pNode, pLayer);

	const int nChildCount = pNode->GetChildCount();
	for (int k = 0; k < nChildCount; ++k)
	{
		ParseLayer(pNode->GetChild(k), pLayer);
	}
}
//----------------------------------------------------------------
void SoFBXManager::ParseChannel(FbxNode* pNode, FbxAnimLayer* pLayer)
{
	FbxAnimCurve* pCurve = pNode->LclTranslation.GetCurve(pLayer, FBXSDK_CURVENODE_COMPONENT_X);
	if (pCurve == 0)
	{
		return;
	}

	FbxTime kTime;
	char lTimeString[256];

	const int nKeyCount = pCurve->KeyGetCount();
	for (int i = 0; i < nKeyCount; ++i)
	{
		kTime = pCurve->KeyGetTime(i);
		const FbxAMatrix& kMatLocal = pNode->EvaluateLocalTransform(kTime);
		const FbxAMatrix& kMatGlobal = pNode->EvaluateGlobalTransform(kTime);
		//
		kTime.GetTimeString(lTimeString, FbxUShort(256));
		SoLogDebug("FBXTime : %s", lTimeString);
	}
}

旧方法的示例代码:

FbxAnimStack* currAnimStack = mFBXScene->GetSrcObject(0);
FbxString animStackName = currAnimStack->GetName();
mAnimationName = animStackName.Buffer();
FbxTakeInfo* takeInfo = mFBXScene->GetTakeInfo(animStackName);
FbxTime start = takeInfo->mLocalTimeSpan.GetStart();
FbxTime end = takeInfo->mLocalTimeSpan.GetStop();
mAnimationLength = end.GetFrameCount(FbxTime::eFrames24) - start.GetFrameCount(FbxTime::eFrames24) + 1;
Keyframe** currAnim = &mSkeleton.mJoints[currJointIndex].mAnimation;

for (FbxLongLong i = start.GetFrameCount(FbxTime::eFrames24); i <= end.GetFrameCount(FbxTime::eFrames24); ++i)
{
	FbxTime currTime;
	currTime.SetFrame(i, FbxTime::eFrames24);
	*currAnim = new Keyframe();
	(*currAnim)->mFrameNum = i;
	FbxAMatrix currentTransformOffset = inNode->EvaluateGlobalTransform(currTime) * geometryTransform;
	(*currAnim)->mGlobalTransform = currentTransformOffset.Inverse() * currCluster->GetLink()->EvaluateGlobalTransform(currTime);
	currAnim = &((*currAnim)->mNext);
}

我看到了两篇博客都是使用旧方法。

这篇帖子对新方法与旧方法的做了一点区别:http://gamedev.stackexchange.com/questions/59419/c-fbx-animation-importer-using-the-fbx-sdk

新方法的缺陷是,你不知道该动画的持续时长。使用旧方法,你能够得到动画的持续时长,然后根据你

的需要,把该动画切割成若干帧,把每帧的矩阵保存起来。

每个Fbx文件中只能存储一个FbxTakeInfo,也即只能存储一个动画,例如只能存储一个走路动画或者一个跑步动画。

有一个外国团队做了一个插件叫FBX Multi Take (3DS Max),可以把多个动画拼接成一个动画,让fbx文件存储拼接之后的动画。

该插件是收费的,官网在这里 。

Fbx文件中可以只包含mesh顶点数据,也可以只包含骨骼动画数据。一个角色模型,可以把Mesh顶点数据保存成一个fbx文件,

把所有的骨骼动画分别保存成一个fbx文件。





你可能感兴趣的:(FbxSDK使用总结)