添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
打盹的冰棍  ·  小米/红米 AC2100 ...·  1 周前    · 
苦恼的草稿本  ·  分位数_百度百科·  1 月前    · 
失落的小虾米  ·  USB3 Vision driver ...·  4 月前    · 
善良的稀饭  ·  D3.js(三) ------- ...·  10 月前    · 

Fbx 转化为 md5

fbx md5 都是 3d 模型格式就不多作介绍了,直接进入主题;我们的目标是把 fbx 转化为 md5 md5 mesh animator 是分开的, mesh animator 会分别介绍。

既然要解析 fbx ,那么就必须先对其足够的了解,才能对解析出来的数据做正确的处理,少走弯路。

FBX(这里转载翻译后的fbx简单说明):

fbx是一种封闭的模型格式,这不仅说它通常作为二进制文件出现,而且是目前只能使用Autodesk提供的FBX SDK来操控这种文件。事实上,无论是3DS Max还是其SDK内置的Converter工具,都可以把其转换成ASCII的文本格式,虽然看上去有点JSON的样子,可事实上是全自给的“仅供观赏”的数据堆,也没有spec,通常也不会有人使用这种方式输出模型。好了,看来是必须借助其SDK了,所以首先要做的一件不太让人愉快的事情:加入这个SDK的库(lib&dll)。现在我用的是最新的fbxsdk-2013.3,下文仅就这版本兼容的模型而言。(注意,在预处理器选项中加入FBXSDK_NEW_API和FBXSDK_SHARED,如果你不想编译器抱怨一大堆的话。)

这里首先说一下,对于Fbx,它保存的最大集合是一个Scene(场景),跟很多的游戏引擎所使用的概念是一致的,就是用场景节点树来组织成一个场景。以前的3ds虽然也是树状地组织数据,但它是把不同类型数据堆抽象成节点,而Fbx/Dae则是纯粹地表述场景节点。所以对于后者们来说,即使把场景中多个物体/模型保存到同一个模型文件里,也是可以的,只不过是不同名字的节点而已,甚至把灯光、相机也可以抽象成节点而已。当然,对于我们编程者来说,一个模型文件仅仅对应一个模型是最自然的。所以接下来我只会谈及抽取模型和动画本身信息(事实上动画信息并非必须的——fbx和dae文件在没有动画或骨骼信息时,也就是静态模型了),不相关的部分则不涉及也不关心。

因为有其自身的SDK帮我们分析fbx文件,所以对于fbx,只要去获取SDK的FbxImporter读取的结果来为我们所用就可以了——问题是要知道怎么获取我们需要的数据,你要让它直接告诉你每个网格数据的各个直接可用的顶点属性数组,那可为难了。我们还是必须把它分析出来的数据,转换成我们需要的数据的。所以还是先想清楚我们需要什么数据:1.动画信息(如果有的话);2.网格信息;3.关联前两者的骨骼信息(如果有的话)。在导入MD5模型时,其实也是一个寻找这些信息的过程(不过MD5可是把动画信息另外封成一个md5anim文件而已)。

C++代码

1. FbxScene *pScene = FbxScene::Create(pFbxManager, "ImporterScene" );

3. pSdkImporter->Import(pScene);

5. PrepareAnimationInfo((Scene*)pScene);

7. FbxNode *pRootNode = pScene->GetRootNode();

9. if (pRootNode)

10. {

11. for ( int i = 0; i < pRootNode->GetChildCount(); ++i)

12.     {

13.         FbxNode *pNode = pRootNode->GetChild(i);

15.         ProcessNode((Node*)pNode, szResDirectory);

16.     }

17. }

19. SetupJointKeyFrameInfo();

在这里,我首先获取到这个场景(Scene),然后用PrepareAnimationInfo来预先查找场景中存在的动画信息(这里只简单查询动画的基本信息,通过Scene内的各FbxAnimStack下查各FbxAnimLayer,每个Anim Layer保存着一个动画,这里是把这些Layer的地址先存起来),然后获取场景的根节点,逐个处理其下属节点( ProcessNode函数里再轮询处理该节点的下属节点,递归调用 ProcessNode,完成整棵场景树的深度遍历),最后就是SetupJointKeyFrameInfo,获取具体的动画数据并把前两者的信息结合起来。

场景节点除了网格对象(FbxNodeAttribute::eMesh)外还有其他多种类型,前面说过了,省略。对于每个Mesh网格对象,我们要得到它的几个顶点属性数组:位置、纹理坐标、影响的骨骼点(Joint)个数,以及这些骨骼点的索引(Index)和影响因子(Bias),另外对于法线切线这些,也可以顺便获取也可以自行计算。注意除了位置(和顶点属性索引)外其余属性并非每个mesh都有,注意判断了。其中,获取骨骼蒙皮信息(也就是这个Mesh的Skin)还是很值得注意的:

C++代码

1. FbxSkin *pSkinDeformer = (FbxSkin *)pMesh->GetDeformer(0, FbxDeformer::eSkin);

3. for ( int i = 0; i < pSkinDeformer->GetClusterCount(); ++i)

5.     FbxCluster *pCluster = pSkinDeformer->GetCluster(i);

7. int nInfluencedPointIndexCount = pCluster->GetControlPointIndicesCount();

8. int *pInfluencedPointIndice = pCluster->GetControlPointIndices();

9. double *pInfluencedPointWeights = pCluster->GetControlPointWeights();

11. if (pLinkingBoneNode && pInfluencedPointIndice && pInfluencedPointWeights)

12.     {

13.         t3DJoint *pJoint = new t3DJoint((pCluster->GetLink()->GetName());

15.         pCluster->GetTransformMatrix(transMatrix);

16.         pCluster->GetTransformLinkMatrix(transLinkMatrix);

18.         GetMatrixValue(transMatrix.Inverse(), &pJoint->mtPreFramePosed);

19.         GetMatrixValue(transLinkMatrix, &pJoint->mtBindPose);

21.         transLinkMatrix = transLinkMatrix.Inverse() * transMatrix;

22.         GetMatrixValue(transLinkMatrix, &pJoint->mtPostFramePosed);

24.         AddJoint(pJoint);

26. for ( int iPtIndex = 0; iPtIndex < nInfluencedPointIndexCount; ++iPtIndex)

27.         {

28. int nVertIndex = pInfluencedPointIndice[iPtIndex];

30.             tVertWeights.nAttachJointIndex = GetJointCount() - 1;

31.             tVertWeights.fWeightBias = ( float )pInfluencedPointWeights[iPtIndex];

33.             std::map< int , std::vector<t3DVertWeights>>::iterator pFind = VertJointInfo.find(nVertIndex);

35. if (VertJointInfo.end() != pFind)

36.                 pFind->second.push_back(tVertWeights);

37. else

38.                 VertJointInfo.insert(std::make_pair(nVertIndex,

39.                     std::vector<t3DVertWeights>())).first->second.push_back(tVertWeights);

40.         }

41.     }

42. }

可见,获得这个网格的skin后,就可以去查询这个skin内的各个cluster了,每个cluster其实就对应一个joint【骨骼节点】(FBX SDK内习惯叫Bone【骨骼】,其实本质都是一样的,我们需要的是影响骨骼的对应数量的矩阵),接下来保存一个<int, std::vector<t3DVertWeights>>的map的过程就不多说了,这里的key值是给SDK索引顶点用的顶点index(注意不是顶点属性索引,而只是单纯顶点的索引),value就是对应的joint信息,对应多少个joint,vector里就存多少组信息。接下来最好像导入MD5时那样把数量规范化到4个以下,不然就不好传入vertex shader了。注意这里代码的重点:生成Joint的同时,也要获取对应的矩阵信息。

FBX不像MD5那样还要自己计算bindpose下的顶点坐标,但是还是需要知道对于每个Joint,怎样把顶点从bindpose空间转换到模型空间。在MD5中,这个转换只需乘以bindpose矩阵的逆矩阵就OK了,可是Fbx里可像是没那么简单哦(这还是我碰壁后去翻sdk的例子程序里的代码比对才知道的,那个惨):参见上面的代码,每个cluster(joint)可以通过GetTransformMatrix和GetTransformLinkMatrix获取两个矩阵(前者我也不太知道具体意义是啥,不妨自己望文生义一下,后者看来就是bindpose矩阵咯),不妨设为MTrans和Mbindpose。把顶点从bindpose空间转换到模型空间的“Joint影响矩阵”:

Mjoint‘   =  MTrans -1   *  Mjoint  *   Mbindpose-1   *   MTrans

其中Mjoint是当前帧下的该骨骼Joint的变换矩阵(在之前也说过了,就是由该joint的位移旋转缩放信息构成,相当于该Joint的模型矩阵,注意这里不像MD5里可以省略缩放信息,FBX和DAE的动画信息里都是包含缩放信息的说),等式右边是传入shader的joint影响矩阵。对比MD5的公式,可以看到这里多了个“程咬金”:MTrans,居然还分左右地夹在两边,左边是逆矩阵,右边是原矩阵。这样,在每帧计算Joint矩阵时可别忘了它咯。在代码中,考虑到模块统一的问题,Mjoint的左右两边干脆被我封在mtPreFramePosed和mtPostFramePosed中了……

接下来谈一下网格信息。Mesh类型的Node都能获得对应的FbxMesh,顶点属性大致是GetElement类函数获取(再根据GetMappingMode/GetReferenceMode来看怎样具体通过GetDirectArray/GetIndexArray获取数据,这点应该来说是好麻烦的,不过人家也是为了尽量压缩冗余数据)。还有一点就是fbx里保存的不一定是三角面片,也可能是四角面或多角面,为了为我们所用,须通过一些方法转换成三角面的索引顺序(如下的多重循环)或者直接通过SDK自带的FbxGeometryConverter来预先三角化(TriangulateInPlace)。

C++代码

1. for ( int i = 0; i < pMesh->GetPolygonCount(); ++i)

3. int nPolySize = pMesh->GetPolygonSize(i);

5. if (nPolySize < 3) continue ;

7. for ( int nTriCount = 3; nTriCount <= nPolySize; ++nTriCount)

8. for ( int k = nTriCount - 1; k < nPolySize; ++k)

9. for ( int j = nTriCount - 3; j < nTriCount; ++j)

10.             {

11. if (j == nTriCount - 1)    j = k;

13. int nVertexIndex = pMesh->GetPolygonVertex(i, j);

15.                 vPosition = mtBindShape * pMesh->GetControlPointAt(nVertexIndex);

16.                 //.....

这里更重要的是,得出的顶点(fbx内称control point)须进一步经过一个矩阵(mtBindShape)变换一下。这个叫做BindShape矩阵的矩阵,我的理解是,有时候模型制作者绑定骨骼节点参数时的基准并不是bindpose状态而是稍微对每个网格经过一个调整(缩放旋转移位)后再进行的,那么导出时就会给每个mesh生成这样个BindShape。看sdk自带例子中的这一步,应该就是获取此矩阵的方法了:

C++代码

1. const FbxVector4 lT = pMesh->GetNode()->GetGeometricTranslation(FbxNode::eSourcePivot);

2. const FbxVector4 lR = pMesh->GetNode()->GetGeometricRotation(FbxNode::eSourcePivot);

3. const FbxVector4 lS = pMesh->GetNode()->GetGeometricScaling(FbxNode::eSourcePivot);

5. GetMatrixValue(FbxAMatrix(lT, lR, lS), &mtBindShape); //生成矩阵

在获得网格顶点信息、顶点骨骼信息、纹理信息之后,对于这个网格,还需要判断它是直接由骨骼驱动,还是通过Attach的方式(例如武器)绑在其他节点或骨骼上,这个对于模型正确性来说还是比较重要的。而最后,根据动画信息(之前获得的AnimLayer)和骨骼,通过GetCurve-KeyGetCount来获取关键帧的时间集,一一去计算出骨骼节点在每个关键帧时间点的变换矩阵(EvaluateGlobalTransform),即Joint矩阵。fbx文件看上去内部似乎真的存储了一个一个属性曲线(Curve)一样,非得弄这种类似采样的方法去获取动画过程中的各属性值,但相信其实存储的也就关键点和值,比起构造Curve-Sampling的方式,直接能够取得关键帧的各信息肯定效率更高,但SDK内没找到类似接口——结果是,模型导入的大部分耗时都花在EvaluateGlobalTransform这类函数上了。

准备工作 :

首先要解析 fbx 文件,这就要用到 fbx sdk ,安装好 sdk 之后,准备工作就算完成了,通过 sdk 可以直接或者间接的取到我们想要的数据,然后保存成我们希望的格式,这里的目标格式是 md5 ,那么就从这里着手开始解析 ~

Md5mesh 文件头 :

MD5Version 10  // 版本信息

commandline "" // 附加信息

numJoints 25 // 骨骼数量

numMeshes 1 // mesh 数量

1 行和第 2 行没什么好说的,固定写法都可以,第 3 行记录的是骨骼数量,这里要注意的是 md5 中骨骼的意义和 fbx 以及 max 中的意义都不一样, fbx 中的骨骼就是指的 cluster ,受权重影响;而 md5 中的骨骼时指的节点( nodes )信息,是场景根节点( RootNode )的子节点,节点信息和骨骼信息有重复的地方,不重复的地方不受权重影响,理论上每个骨骼都有其对应的节点,通过 (getLink) 接口获取 . 4 行是 mesh 数量,每个模型都可以拆分成多个 mesh ,格式都是一样的。

Md5mesh 数据结构:

joints {

"root" -1 ( -0.00062962 0.745378 20.3006 ) ( -0.799258 0.00024658 -0.00350078 ) //骨骼名称 父节点索引 骨骼位移分量    骨骼旋转分量(四元数)

接下来就是骨骼信息了,骨骼名称应该很好理解,第 2 列是指当前节点的父节点的索引, -1 代表跟节点,然后骨骼索引由 0 依次递加,只要事先保存好不是什么难题;第 3 列括号中的数据是绝对坐标,通过 EvaluateGlobalTransform接口获取,会得到一个矩阵,姑且先叫 matrix ,这里需要注意的地方主要有两点,第一点是 md5 所用的坐标系是左手坐标系, fbx 中则是右手坐标系,这里必须要转换一下,转换只需要把得到的矩阵乘以 AXIS_FLIP_L = FbxAMatrix(FbxVector4(0, 0, 0), FbxVector4(-90, 180, 0), FbxVector4(-1, 1, 1))就可以了;第 2 点, md5 不仅坐标系和 fbx 不一样,坐标轴也不一样, fbx max 一样,竖直的那根轴是 Z 轴, md5 中竖直的却是 Y 轴,这就是为什么用 API 加载站立的人物模型后是躺着的原因,所以需要把 Y Z 调换位置,当然旋转分量也需要做同样的处理;旋转分量的除了要做以上处理外还要注意一点,因为这里记录的是四元数,假设有四元数 q ,那么 q -q 代表的实际角位移是相同的,如果我们将角位移α加上 360 °的倍数,不会改变 q 代表的角位移,但使他 q 的四个分量都变负了,因此, 3D 中的任意角位移都有两种不同的四元数表示方法,他们相互为负。可想而知,同一种文件格式总不能用两种表示方法吧,所以当四元数的 w 分量为负的时候,需要把前 3 个分量的值取负,统一格式,才是我们真正需要的数据。

mesh {

// meshes: np134

shader "Material #35"

numverts 415

vert 0 ( 0.442953 0.785037 ) 0 1 // 顶点索引 uv 顶点在权重列表中第一个权重的索引 权重列表 的长度

vert 1 ( 0.500942 0.772206 ) 1 1

numtris 499

tri 0 0 2 1 // 三角形索引 后面三列是组成三角形的顶点索引

tri 1 2 0 3

numweights 356

weight 0 12 1.0 ( 1.12677 -0.153641 -0.876021 ) // 权重索引 权重所对应的骨骼索引 权重值 权重平移元素

weight 1 12 1.0 ( 0.238282 -0.971952 -1.21794 )

然后就是 mesh 信息了, mesh 主要分为 4 个部分

第一部分记录了 mesh 的名称和 shader 信息,这些可以通过 sdk 直接获得,取到 mesh 节点和 material ,直接 getName 就可以获得。

第二部分是顶点信息, 3D 中顶点分为纹理顶点和几何顶点, md5 中只记录纹理顶点,第一列是顶点索引,依次递加;第 2 列是 uv 信息,这里需要注意的是 uv 信息虽然可以直接取到,但是 v 分量是和 fbx 中的 v 分量互补的,也就是 2 者相加为 1 ,所以用 1 减之就可以了,获取方式也很简单,先通过 GetElementUV(index)获取相应的 uv 信息, index mesh 索引,在通过 GetDirectArray()获取 uv 值;第 3 列数据比较麻烦,由于权重长度不一,顶点对应的权重也不是不规律的,我们需要先把权重索引罗列出来, md5mesh 中只记录了顶点在权重列表中第一个权重的索引,也就是说要根据权重长度来把多余的长度跳过,假如说第一个顶点对应的权重列表长度为 2 ,那么整个第 3 列数据就不应该存在 2 这个索引,因为被第一个顶点所对应的权重列表的第 2 个权重占用了,所以我们先要保存一个 dictionary 记录几何顶点所对应的纹理顶点和权重列表的长度,这里提到了几何顶点,虽说 md5 中并不记录他,但是只有几何顶点才跟权重和骨骼有关,这里必须要用到,几何顶点索引可以通过 GetPolygonVertex接口获取, uv 顶点索引通过 GetTextureUVIndex获取,这里可以打印出来观察一下,可以发现 2 者的长度是相同的,也就是说他们有 11 对应的关系,那么现在我们把他保存到一个 dictionary 中,可以去除掉重复的数据,我们就得到了一个纹理顶点和几何顶点 11 对应的数据结构;第 2 步,我们刚才说到了权重长度会影响权重列表第一个索引的记录,所以这个长度就一定要求到,可以通过 GetControlPointWeights得到每一个顶点所对应的权重,然后保存到一个列表中,再把这些列表作为元素保存一个新的列表,这些列表的长度决定了下一步的逻辑;第 3 步,既然顶点的对应关系和权重长度都得到了,我们就干脆把他们保存在一起,可以做成一个新的 dictionary key 为纹理顶点索引, value 为一个列表,保存了想对应的几何顶点索引和权重长度;最后一步就是输出到文件了,顶点索引和 uv 索引依次递加即可,每个顶点在权重列表中第一个权重的索引可以通过纹理顶点和几何顶点对应关系的列表中获取相应的几何顶点索引,再通过这个索引在新的 dictionary 中找到对应的列表信息的第一个值(几何顶点索引,权重长度为第 2 个值),这样顶点部分就算导出完成了。

3 部分为三角形信息,第一列当然是依次递加的三角形索引,后面三列分别为组成三角形的 3 个顶点的索引。

这里主要需要注意的地方可能是 f bx文件中包含的mesh不一定是由三角形组成,还可能是四边形,五边形等等,因此,要做的第一步,就是三角化mesh,可以用以上两种方法实现。

TriangulateMesh和TriangulateInPlace区别在于前者返回一个三角化之后的新mesh,后者则是对当前数据进行三角化。注意TriangulateInPlace之后需要重新获取mesh指针,否则代码会出错。Mesh类的大部分成员函数用途都一目了然,只是有一些概念需要注意:

1. GetPolygonCount() 返回三角形数量;

2. GetControlPointsCount() 返回控点数量,这里控点的概念和DirectX中常说的顶点非常类似,但不完全一样,更像是只包含了position的顶点。也就是说如果这个顶点被n个多边形共享(比如立方体八个角的点),而在每个多边形上又有不同的纹理坐标或者法线,那么稍后将分裂或者说生成n个包含position,normal,uvs等信息的顶点;

3. GetControlPoints () 返回控点数组指针;

4. GetPolygonVertexCount() 这是个迷惑人的名字,这个函数返回的其实是大家熟悉的vertex index count,对triange list来说,其实就是GetPolygonCount() * 3;

5. GetPolygonVertices() 返回索引数组指针;

当然这些接口都无法得到我们想要的数据,既然md5中记录的是纹理顶点,那么理所当然,三角形也要由纹理顶点组成,这个数据还要从uv信息中获取,接口

GetDirectArray() 获取到的是 uv 值,那么 GetIndexArray() 获取到的就是顶点排列顺序,把这个顺序每 3 个一组直接输出到文件,就是我们想要的 3 列数据了。

4 部分是权重信息,第一列依然是依次递加的权重索引;第 2 列为权重所对应的骨骼索引; 第 3 列为权重值,也就是我们刚刚求权重长度的时候那个权重列表中所记录的数据;第 4 列括号中的数据为权重相对骨骼的平移分量,这个数据通过 sdk 并不能直接获得,需要转换一下,具体下文中会说。

首先遇到的问题肯定是权重所对应的骨骼索引,这里我们需要做一件事情,关于权重这块,之前在做的时候也是很头疼,因为骨骼概念的不一样,导致总是误解,其实简单来说,只要跟权重挂钩了,那么就没节点啥事了,但是有个问题就是权重信息中是要记录对应的骨骼索引的,那不索引不就错乱了么?

后来通过观察才发现,骨骼其实也是节点,只不过和节点有点区别,节点并不关联权重,我们只需要把所有的骨骼在节点列表中的索引取到就可以了。

那么骨骼在权重列表中的排列顺序如何取得呢?其实这个也无法直接获取,但是我们可以通过GetCluster( index )( index 为递增常量)接口获取每根骨骼所对应的几何顶点(这里的骨骼是指 cluster 而不是 node ),虽然不知道为什么,得到的就是我们想要的索引排列顺序,然后根据骨骼名称从节点列表中找到相应的索引即可。

权重值这一列就比较简单了,因为之前已经拿到了数据,直接依次输出到文件即可。

最后就是权重的平移分量了,我们可以通过GetControlPointAt( index )取到每个顶点的平移分量,但是并不是我们所需要的数据,权重平移的话,理论上应该受顶点影响,但是他们的关系仅仅通过观察数据是很难了解的,即使我在写本文的时候,我还是不知道他们到底是什么关系。后来想到了坐标系的问题,因为坐标系不一样,所以坐标数据不一样也是很正常的,结果转来转去,数据也不对,也就是说不是这个原因。解决问题的方法是用 GetTransformMatrix接口得到的矩阵的逆矩阵乘以GetTransformLinkMatrix接口所得到的矩阵得到的新矩阵的逆矩阵再乘以对应的顶点平移向量,这样就得到了一个 fbxVector4 的数据结构,这样说有点烦,直接看 python 代码如下 :

weightPos = []

tempIndex = 0

for i in range(clusterCount):

cluster = skinDeformer.GetCluster(i)

indexCount = cluster.GetControlPointIndicesCount()

clusterIndices = cluster.GetControlPointIndices()

aMatrix = FbxAMatrix()

tanMatrix = FbxAMatrix()

for index in clusterIndices:

cluster.GetTransformLinkMatrix(aMatrix)

cluster.GetTransformMatrix(tanMatrix)

aMatrix = tanMatrix.Inverse() * aMatrix

tempIndex += 1

weightPos.append(aMatrix.Inverse().MultT( self.fbxMesh.GetControlPointAt( index ) ))

weightpos 中记录的就是最终我们所需要的数据,一次输出到文件,这样 md5mesh 最基本的的数据就算导出完成了。

其实应该还有 material boundsbox 等信息,因为用不到,所以就没去做导出。

下面介绍动作部分 :

md5anim md5mesh 是相对独立的两个文件 , 所以要单独拉出来介绍

Md5anim 文件头:

MD5Version 10      // 版本信息

commandline "" // 命令行信息

numFrames 63 // 总帧数

numJoints 25 // 骨骼数

frameRate 30 // 帧率

numAnimatedComponents 103 // 动画帧参数

同样的,版本信息和命令行信息不用管他,固定写法即可。

总帧数就是该动作总共有多少帧。

骨骼数的意思和 md5mesh 中是一样的,就不多说了。

帧率就是每秒播放的帧数。

这个动画帧参数有点复杂,这里也不太好说,后面再介绍。

hierarchy {

"root" -1 30 0

"RPelvis" 0 0 0

"RThigh" 1 62 4 // 骨骼名称    父节点索引    变化的 flags 变化数据在帧中的索引

骨骼名称和父节点索引和 md5mesh 中的意思一样。

变化的 flags 暂时也不太好说,下面跟动画帧参数一起介绍。

变化数据在帧中的索引,这里也不太好说……

baseframe {

( -0.00062962 0.745378 20.3006 ) ( -0.799258 0.000246581 -0.00350078 )

// (位移分量) (旋转分量)

Baseframe 中记录了模型的初始信息,与 mesh 不同的是,数据都是相对父节点的,也就是相对坐标。

frame 0 {

0.00025

-0.026168 -0.000137

-1.070026 0.241914

0.032356

-0.632515 0.06263

0.000132

-1.051603 -1.015528 0.252326 0.110814 0.488949

-0.996936 0.001161

-0.853822 -0.930709 0.048477

0.140578 0.002583

0.017588 -0.976601 0.019574 0.079625

-0.186378 0.134085

-0.094903 0.109751

0.117249 -0.629433 0.056689 0.001488 0.106753

-0.43033 0.021295 4e-06 0.054184

0.037597

0.14227 0.173913 0.727972

-0.505419 0.026972 0.005023

0.040113 0.001301

0.257536 0.718552 0.050576 0.219866

这里截了一整段帧数据,不是没意义的;我们可以看到数据是参差不齐的,那么 md5 中这样记录数据到底是为什么呢?

接下来我们可以解释上面一系列字段的意思了:

md5 为了缩减文件占用的空间,帧中未变化的数据就不记录了,所以要把变化的数据做一个标示,在写帧中数据的时候跳过未变化的数据;因为位移和旋转分量的总数为 6 ,我们把他看做是一个 6 位的 2 进制数,然后每一帧数据和 baseframe 中的数据做与运算,得到的 2 进制数再转换成 10 进制作为 hierarchy数据块的第 3 列数据;

既然帧中记录了参差不齐的数据,那么我们就必须再加一个字段来表示每根骨骼在帧中数据到底从那一个开始,比如第一根骨骼 root flag 30 ,那么就表示 2+4+8+16 ,很明显是位移分量的 y z 和旋转分量的 x y ,总共占了 4 位,长度也就知道了,那么第 2 根骨骼就必须从第 5 位开始,也就是索引 4 ,但是我们发现第 2 根骨骼居然是从 0 开始的,这是为什么呢?

之前说过,帧中变化的数据要在第 3 列中标示,既然第 2 根骨骼标示为 0 ,那就意味着此骨骼无动画;所以既然此骨骼没动作,那就意味着帧中不需要记录,从第几根开始也就没所谓了;我们可以看到第 3 根骨骼是从索引 4 开始的。

以此类推,我们就可以得到所有骨骼标示和开始索引,然后我们只需要把每根骨骼中需要变化的数据总数累加之后,就得到了文件头中numAnimatedComponents的值。

解析之后发现的问题:

如果解析带有动作的 fbx 文件,导出的 md5 文件可以正常运作;

单独的 fbx 的动作信息中不会记录未改变的骨骼,导出的 md5anim 中骨骼就会不全,导致无法与 mesh 中的骨骼匹配,所以不要把 fbx mesh 和动作分开;如果一定要分开的话,就需要用 max mesh animator 一起导入,然后再用插件导出 , 或者资源规范化操作,同一目录下只放单个模型的 mesh 和动作,这样就可以取得单个模型的骨骼数据保证不出错

· cslgpanda

2015.12.22

2者都是模型格式,不要问我为什么要这么转,也不要问我为什么不走插件,有些情况我也说不清楚Fbx转化为md5简述:fbx和md5都是3d模型格式就不多作介绍了,直接进入主题;我们的目标是把fbx转化为md5,md5的mesh和animator是分开的,mesh和animator会分别介绍。既然要解析fbx,那么就必须先对其足够的了解,才能对解析出来的数据做正确的处理,少走弯路。 命令行参数支持如下选项: -i [ fbx 文件地址] 输入的 FBX 文件源地址 -o [输出目录(可选)] 输出的 MD5 的保存目录(如果忽略将保存在源目录) 示例: fbx 2 md5 -i c:/1. fbx -o d:/model 示例: fbx 2 md5 c:/1. fbx 也可以直接将 fbx 文件,拖放到 fbx 2 md5 .exe上进行转换。
FBX 2glTF 这是一个命令行工具,用于将Autodesk古老的格式的 3D 模型资产转换为 (一种现代的运行时资产交付格式)。 可在找到适用于Windows,Mac OS X和Linux的预编译二进制版本。 Windows的出血边缘二进制文件可在找到。 Linux和Mac OS X即将面世; 同时,您可以。 可以像下面这样调用该工具: > FBX 2glTF ~/models/butterfly. fbx 或者,作为更复杂的管道的一部分: > FBX 2glTF --binary --draco --verbose \ --input ~/models/source/butterfly. fbx \ --output ~/models/target/butterfly.glb 上还有一些友好而动手的说明。 CLI开关 您始终可以使用--hel
●该程序能将 fbx 动画导出 md5 骨骼动画,封装的exe程序(非max脚本,非插件). ●输入的 fbx 和输出的 md5 坐标系都是采用Z朝上的max坐标(标准 Md5 坐标系). ●输入的 fbx 路径和文件名只能是英文,输出路径可为中文. ●数据算法原创作者:Gaara(讨论群:252538785) 提取修改:迷雾森林 ●最后,感谢Gaara大神,让我在 3d 这条不归路上越走越远!
支持将 FBX 模型格式转换成 md5 mesh和 md5 anim。 历史版本Release更新日志: V3.0:支持中文路径,增加按骨骼拆分导出模型,优化顶点导出数据。[2018.04.20] V2.0:增加对虚拟对象支持,支持多维子材质拆分面,增加tga,bmp,psd,png,jpg贴图转换导出功能。 V1.0:实现基本转换功能。
FBX 格式和obj格式的区别 目前主流常用的通用模型格式 FBX 格式和OBJ都是众多可以用在众多软件平台的。既然两个都是通用模型格式,那么 FBX 格式和obj格式的区别到底是什么呢?很多三维设计师朋友都听说过这两个格式,但却不知道该如何选择,接下来就为大家解读对比一下吧。 1. FBX 格式和OBJ格式简介 FBX 模型是一种通用模型格式,支持所有主要的三维数据元素以及二维、音频和视频媒体元素,Aut...
MarvelousDesigner格式 + Maya格式文件 引用文章连接:https://blog.csdn.net/Allen7474/article/details/130598317 文件中包含了 MD 的解算文件,和Maya将布料导入ABC后通过BlendShape转为 FBX 的过程文件。
在 Blender 中,你可以使用多个材质节点来创建一个物体。如果你想将这个物体导出为 FBX 文件,并且保留所有的材质节点,可以按照以下步骤进行操作: 1. 在 Blender 中选择你要导出的物体。 2. 在材质选项卡中,为每个材质节点选择一个唯一的名称。 3. 选择文件 > 导出 > FBX 。 4. 在导出设置中,选择“选中对象”并确保选择“所有节点”。 5. 在“材质”选项卡中,选择“所有材质”。 6. 点击“导出”按钮,将文件保存为 FBX 格式。 这样,你就可以将包含多个材质节点的物体导出为一个 FBX 文件,并且保留所有的材质信息。