导出骨骼动画树
对FBX人物动画文件的骨骼动画节点树中每一个节点的动画帧进行导出时,导出数据为骨骼local space中的offset,传统的导出方式:
out.position[frame] = in.position[frame];
out.rotation[frame] = in.rotation[frame];
out.scale[frame] = in.scale[frame];
但这样在实际应用在导出FBX人物动画场景下可能会出现严重的问题(3dsmax默认使用单位是inch)
1.父节点坐标系的Scaling变化。
如下图,A是B的父节点,那么A节点的坐标系进行Scaling变化时,B是否要变化呢?如果不变,那么可以想象海贼王路飞的伸缩手臂,当只有锁骨部分无限拉长,且其他部位纹丝不动,测试,它是无法像动漫里一样拉伸的。所以当父节点(骨骼绑定在节点上,可以理解为骨骼继承了节点)出现Scale变化时,是需要引起子节点的变化,即A节点Scaling变化,那么所有的子节点都要进行Scaling变化,那么如何变化呢?当然是通过程序运算而来=-=。FBX骨骼动画文件存储的KeyFrame数据都是相对于当前骨骼的局部坐标系,我们需要根据当前节点的Scale作为子节点的全局缩放,一路递归运算…
FBX骨骼动画文件的KeyFrame信息:
2.单位问题(1英寸=0.0254米)。
FBX的keyFrame数据导出时,英寸和米的单位换算也会造成动画G掉。当你使用FBX SDK提供的方法将英寸单位转为米后,移动1英寸和移动0.0254米是一样,但Scaling 1英寸和Scaling 0.0254米是一样的嘛?不是的。所以确定好动画KeyFrame的单位变化量也是一件至关重要的事情。
3.解决方式
综上,对于人物动画的导出,解决方式应该具体针对父节点坐标系的Scaling变化需要引起子节点坐标系的Scaling变化,体现在Position和Scale。以及需要注意单位变化量。话不多说,上代码。
代码
动画track的处理
void export(TrackDesc* in, TrackDesc* out, uint32 frame)
{
size_t childCount = in->GetChildrenCount();
if (childCount == 0)
return;
// 英寸的单位变化量
const float UNIT_CONVERSION = 0.0254f;
for (size_t i = 0; i < childCount; ++i)
{
TrackDesc* outChild = out->GetChild(i);
TrackDesc* inChild = in->GetChild(i);
outChild->positions[frame] = inChild->positions[frame] * globalScale;
outChild->rotations[frame] = inChild->rotations[frame];
outChild->scalings[frame] = outChild->scalings[frame]/ UNIT_CONVERSION;
// 当前节点的局部坐标系为子节点的全局坐标系
export(inChild, outChild, frame, localScale);
}
}
得到的是应用了全局缩放后的骨骼局部坐标系下的KeyFrame信息。
骨骼的处理,需要从世界坐标系转化到骨骼的局部坐标系。
Bone bone;
Bone parent = bone.getParent();
bone.scale = bone.scale / parent.scale;
bone.rotation = parent.rotation.inverse() * bone.rotation;
bone.translation = parent.rotation.inverse() * (bone.translation - parent.translation) / parent.scale();
补充部分
如何实现多个动画Track共用一个骨骼文件=》提供统一的骨骼参考标准T-pose形态下的骨骼。所有动画track数据基于T-pose下的骨骼计算offset值存在track中,这样就不需要多个动画,每一个都需要单独的骨骼的文件存储。
Vector3 bonePos = targetBone->getPosition();
Quaternion boneQuat = targetBone->getOrientation();
Vector3 boneScale = targetBone->getScale();
for (uint32 j = 0; j < frameCount; ++j)
{
Vector3 &framePos = targetTrack->positions[j];
Quaternion &frameOri = targetTrack->rotations[j];
Vector3 &frameSca = targetTrack->scalings[j];
framePos = framePos - bonePos;
frameOri = boneQuat.Inverse() * frameOri;
frameOri.Normalize();
frameSca = frameSca / boneScale;
}
trackStack.push_back(targetTrack);
这里的坐标系都是局部坐标系,在bone space中,位移的计算不需要考虑scale和rotation,因为它不受父节点的影响。