本文翻译自FBX SDK官方英文地址:
https://help.autodesk.com/view/FBX/2020/ENU/?guid=FBX_Developer_Help_animation_html
FBX SDK将以下数据结构用于动画:动画堆栈(FbxAnimStack)、动画层(FBX AnimLayer)、动画曲线节点(FBX-AnimCurveNode)、动画线(FBX.AnimCurve)和动画曲线关键帧(FbxranimCurveKey)。在FBX场景(FbxScene)中,数据结构通过对象到对象(OO)连接和对象到属性(OP)连接相互连接。有关这些数据结构的详细信息,请参见动画类及其相互关系。
FBX SDK支持混合动画,这是Autodesk MotionBuilder、Autodesk Maya和其他应用程序的一项功能。混合创建从一个动画到另一个动画的平滑过渡。例如,如果您有一组允许角色行走的动画曲线,以及另一组允许该角色运行的动画,则可以通过将行走动画与运行动画混合来创建从行走到运行的平滑过渡。混合动画时,如果将动画与类似运动混合,将获得最佳效果。有关使用动画数据结构进行混合的描述,请参见混合动画。
在FBX SDK 2011之前,用于动画的数据结构是take节点、take节点容器、take信息等。请参见迁移到新的动画数据结构,以了解当前数据结构与FBX SDK 2010中用于动画的数据结构之间的差异。
以下是在场景中存储动画相关数据的类:
类 |
描述 |
FbxScene |
场景可以包含一个或多个动画堆栈。如果场景中没有动画,则不需要使用动画堆栈或其他动画类。 |
FbxAnimStack |
动画堆栈是动画数据的最高级别容器,包含一个或多个动画层。 |
FbxAnimLayer |
动画层包含一个或多个连接到动画曲线的动画曲线节点。动画堆栈必须至少有一个称为基础层的动画层。对于混合动画,需要多个动画层。 |
FbxAnimCurve |
动画曲线(也称为功能曲线或FCurve)定义FBX对象的FBX属性(FbxProperty)如何设置动画或与动画层中的默认值不同。动画曲线连接到动画曲线节点。无论引用的FBX属性如何,同一动画曲线都可以连接到多个动画曲线节点。因此,一条动画曲线可以为许多FBX对象的许多FBX属性设置动画。动画曲线中的值取决于FBX属性类型,并由应用程序验证。 动画曲线不是必需的。为了节省内存和处理时间,可以保留未设置动画的FBX属性,而不使用动画曲线。 |
FbxAnimCurveNode |
动画曲线节点(FbxAnimCurveNode)是动画曲线和FBX属性之间的连接点。要将动画曲线连接到FBX属性,可以将动画曲线和FBX属性连接到一个动画曲线节点。 动画曲线节点只能连接到一个FBX对象的一个FBX属性。FBX属性(例如FbxNode::LclTranslation)包含多个值(X、Y、Z)。如果通过调用FbxAnimCurveNode::CreateTypedCurveNode创建动画曲线节点,则必须指定所需数据类型的FBX属性,例如,LclTranslation中的函数CreateTypedCriveNode()将创建具有必要动画通道的动画曲线节点。在本示例中,通道X、Y和Z。但是,动画曲线节点实际上没有连接到指定的FBX属性。请参见 若要允许同一FBX属性在每个动画层上具有不同的值,必须将动画曲线节点连接到单个动画层。如果同一动画曲线节点连接到多个动画层,则每个动画层中的FBX属性使用相同的值。 |
FbxAnimCurveKey |
关键点或关键帧标记动画曲线的开始和结束。 |
FbxObject |
FBX对象可以包含零个或多个FBX属性。类FbxNode包含处理对象作为空间点的位置的属性。 |
FbxProperty |
FBX属性是属于FBX对象的强类型数据。要设置场景动画,可以设置FBX对象中包含的相应FBX属性(LclTranslation是最常见的)。有关详细信息,请参见FBX属性 |
下面的UML类图显示了FBX场景中类之间的相互关系。
显示数据结构之间相互关系的场景,下图将有助于理解数据结构在典型场景中是如何相互关联的。
图中的示例场景有三个FBX对象(FBXObject0、 FBXObject1和FBXObject2),每个对象都有一个可以设置动画的FBX属性(Obj0Property、Obj1Property和Obj2Property)。这三个FBX属性的默认值分别为a、b和c。数据结构通过对象到对象(OO)连接和对象到属性(OP)连接相互连接,如图所示。
示例场景有一个动画堆栈,其中包含三个动画层。下表说明了动画层和其他数据结构如何影响FBX属性的值。
动画层 |
描述 |
FBX属性值 |
AnimationLayer0 |
AnimationLayer0是基础层,它连接到一个动画曲线节点(CurveNode0)。该动画曲线节点未连接到任何动画曲线,因此不用于将动画应用于FBX对象。但是,该动画曲线节点连接到一个FBX属性(Obj0Property)。 |
在AnimationLayer0中,Obj0Property的值a被动画曲线节点的值aa0覆盖。 通常,在缺少动画曲线或忽略动画曲线时,动画曲线节点中的值会覆盖FBX属性的值。 |
AnimationLayer1 |
AnimationLayer1连接到两个动画曲线节点:CurveNode1和CurveNode 2。 CurveNode1连接到一个FBX属性(Obj0Property)和一条动画曲线(AnimationCurve0)CurveNode2连接到一个FBX属性(Obj1Property)和一条动画曲线(AnimationCurve1)。 |
在AnimationLayer1中,Obj0Property的值既不是a也不是aa1,而是由AnimationCurve0中随时间变化的值确定的 在AnimationLayer1中,Obj1Property的值既不是b也不是bb1,而是由AnimationCurve1中随时间变化的值确定的。 |
AnimationLayer2 |
AnimationLayer2连接到一个曲线节点(CurveNode3),该节点连接一条动画曲线(AnimationCurve0)和一个FBX属性(Obj1Property)。 |
注意:AnimationCurve0连接到CurveNode1和CurveNode 3。因此,在计算动画场景时,AnimationCive0会影响两个不同动画层(AnimationLayer1和AnimationLayer2)中两个不同FBX对象(FBXObject0和FBXObject1)的两个不同的FBX属性(Obj0Property和Obj1Property)。此外,如何计算动画数据不仅取决于动画曲线,还取决于每个动画层的混合模式和混合权重。有关混合的详细信息,请参见混合动画。
FBX SDK动画系统提供了很大的灵活性,但您有责任确保数据结构之间的相互关系有效。
当前的FBX SDK动画系统已完全重新设计。FBX SDK 2010中的数据结构(如take node、take node-container、take info等)已替换为新的数据结构。下表说明了当前数据结构与FBX SDK 2010中的数据结构之间的差异。
不推荐的类和数据结构 |
当前数据结构和类 |
差别 |
|
Animation Stack (FbxAnimStack) |
动画堆栈取代了take作为动画的最高级别容器的概念。 场景可以包含一个或多个动画堆栈。现在可以使用多个动画堆栈,而不是在场景中多次拍摄。如果场景中没有动画,则不需要使用动画堆栈或其他动画类。 当前的FbxAnimStack类与不推荐使用的KFbxTake类管理相同的数据。 动画堆栈可以包含一个或多个动画层。 |
|
当前SDK中没有等效的数据结构 |
|
没有等效的类或数据结构 |
Animation Layer (FbxAnimLayer) |
动画层包含一个或多个连接到动画曲线的动画曲线节点。动画堆栈必须至少有一个称为基础层的动画层。对于混合动画,需要多个动画层。 |
|
Animation Curves ( |
动画曲线(也称为功能曲线或FCurve)定义FBX对象的FBX属性(FbxProperty)如何设置动画或与动画层中的默认值不同。动画曲线连接到动画曲线节点。 FbxAnimCurve类与KFCurve类具有类似的接口。 |
|
Animation Curve Node (FbxAnimCurveNode) |
动画曲线节点是动画曲线和FBX属性之间的连接点。要将动画曲线连接到FBX属性,可以将动画曲线和FBX属性连接到一个动画曲线节点。动画曲线节点只能连接到一个FBX对象的一个FBF属性。 FbxAnimCurveNode类比KFCurveNode更简单,因为不需要层次结构。 |
FbxNode::GetGlobalFromCurrentTakeNode函数以及FbxNode和从KFbxTakeNodeContainer继承的其他类的类似函数 |
Evaluator (FbxAnimEvaluator) |
当前的动画计算系统提供了与前一个系统相同的行为,但更好地进行了封装。您可以通过从FbxAnimEvaluator类派生来编写自己的求值器。请参阅编写和使用自己的动画计算类。 |
注意:不建议使用不推荐使用的类和API元素。当使用不推荐使用的类和API元素时,编译器可能会显示警告。
例外情况:
FbxTakeInfo对象在当前FBX SDK版本中不受欢迎。
可以使用FbxAnimEvaluator类计算场景中的动画。
// Assume that myScene is already created
FbxAnimEvaluator* mySceneEvaluator = MyScene->GetEvaluator();
FbxAnimEvaluator是一个抽象类。当您调用FbxScene::GetEvaluator()时,该函数将创建FbxAnimEvalClassic对象的实例。FbxAnimEvalClassic对象是当前版本的FBX SDK中FbxAnimEvaluator类的唯一实现。
但是,您可以实现从FbxAnimEvaluator类派生的自己的动画计算类。
执行以下步骤以实现自定义的动画计算类:
FbxAnimEvaluator* mySceneEvaluator = MyScene->GetEvaluator();
现在,mySceneEvaluator将指向您自己的动画计算类的对象。
有关在场景中评估动画的信息,请参见评估场景中的动画。
顶点动画发生在子对象(顶点)层级,例如,在球体中移动四个顶点。顶点缓存是一种将顶点动画存储在缓存文件或点缓存文件中的方法,以便在从程序导出时不会丢失顶点动画。要从3ds Max等程序导出子对象动画,必须计算每个顶点位置并将其存储在缓存文件中。
有关使用顶点缓存创建顶点并为其设置动画的示例代码,请参见示例程序ExportScene03中的函数MapVertexCacheOnTriangle()和AnimateVertexcacheOnTringle()
有关读取顶点缓存中的数据的示例代码,请参见示例程序ViewScene中的函数PreparePointCacheData()和ReadVertexCache()。
示例程序位于
混合创建从一个动画到另一个动画的平滑运动过渡。可以混合两个共享公共属性且属于同一角色集的动画。混合动画时,如果混合具有类似运动的动画,将获得最佳效果。例如,创建行走动画和跑步动画之间的混合。
在场景中混合动画需要多个动画层。例如,要将行走动画混合到跑步动画,必须使用两个动画层。基础层(第0层)包含行走动画的动画曲线节点,另一层(第1层)包含运行动画的动画曲面节点。
动画层按添加到动画堆栈的顺序进行求值:Layer0、Layer1、Layer2,依此类推。
可以设置动画层的权重(混合权重)属性。权重属性确定混合期间每个动画的影响量。例如,在步行-跑步过渡期间,您可以花一秒钟将层0(步行)的权重从0.99更改为.01,将层1(跑步)的权重由.01更改为0.99。
动画曲线节点通常将一条动画曲线连接到一个FBX属性,但也可以使用动画曲线节点简单地覆盖FBX属性的值。请参见显示数据结构之间相互关系的场景。
注意:动画曲线节点也可以用作FBX属性值的容器。请参见评估场景中的动画。
混合模式指定动画层中的动画如何与动画堆栈的前一个动画层的动画混合。下表说明了混合模式:
模式 |
描述 |
示例 |
Additive |
相加动画层将动画添加到动画堆栈中前面的动画层。前面的动画层必须影响相同的FBX属性。 |
如果AnimLayerA和AnimLayerB都包含控制FBX对象的X平移的动画曲线节点,则生成的X平移是两个动画层中X平移值的总和。 |
Override |
覆盖动画层覆盖动画堆栈前面的动画层中的动画。前面的动画层必须控制相同的FBX属性。 |
如果AnimLayerA和AnimLayerB处于覆盖模式,并且AnimLayourA上FBX对象的X平移为10,而AnimLaierB上的X平移则为15,则FBX对象产生的X平移是15。 |
Override- Passthrough |
覆盖通过与完全阻止前面动画层中的动画的覆盖模式不同,在覆盖通过模式中,可以控制动画层的不透明度。前面的动画层必须影响相同的FBX属性。 |
如果AnimLayerC处于覆盖模式,则它是完全不透明的,并会阻止前面动画层中的动画。但是,如果AnimLayerC处于覆盖传递模式,则可以通过设置权重值来控制AnimLayerC的不透明度。 |
另请参见:
FbxAnimLayer::EBlendMode
FbxAnimLayer::ERotationAccumulationMode
FbxAnimLayer::EScaleAccumulationMode
每个动画层可以包含动画曲线节点,这些节点指定不同数据类型的许多FBX属性。对于特定数据类型的FBX属性,您可能不希望安某一个动画层中的动画与另一个层的动画混合,这个FBX属性就是布尔数据类型。
对于头文件fbxtype.h中定义的每个数据类型,每个动画层都有一个混合模式绕过标志。要绕过混合模式,可以获取并将混合模式绕过标记的值设置为以下值之一:
是否要绕过混合模式 |
描述 |
true |
忽略动画层中的混合模式。在求值过程中(通过使用FbxAnimEvaluator类),动画层中的属性值将覆盖任何先前层中的值。 |
false |
在计算动画期间使用动画层的混合模式。 |
另请参见:
你可以从场景(FBX文件)中提取和查询动画数据。
场景(FbxScene)可以包含一个或多个动画堆栈(Fbx动画堆栈)。动画堆栈是动画数据的最高级别容器,包含一个或多个动画层(FbxAnimLayer)。动画层包含一个或多个动画曲线节点(FbxAnimCurveNode)。动画曲线节点是动画曲线(FbxAnimCurve)和FBX对象(FBX对象)的FBX属性(FbxProperty)之间的连接点。
执行以下步骤以从场景中提取动画数据:
(1)、使用指向FbxScene(pScene)实例的指针提取动画堆栈的数量:
int numStacks = pScene->GetSrcObjectCount(FBX_TYPE(FbxAnimStack));
(2)、检索第n个动画堆栈对象指针:
FbxAnimStack* pAnimStack = FbxCast(pScene->GetSrcObject(FBX_TYPE(FbxAnimStack), n)
(3)、检索动画堆栈中的动画层数:
int numAnimLayers=pAnimStack->GetMemberCount(FBX_TYPE(FbxAnimLayer));
(4)、从动画堆栈检索第n个动画层对象指针:
FbxAnimLayer* lAnimLayer=lAnimStack->GetMember(FBX_TYPE(FbxAnimLayer), n);
检索动画层后,可以访问节点属性和节点属性的动画曲线。
(5)、检索动画曲线:
FbxAnimCurve* lAnimCurve=lNode->LclTranslation.GetCurve(lAnimLayer, FBXSDK_CURVENODE_COMPONENT_X);
FbxNodeAttribute* lNodeAttribute = lNode->GetNodeAttribute();
FbxAnimCurve* lAnimCurve = lNodeAttribute->Color.GetCurve(pAnimLayer, FBXSDK_CURVENODE_COLOR_RED);
你还可以使用动画曲线节点访问属性的动画数据。以下步骤显示了如何访问第一个通道(从0开始)上特定属性的动画曲线。
(1)、检查属性是否有效
if (lProperty.IsValid) ...
(2)、使用动画层检索曲线节点
FbxAnimCurveNode* lCurveNode=lProperty.GetCurveNode(pAnimLayer);
(3)、如果该属性未设置动画,则曲线节点为NULL
if (lCurveNode != NULL) ...
(4)、遍历获取所有动画曲线节点中的动画曲线
for( int c = 0; c < lCurveNode->GetCurveCount(0U); c++ ) {
FbxAnimCurve* lAnimCurve = lCurveNode->GetCurve(0U, c);
if (lAnimCurve != NULL) {
// ...
}
}
有关动画曲线节点和通道的详细信息,请参见FbxAnimCurveNode类参考。
有关使用FBX SDK导入动画数据的更详细示例,请参见示例:
ImportScene/DisplayAnimation.cxx。
要渲染动画场景(FbxScene),必须在不同的时间点计算场景中对象(FbxObject)的动画属性。例如:要显示由网格表示的移动赛车,必须设置节点属性为网格的节点的局部平移属性的动画。请参见FbxNode::LclTranslation。
执行以下步骤以计算场景中的动画:
(1)、从包含节点的场景对象中获取求值器对象。
//假设myScene已经创建
FbxAnimEvaluator* mySceneEvaluator = MyScene->GetAnimationEvaluator();
(2)、创建一个时间对象。
FbxTime myTime; // 动画曲线中每个关键帧的时间
myTime.SetSecondDouble(0.0); // 开始时间
(3)、创建一个动画节点对象。
// Create a node object
FbxNode* myMeshNode = FbxNode::Create (myScene, "");
// ... Code to connect myMeshNode to a mesh object
(4)、获取节点在特定时间的全局变换矩阵或局部变换矩阵。
// Get a reference to node’s global transform.
FbxMatrix& GlobalMatrix = mySceneEvaluator->GetNodeGlobalTransform(myMeshNode, myTime);
// Get a reference to node’s local transform.
FbxMatrix& GlobalMatrix = mySceneEvaluator->GetNodeLocalTransform(myMeshNode, myTime);
(5)、计算摄影机在特定时间以矢量表示的位置。注:计算相机位置时不需要变换矩阵。
// Given a scene, we can create a camera
FbxCamera* myCamera = FbxCamera::Create (myScene, "");
// Store the value of the property in an animation curve node
FbxAnimCurveNode& myCameraPositionVector;
// Get and store the value of the camera's local translation
myCameraPositionVector = mySceneEvaluator->GetPropertyValue (myCamera->Position, myTime);
(6)、在特定时间计算材质属性。
FbxTime time;
time.SetSecondDouble(3.5);
FbxColor color = MyMaterial->GetDiffuseColor()->EvaluateValue(time);
请参见Anitmation/main.cxx示例说明了动画堆栈、动画层、动画曲线节点和动画曲线的使用。
使用“动画曲线”节点存储FBX属性的值
在上面的代码段中,FbxAnimCurveNode对象未用作动画曲线和FBX属性之间的连接点。相反,FbxAnimCurveNode被用作存储求值器返回的属性向量的方便位置。FBX对象的可设置动画的属性有多种数据类型。有些数据类型是标量,例如FbxDouble1、FbxInteger1和FbxBool1。其他数据类型具有存储为矢量的三元组(X、Y、Z)值。
FbxNode::LclTranslation的类型为FbxDouble3,它是一个包含三个元素的向量。FbxAnimCurveNode是一个容器,它可以存储任何FBX属性的值,而与数据类型无关。FbxAnimCurveNode的成员函数允许您访问向量中每个元素的值,例如,获取通道X的值、通道Y的值和通道Z的值。
请参见以下函数:
本节包含一个示例动画,可帮助您熟悉FBX SDK中的动画功能。本节基于CubeCreater教程程序,该程序位于
设置节点动画
如果不使用混合动画,则为动画设置数据结构很简单。每个FBX属性至少需要一个动画堆栈、一个动画层和一个动画曲线节点。
此示例显示如何设置摄影机的FbxNode对象的局部平移属性的动画。
FbxScene* myScene; //已初始化且有效的场景对象
FbxNode* myCameraNode; //已初始化且有效的节点对象
// Set the default local translation values (X, Y, Z) for the camera
myCameraNode.LclTranslation.Set(fbxDouble3(1.1, 2.2, 3.3))
尽管此示例显示了如何设置摄影机平移属性的动画,但如果FBX属性可设置动画,则可以设置任何FBX对象的任何FBX属性的动画。有关详细信息,请参阅FbxPropertyFlags::eAnimatable。
按照这些部分中的说明设置摄影机的局部平移属性的动画。
首先创建动画堆栈和动画层:
执行以下步骤:
(1)、创建一个动画堆栈
// Create an animation stack
FbxAnimStack* myAnimStack = FbxAnimStack::Create(pScene, "My stack");
(2)、创建一个动画层
// Create the base layer (this is mandatory)
FbxAnimLayer* myAnimBaseLayer=FbxAnimLayer::Create(pScene, "Base Layer");
(3)、将动画层加入到动画堆栈
myAnimStack->AddMember(myAnimBaseLayer);
将基础层添加到动画堆栈后,可以创建动画曲线节点以将动画数据连接到FBX特性。
创建动画曲线节点
CubeCreator教程程序为摄影机沿其移动的每个轴创建一个动画曲线节点。
创建一个动画曲线节点,以将平移数据连接到摄影机节点的LclTranslation属性:
// 获取摄像机对象的本地平移动画曲线节点
// 函数GetCurveNode() 的第二个参数是 "true",表示该本地平移动画曲线节点如果未创建,则自动创建.
FbxAnimCurveNode* myAnimCurveNode = pCamera->LclTranslation.GetCurveNode(pAnimLayer, true);
笔记:
有关创建动画曲线节点和动画曲线的另一种方法,请参见
设置动画曲线节点(myAnimCurveNode)以将动画曲线连接到摄影机(myCameraNode)的局部平移属性后,可以创建动画曲线。
创建动画曲线
执行以下步骤:
(1)、创建用于在X轴上平移摄影机的动画曲线。
FbxAnimCurve* myTranXCurve = NULL; // X坐标的动画曲线
FbxTime myTime; // 开始帧和停止帧的停止时间
int myKeyIndex = 0; // 关键帧索引
// 根据摄像机动画节点获取X轴上的动画曲线,最后一个参数为true:表示不存在时自动创建
myTranXCurve = myCameraNode->LclTranslation.GetCurve(myAnimLayer, FBXSDK_CURVENODE_COMPONENT_X, true);
(2)、定义动画曲线的开始和结束关键帧。
// 开始修改关键帧
myAnimCurve->KeyModifyBegin();
// 开始帧
myTime.SetSecondDouble(0.0); // 开始帧的时间,第0秒
myKeyIndex = myAnimCurve->KeyAdd(lTime); // 添加第一个关键帧,并返回帧索引
myAnimCurve->KeySet(lKeyIndex, // 关键帧的索引
lTime, // 开始时间
0.0, // 本关键帧在开始时间的X坐标值
FbxAnimCurveDef::eInterpolationLinear);// 在两个关键帧之间采用线性插值算法
// 停止帧
lTime.SetSecondDouble(20.0); // 设置停止帧的时间,第20秒
lKeyIndex = myAnimCurve->KeyAdd(lTime);// 添加停止帧,并返回帧索引
// 设置停止帧的X轴位置
myAnimCurve->KeySet(lKeyIndex, lTime, 500.0, FbxAnimCurveDef::eInterpolationLinear);
myAnimCurve->KeyModifyEnd();
如果运行此动画,相机需要20秒才能沿X轴移动500个单位。
您可以参考以下演示动画的示例程序: