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

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);
}
}
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。

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

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

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

    6. 加载Camera和Light 在FBX模型中除了几何数据外较为常用的信息可能就是Camera和Light,虽然在游戏中一般不直接从模型中得到这两部分信息,而是由引擎来提供,但是FBX中提供了对这 ...

  2. Html监听Fbx文件加载,FBX格式mesh解析与加载(一)

    FBX格式mesh解析与加载(一) FBX格式mesh解析与加载(一) ** 理解FBX格式中Mesh数据结构** fbx文件是现在许多建模动画软件和游戏引擎之间共用的模型文件格式.fbx文件分为两种 ...

  3. 微信小程序实现FBX模型的动画加载

    鉴于有CSDN友问我FBX模型在小程序端加载的问题,我就在这里给大家介绍一下吧~ 首先,加载fbx模型,我们用到的是three.js和不同的模型类型的加载库,那么,我们在得到了web版本的加载库的前提 ...

  4. nuScenes自动驾驶数据集:格式转换,模型的数据加载(二)

    文章目录 一.nuScenes数据集格式精解 二.nuScenes数据格式转换(To COCO) 数据格式转换框架 2.1 核心:convert_nuScenes.py解析 其他格式转换文件 2.1. ...

  5. nuScenes自动驾驶数据集:数据格式精解,格式转换,模型的数据加载 (一)

    nuScenes数据集及nuScenes开发工具包简介 文章目录 nuScenes数据集及nuScenes开发工具包简介 1.1. nuScenes数据集简介: 1.2 数据采集: 1.2.1 传感器 ...

  6. pytorch模型保存与加载总结

    pytorch模型保存与加载总结 模型保存与加载方式 模型保存 方式一 只存储模型中的参数,该方法速度快,占用空间少(官方推荐使用) model = VGGNet() torch.save(model ...

  7. 人工智能算法之梯度下降法、协同过滤、相似度技术、ALS算法(附案例分析)、模型存储与加载、推荐系统的冷启动问题

    梯度下降法 求解机器学习算法的模型参数,即无约束优化问题时,梯度下降法是最常采用的方法之一,另一种常用的方法是最小二乘法.这里对梯度下降法做简要介绍. 最小二乘法法适用于模型方程存在解析解的情况.如果 ...

  8. tensor和模型 保存与加载 PyTorch

    PyTorch教程-7:PyTorch中保存与加载tensor和模型详解 保存和读取Tensor PyTorch中的tensor可以保存成 .pt 或者 .pth 格式的文件,使用torch.save ...

  9. Spring解析,加载及实例化Bean的顺序(零配置)

    点击上方蓝色"方志朋",选择"设为星标"回复"666"获取独家整理的学习资料! 作者:jb_hz blog.csdn.net/qq_2752 ...

  10. 页面加载完毕_【前端面试】dom 的解析,加载,渲染

    本文会把 dom 的解析,加载,渲染结合 window.performance 一起讲. dom 的解析 解析:HTMl 解析器把 HTML 构建成 HTML 树形数据结构,也就是 DOM 树. 注意 ...

最新文章

  1. NYOJ 90 —— 求正整数n划分为若干个正整数的划分个数
  2. zw版【转发·台湾nvp系列Delphi例程】HALCON Histogram
  3. unix环境高级编程-进程间通信
  4. .net 默认时间格式不正确
  5. 天池入门赛--蒸汽预测
  6. java iterator_Java ArrayDeque iterator()方法与示例
  7. 自己开发的ASP.NET分页控件2.0 (Ling.Pager)
  8. RDS数据库全量恢复方案
  9. 计算机网络期末4小时速成
  10. 18-FreeSwitch-配置G729转码
  11. 佳佳数据恢复软件免费版
  12. 清华大学和ubc计算机哪个好,2021世界大学排名出炉!多大进前20!清华UBC并列!...
  13. 第五章、Zigbee模块的数据传输
  14. 工作杂谈(十五)——谷歌学术搜索网站
  15. 计算机视觉与机器学习之6σ问题
  16. 计算机硬件技术基础(太原理工大学):第二章
  17. 经验分享给你!小伙利用业余时间听歌赚钱,一个月挣了6000?
  18. 美团3.12笔试题解
  19. 你好,法语!A2课文汇总
  20. Will的将来时态_40

热门文章

  1. 考察一名UI设计师的能力素质模型(转)
  2. python学习-day9内置函数(高阶)
  3. 大二暑假立秋学习总结
  4. PHP获取中国所有的大学,全国300所大学的BBS论坛.doc
  5. virtualBox报错 0x80004005
  6. python 蒙特卡罗_python实现蒙特卡罗方法(代码示例)
  7. python模拟行星运动_动态模拟运行太阳系的行星运转
  8. bp神经网络是前馈网络吗,什么是前馈神经网络
  9. 如何实现android设备进入recovery界面后自动重启
  10. 如何实现 ASP.NET Core WebApi 的版本化