2者都是模型格式,不要问我为什么要这么转,也不要问我为什么不走插件,有些情况我也说不清楚
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");
2.
3. pSdkImporter->Import(pScene);
4.
5. PrepareAnimationInfo((Scene*)pScene);
6.
7. FbxNode *pRootNode = pScene->GetRootNode();
8.
9. if (pRootNode)
10. {
11. for (int i = 0; i < pRootNode->GetChildCount(); ++i)
12. {
13. FbxNode *pNode = pRootNode->GetChild(i);
14.
15. ProcessNode((Node*)pNode, szResDirectory);
16. }
17. }
18.
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);
2.
3. for (int i = 0; i < pSkinDeformer->GetClusterCount(); ++i)
4. {
5. FbxCluster *pCluster = pSkinDeformer->GetCluster(i);
6.
7. int nInfluencedPointIndexCount = pCluster->GetControlPointIndicesCount();
8. int *pInfluencedPointIndice = pCluster->GetControlPointIndices();
9. double *pInfluencedPointWeights = pCluster->GetControlPointWeights();
10.
11. if (pLinkingBoneNode && pInfluencedPointIndice && pInfluencedPointWeights)
12. {
13. t3DJoint *pJoint = new t3DJoint((pCluster->GetLink()->GetName());
14.
15. pCluster->GetTransformMatrix(transMatrix);
16. pCluster->GetTransformLinkMatrix(transLinkMatrix);
17.
18. GetMatrixValue(transMatrix.Inverse(), &pJoint->mtPreFramePosed);
19. GetMatrixValue(transLinkMatrix, &pJoint->mtBindPose);
20.
21. transLinkMatrix = transLinkMatrix.Inverse() * transMatrix;
22. GetMatrixValue(transLinkMatrix, &pJoint->mtPostFramePosed);
23.
24. AddJoint(pJoint);
25.
26. for (int iPtIndex = 0; iPtIndex < nInfluencedPointIndexCount; ++iPtIndex)
27. {
28. int nVertIndex = pInfluencedPointIndice[iPtIndex];
29.
30. tVertWeights.nAttachJointIndex = GetJointCount() - 1;
31. tVertWeights.fWeightBias = (float)pInfluencedPointWeights[iPtIndex];
32.
33. std::map<int, std::vector<t3DVertWeights>>::iterator pFind = VertJointInfo.find(nVertIndex);
34.
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)
2. {
3. int nPolySize = pMesh->GetPolygonSize(i);
4.
5. if (nPolySize < 3) continue;
6.
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;
12.
13. int nVertexIndex = pMesh->GetPolygonVertex(i, j);
14.
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);
4.
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个顶点的索引。
这里主要需要注意的地方可能是fbx文件中包含的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