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;这些关系在设计数据结构时就应该有所注意。该部分的代码大体如下所述:

[cpp] view
plain
 copy

  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. }
[cpp] view
plain
 copy

  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. }

上述代码只是完整代码的一部分,因其中涉及的大多数操作都与具体的实现系统相关,这里只列出部分以供参考而己。其中有两个操作
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。

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

基于FBX SDK的FBX模型解析与加载 -(四)的更多相关文章

  1. 基于FBX SDK的FBX模型解析与加载 -(一)

    http://blog.csdn.net/bugrunner/article/details/7210511 1. 简介 FBX是Autodesk的一个用于跨平台的免费三维数据交换的格式(最早不是由A ...

  2. 基于FBX SDK的FBX模型解析与加载 -(三)

    http://blog.csdn.net/bugrunner/article/details/7229416 6. 加载Camera和Light 在FBX模型中除了几何数据外较为常用的信息可能就是Ca ...

  3. 基于FBX SDK的FBX模型解析与加载 -(二)

    http://blog.csdn.net/bugrunner/article/details/7211515 5. 加载材质 Material是一个模型渲染时必不可少的部分,当然,这些信息也被存到了F ...

  4. 全面解析Pytorch框架下模型存储,加载以及冻结

    最近在做试验中遇到了一些深度网络模型加载以及存储的问题,因此整理了一份比较全面的在 PyTorch 框架下有关模型的问题.首先咱们先定义一个网络来进行后续的分析: 1.本文通用的网络模型 import ...

  5. tensorflow 模型保存与加载 和TensorFlow serving + grpc + docker项目部署

    TensorFlow 模型保存与加载 TensorFlow中总共有两种保存和加载模型的方法.第一种是利用 tf.train.Saver() 来保存,第二种就是利用 SavedModel 来保存模型,接 ...

  6. 一款基于jquery带百分比的响应式进度加载条

    今天要给大家带来一款基于jquery带百分比的响应式进度加载条.这款加载条非常漂亮,而且带有进度的百度比,且在不同的百分比用的是不同的颜色.而且这款加载条采用了响应式设计,在不同的分辨率的显示器下完美 ...

  7. Mybatis源码解析(二) —— 加载 Configuration

    Mybatis源码解析(二) -- 加载 Configuration    正如上文所看到的 Configuration 对象保存了所有Mybatis的配置信息,也就是说mybatis-config. ...

  8. tensorflow实现线性回归、以及模型保存与加载

    内容:包含tensorflow变量作用域.tensorboard收集.模型保存与加载.自定义命令行参数 1.知识点 """ 1.训练过程: 1.准备好特征和目标值 2.建 ...

  9. [PyTorch 学习笔记] 7.1 模型保存与加载

    本章代码: https://github.com/zhangxiann/PyTorch_Practice/blob/master/lesson7/model_save.py https://githu ...

随机推荐

  1. PS 图层后面有索引两字怎么办

    ps中图层后面有索引两字的怎么把它拖进别的图中?或怎么把索引去掉? 悬赏分:0 | 解决时间:2010-11-5 08:58 | 提问者:jk500pk 最佳答案 图像--模式 把索引颜色模式改成RG ...

  2. 记录:50多行程序中找出多写的一个字母e

    小霍同学调程序,做的是第11周的项目1 - 存储班长信息的学生类,可是她写的程序(就在以下),呃,请读者自己执行一下吧.(下午在机房调试时用的是Code::Blocks10.05.输出的是非常长的莫名 ...

  3. hdu 3746 Cyclic Nacklace (KMP求最小循环节)

    //len-next[len]为最小循环节的长度 # include <stdio.h> # include <algorithm> # include <string. ...

  4. eclipse无法启动问题记录

    几天没开eclipse,居然报错“can not unload……”,搜索答案发现没有准确的,遵从了一个多人顶赞的办法重下eclipse,把配置文件拷贝一份,结果悲剧了,虽然能够打开了,但我之前配置的 ...

  5. GDI泄露+改EXE名

    CDC 应该是成对使用 GetDC and ReleaseDC(不用new and delete) 泄露 1.改变生产exe名称:工程->设置->连接->输出文件名:Release/ ...

  6. Android:在子线程中更新UI的三种方式

    ①使用Activity中的runOnUiThread(Runnable) ②使用Handler中的post(Runnable) 在创建Handler对象时,必须先通过Context的getMainLo ...

  7. React创建组件的三种方式比较和入门实例

    推荐文章: https://www.cnblogs.com/wonyun/p/5930333.html 创建组件的方式主要有: 1.function 方式 2.class App extends Re ...

  8. Tomcat调优策略

    Jmeter压力测试工具 JMeter是一款在国外非常流行和受欢迎的开源性能测试工具,像LoadRunner 一样,它也提供了一个利用本地Proxy Server(代理服务器)来录制生成测试脚本的功能 ...

  9. 深入浅出Oracle学习笔记:Buffer Cache 和Shared pool

    Buffer cache 和 share pool 是sga中最重要最复杂的部分. 一.Buffer Cache 通常数据的读取.修改都是通过buffer cache 来完成的.buffer cach ...

  10. hash与map的区别联系应用(转)

    一,hashtable原理: 哈希表又名散列表,其主要目的是用于解决数据的快速定位问题.考虑如下一个场景. 一列键值对数据,存储在一个table中,如何通过数据的关键字快速查找相应值呢?不要告诉我一个 ...