基于FBX SDK的FBX模型解析与加载 -(四)

8. 骨骼蒙皮动画

骨骼蒙皮动画是当前游戏引擎中最常用的一种动画方式,关于其基本原理网络上的资料较多,关于到涉及的其它较复杂操作,如插值、融合等在这里也就先不再讨论了,而且其实现方式也与具体引擎的动作管理系统相关;在这里就主要简单介绍一下如何从FBX里加载骨骼以及蒙皮信息并完成最基本的蒙皮动画效果。骨骼动画的实现主要包括骨骼的驱动和蒙皮两部分操作,骨骼的驱动在前一篇中介绍动画数据的加载时已经完成了,接下来就是对于Mesh与Skeleton之间的Skinning操作。

我们知道,骨骼动画其实就是通过更新较少量的Skeleton,进而实现对关联到这些骨骼上的Mesh的更新,在每帧间都进行这样的更新并做合适的插值与融合就可以得到平滑流畅的动作效果了。通过前面基本几何和动画数据(Skeleton和Mesh)的加载已经有了这两部分必要信息,接下来就需要对两者进行关联从而实现Skinning时的正确映射。这一部分数据的读取其实还是以Mesh为单位进行的,其层次关系结构图如下所示:

其中的Mesh可从当前属性为eMESH的Node结点中获得(与读取几何网格数据相同),其可能是构成整个模型的网格的一小部分(Sub-Mesh)。若当前的Mesh中含有相应的蒙皮动画数据,则可以从其中读取出全部的Vertex到Skeleton的映射信息。Mesh中的蒙皮数据由一个或多个KFbxDeformer来管理,KFbxDeformer是类型为KFbxTakeNodeContainer的一个对象。每个Deformer管理当前Mesh中的部分顶点到Skeleton的映射关系,而这种映射关系的组织方式又分为两种不同的形式,因而就有了派生自Deformer的KFbxSkin和KFbxVertexCacheDeformer(一般情况下只需考虑KFbxSkin的方式)。每个Skin(Deformer)中可能对应到多个顶点,这些顶点又可能映射到多个Skeleton,在Skin(Deformer)中的每个Skeleton对应着一个Cluster。如此一来,通过在每个Cluster(->Skeleton)中寻找其所影响到的Vertex,得到相应的联接信息如映射Matrix、骨骼Weight等并做相应的存储即可完成Skeleton到Mesh之间的映射蒙皮。另外注意:Vertex和Skeleton之间的关系是多对多,即一个Vertex可能受多个Skeleton影响,而一个Skeleton又可能影响到多个Vertex;这些关系在设计数据结构时就应该有所注意。该部分的代码大体如下所述:

  1. void AssociateSkeletonWithCtrlPoint(KFbxMesh* pMesh , CSkeletonMgr* pSkeletonMgr , List<VertexSkeletonList>& ctrlPointSkeletonList)
  2. {
  3. if(!pMesh || !pSkeletonMgr)
  4. {
  5. return;
  6. }
  7. int ctrlPointCount = pMesh->GetControlPointsCount();
  8. int deformerCount = pMesh->GetDeformerCount();
  9. // 初始化相应的列表
  10. ctrlPointSkeletonList.SetCapacity(ctrlPointCount);
  11. ctrlPointSkeletonList.setListSize(ctrlPointCount);
  12. KFbxDeformer* pFBXDeformer;
  13. KFbxSkin* pFBXSkin;
  14. for(int i = 0 ; i < deformerCount ; ++i)
  15. {
  16. pFBXDeformer = pMesh->GetDeformer(i);
  17. if(pFBXDeformer == NULL)
  18. {
  19. continue;
  20. }
  21. // 只考虑eSKIN的管理方式
  22. if(pFBXDeformer->GetDeformerType() != KFbxDeformer::eSKIN)
  23. {
  24. continue;
  25. }
  26. pFBXSkin = (KFbxSkin*)(pFBXDeformer);
  27. if(pFBXSkin == NULL)
  28. {
  29. continue;
  30. }
  31. AssociateSkeletonWithCtrlPoint(pFBXSkin , pSkeletonMgr , ctrlPointSkeletonList);
  32. }
  33. }
void AssociateSkeletonWithCtrlPoint(KFbxMesh* pMesh , CSkeletonMgr* pSkeletonMgr , List<VertexSkeletonList>& ctrlPointSkeletonList)
{
	if(!pMesh || !pSkeletonMgr)
	{
		return;
	}

	int ctrlPointCount = pMesh->GetControlPointsCount();
	int deformerCount  = pMesh->GetDeformerCount();

	// 初始化相应的列表
	ctrlPointSkeletonList.SetCapacity(ctrlPointCount);
	ctrlPointSkeletonList.setListSize(ctrlPointCount);

	KFbxDeformer* pFBXDeformer;
	KFbxSkin*     pFBXSkin;

	for(int i = 0 ; i < deformerCount ; ++i)
	{
		pFBXDeformer = pMesh->GetDeformer(i);

		if(pFBXDeformer == NULL)
		{
			continue;
		}

		// 只考虑eSKIN的管理方式
		if(pFBXDeformer->GetDeformerType() != KFbxDeformer::eSKIN)
		{
			continue;
		}

		pFBXSkin = (KFbxSkin*)(pFBXDeformer);
		if(pFBXSkin == NULL)
		{
			continue;
		}

		AssociateSkeletonWithCtrlPoint(pFBXSkin , pSkeletonMgr , ctrlPointSkeletonList);
	}
}
  1. void AssociateSkeletonWithCtrlPoint(KFbxSkin* pSkin , CSkeletonMgr* pSkeletonMgr , List<VertexSkeletonList>& ctrlPointSkeletonList)
  2. {
  3. if(!pSkin || !pSkeletonMgr)
  4. {
  5. return;
  6. }
  7. KFbxCluster::ELinkMode linkMode = KFbxCluster::eNORMALIZE;
  8. KFbxCluster* pCluster;
  9. KFbxNode* pLinkNode;
  10. int skeletonIndex;
  11. CSkeleton* pSkeleton;
  12. KFbxXMatrix transformMatrix , transformLinkMatrix;
  13. int clusterCount = pSkin->GetClusterCount();
  14. // 处理当前Skin中的每个Cluster(对应到Skeleton)
  15. for(int i = 0 ; i < clusterCount ; ++i)
  16. {
  17. pCluster = pSkin->GetCluster(i);
  18. if(!pCluster)
  19. {
  20. continue;
  21. }
  22. pLinkNode = pCluster->GetLink();
  23. if(!pLinkNode)
  24. {
  25. continue;
  26. }
  27. // 通过Skeleton管理器搜索到当前Cluster所对应的Skeleton,并与Cluster进行关联
  28. skeletonIndex = pSkeletonMgr->FindSkeleton(pLinkNode->GetName());
  29. // ... //关联Skeleton与Cluster
  30. if(skeletonIndex < 0)
  31. {
  32. continue;
  33. }
  34. pSkeleton = pSkeletonMgr->GetSkeleton(skeletonIndex);
  35. // 得到每个Cluster(Skeleton)所对应的Transform和TransformLink矩阵,后面具体说明
  36. pCluster->GetTransformMatrix(transformMatrix);
  37. pCluster->GetTransformLinkMatrix(transformLinkMatrix);
  38. // 其它适宜的操作,将Transform、TransformLink转换为映射矩阵并存储到相应的Skeleton中
  39. // ...
  40. int associatedCtrlPointCount = pCluster->GetControlPointIndicesCount();
  41. int* pCtrlPointIndices = pCluster->GetControlPointIndices();
  42. double* pCtrlPointWeights = pCluster->GetControlPointWeights();
  43. int ctrlPointIndex;
  44. // 遍历当前Cluster所影响到的每个Vertex,并将对相应的信息做记录以便Skinning时使用
  45. for(int j = 0 ; j < associatedCtrlPointCount ; ++j)
  46. {
  47. ctrlPointIndex = pCtrlPointIndices[j];
  48. ctrlPointSkeletonList[ctrlPointIndex].AddSkeleton(skeletonIndex , pCtrlPointWeights[j]);
  49. }
  50. }
  51. }
void AssociateSkeletonWithCtrlPoint(KFbxSkin* pSkin , CSkeletonMgr* pSkeletonMgr , List<VertexSkeletonList>& ctrlPointSkeletonList)
{
	if(!pSkin || !pSkeletonMgr)
	{
		return;
	}

	KFbxCluster::ELinkMode linkMode = KFbxCluster::eNORMALIZE;
	KFbxCluster* pCluster;
	KFbxNode*    pLinkNode;
	int          skeletonIndex;
	CSkeleton*   pSkeleton;
	KFbxXMatrix  transformMatrix , transformLinkMatrix;
	int          clusterCount = pSkin->GetClusterCount();

	// 处理当前Skin中的每个Cluster(对应到Skeleton)
	for(int i = 0 ; i < clusterCount ; ++i)
	{
		pCluster = pSkin->GetCluster(i);

		if(!pCluster)
		{
			continue;
		}

		pLinkNode = pCluster->GetLink();

		if(!pLinkNode)
		{
			continue;
		}

		// 通过Skeleton管理器搜索到当前Cluster所对应的Skeleton,并与Cluster进行关联
		skeletonIndex = pSkeletonMgr->FindSkeleton(pLinkNode->GetName());

		// ... //关联Skeleton与Cluster

		if(skeletonIndex < 0)
		{
			continue;
		}

		pSkeleton = pSkeletonMgr->GetSkeleton(skeletonIndex);

		// 得到每个Cluster(Skeleton)所对应的Transform和TransformLink矩阵,后面具体说明
		pCluster->GetTransformMatrix(transformMatrix);
		pCluster->GetTransformLinkMatrix(transformLinkMatrix);

		// 其它适宜的操作,将Transform、TransformLink转换为映射矩阵并存储到相应的Skeleton中
		// ...

		int     associatedCtrlPointCount = pCluster->GetControlPointIndicesCount();
		int*    pCtrlPointIndices = pCluster->GetControlPointIndices();
		double* pCtrlPointWeights = pCluster->GetControlPointWeights();
		int     ctrlPointIndex;

		// 遍历当前Cluster所影响到的每个Vertex,并将对相应的信息做记录以便Skinning时使用
		for(int j = 0 ; j < associatedCtrlPointCount ; ++j)
		{
			ctrlPointIndex = pCtrlPointIndices[j];
			ctrlPointSkeletonList[ctrlPointIndex].AddSkeleton(skeletonIndex , pCtrlPointWeights[j]);
		}
	}
}

上述代码只是完整代码的一部分,因其中涉及的大多数操作都与具体的实现系统相关,这里只列出部分以供参考而己。其中有两个操作
pCluster->GetTransformMatrix(transformMatrix);
pCluster->GetTransformLinkMatrix(transformLinkMatrix);
需要特别说明一下,两个操作分别得到两个Matrix,前者transformMatrix(记为Mt)用来描述当前映射时刻(初始的映射状态下)Mesh的变换矩阵(顶点的变换矩阵),
后者transformLinkMatrix(记为Mtl)用来描述当前映射时刻Bone的变换矩阵(可以参考kfbxcluster.h中的说明)。假设通过当前的Cluster可以关联顶点V和骨骼B,而其对应的空间变换矩阵分别为MVMB,因而有
MV = Mt; MB = Mtl
而在Mesh到Skeleton的蒙皮中需要由Skeleton的空间位置变换得到Mesh(顶点)的空间位置,所以就需要这样一个变换矩阵M使得


通过简单的变换即可得到

M在动画更新时就可以用来做Skeleton到Mesh之间的映射计算。
然后,即可以通过Skeleton的更新而完成对Mesh的更新,进而得到对整个模型的动画。比如下列图所示的一套动作:

9. 其它一些问题

虽然FBX SDK提供了对FBX模型的很友好的操作接口,但是目前的发布版本也有一些相应的问题:

  • FBX SDK提供的FBXImporter目前不支持中文路径,因而提供的fbx源文件地址中应不含有中文字符。
  • 3D Max或Maya中的FBX导出插件计算得到的Tangent会一些问题,特别是在那些具有对称属性UV的部位。
  • 导出的具有Smooth特性的Normal也会在某些网格接口处出现不平滑的现象。

后两个问题某些情况下的影响会比较严重,但是既然已经将原始的几何数据加载到自己的引擎中了,因而也就可以在引擎中对Tangent与Normal进行再计算。

前述内容介绍了使用FBX SDK来对FBX进行加载时涉及到的比较常见的操作,如加载网格、材质以及动画等,也给出了部分实现的代码,但毕竟不同的系统对各种资源(如Animation、Skeleton、Material等)有不同的管理方法,代码也不能完全直接使用,适宜地修改是必不可少的。而且其中的错误也是难免的,所以上述介绍内容只作为参考,具体的实现还需要好好研究与参考Autodesk的相关doc。

最后,欢迎交流与讨论~~

### 回答1: Qt3D是一个用于创建3D应用程序的Qt模块,支持加载和展示各种3D模型格式,包括FBX模型。 要加载FBX模型,首先需要在Qt项目中引入Qt3D模块,并在代码中创建一个Qt3D场景和一个Qt3D实体(Entity)。然后,可以通过Qt3D提供的加载器(如QSceneLoader)来加载FBX模型文件。 加载FBX模型的步骤如下: 1. 创建一个Qt3D场景和一个实体: Qt3DCore::QEntity *rootEntity = new Qt3DCore::QEntity(); Qt3DRender::QCamera *cameraEntity = view.camera(); 2. 创建一个Qt3D加载器,并设置加载器的模型文件路径: Qt3DRender::QSceneLoader *loader = new Qt3DRender::QSceneLoader(); loader->setSource(QUrl::fromLocalFile("path/to/your/fbx/file.fbx")); 3. 将加载器添加到实体中: rootEntity->addComponent(loader); 4. 将根实体添加到场景中,并将相机与场景关联: Qt3DExtras::Qt3DWindow view; view.setRootEntity(rootEntity); view.show(); 以上步骤将加载并展示FBX模型。你可以通过进一步的Qt3D组件和功能来实现模型的自定义渲染、动画控制和交互等。 需要注意的是,FBX是一种二进制文件格式,需要先将其转换为Qt3D可读取的格式。可以使用FBX SDK或其他相关工具来进行模型转换和预处理。 总结,通过引入Qt3D模块和使用Qt3D加载器,我们可以在Qt应用程序中加载和展示FBX模型。灵活的Qt3D框架还提供了许多功能来处理和渲染3D模型,使我们能够在应用程序中创建丰富的3D体验。 ### 回答2: Qt3D 是一个用于创建3D图形应用程序的Qt模块。它支持加载多种3D模型格式,其中包括FBXFBX是一种广泛使用的3D模型格式,由Autodesk开发并支持。 要加载一个FBX模型,我们需要进行以下步骤: 1. 首先,我们需要在我们的Qt应用程序中包含Qt3D模块。在.pro文件中添加如下行: ``` QT += 3dcore 3drender 3dinput ``` 2. 然后,我们需要创建一个Qt3D渲染窗口来显示我们的3D场景。我们可以使用QQuickView或QWindow派生的自定义窗口,具体使用哪个取决于我们的应用程序需求。 3. 接下来,我们需要创建一个QEntity对象作为场景的根节点。这个对象可以包含其他实体和组件。 4. 然后,我们可以使用QSceneLoader组件来加载FBX模型文件。我们需要将这个组件添加到场景中,并指定FBX文件的路径。 ```cpp QSceneLoader *sceneLoader = new QSceneLoader(); sceneLoader->setSource(QUrl::fromLocalFile("path/to/fbx/file.fbx")); QEntity *modelEntity = new QEntity(); modelEntity->addComponent(sceneLoader); ``` 5. 最后,我们可以将模型实体添加到场景的根节点中,并在渲染窗口中显示场景。 ```cpp rootEntity->addEntity(modelEntity); view->setRootEntity(rootEntity); view->show(); ``` 这样,我们就成功加载FBX模型并在Qt3D应用程序中显示出来了。我们可以通过添加其他组件和实体来对模型进行进一步的修改和控制。 在加载FBX模型之前,我们需要确保我们的应用程序已经安装了Qt3D模块。可以使用Qt的在线安装程序或源码编译安装Qt以获取Qt3D模块。 以上是一个简单的示例,演示了如何使用Qt3D加载FBX模型。根据我们的需求,我们可以在加载模型之后进行更多高级操作和修改。 ### 回答3: Qt3D是Qt框架中的一个模块,用于实现3D图形的渲染和交互。而FBX是一种用于存储和传输3D模型和动画数据的文件格式。 要在Qt3D中加载并显示FBX模型,首先需要导入Qt3D相关的头文件,并创建一个Qt3D的场景。 然后,需要创建一个Qt3D中的实体(Entity)来表示我们要加载FBX模型。可以使用Qt3D提供的QEntity类来创建实体,并将其添加到场景中。 接下来,需要创建一个Qt3D中的组件(Component),将FBX模型加载到实体中。Qt3D提供了QSceneLoader组件,可以用来加载FBX模型文件。通过调用QSceneLoader的setSource函数,可以指定要加载FBX文件路径。然后,将该组件添加到实体中。 最后,将实体添加到场景中,并启动Qt3D的渲染循环,即可在窗口中显示加载FBX模型。 除了加载FBX模型,Qt3D还提供了许多其他功能,如光照、材质、相机等,可以通过设置相应的组件和属性来实现。在加载后,可以通过操作相应的组件来对模型进行旋转、平移、缩放等操作,实现交互效果。 需要注意的是,加载FBX模型时需要保证FBX文件的路径正确,并且需要安装对应的FBX导入插件,以便Qt3D能够正常解析加载FBX文件。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值