导出骨骼动画树
对FBX人物动画文件的骨骼动画节点树中每一个节点的动画帧进行导出时,导出数据为骨骼local space中的offset,传统的导出方式:
out.position[frame] = in.position[frame];
out.rotation[frame] = in.rotation[frame];
out.scale[frame] = in.scale[frame];
但这样在实际应用在导出FBX人物动画场景下可能会出现严重的问题(没出问题是因为你没用Scaling动画)
这里有2点需要注意:
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。以及需要注意单位变化量。话不多说,上代码。
代码
void export(TrackDesc* in, TrackDesc* out, uint32 frame, Vector3 globalScale = Vector3::UNIT_SCALE)
{
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];
// 计算当前节点的局部缩放
Vector3 localScale = inChild->scalings[frame] * globalScale;
outChild->scalings[frame] = localScale / UNIT_CONVERSION;
// 当前节点的局部坐标系为子节点的全局坐标系
export(inChild, outChild, frame, localScale);
}
}
得到的是应用了全局缩放后的骨骼局部坐标系下的KeyFrame信息.
补充部分
需要将导出后的骨骼局部坐标系的Track数据应用到实际角色动画中,还需进行一步处理:计算KeyFrame数据位于当前骨骼的父骨骼节点坐标系下的Offset数据。因为动画Track中当前骨骼的Offset TransForm是拿当前骨骼在Global Coordinate(父骨骼节点坐标系下)下的Offset算的,而不是Local Coordinate下的Offset,即KeyFrame数据。
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);
如:framePos - bonePos放在坐标系下,可以理解为在将当前骨骼在父骨骼下的Pos移到父骨骼的原点部分,然后应用framePos作为父骨骼的Offset,旋转和缩放同理。整个过程就是子骨骼Local Coordinate到Global Coordinate的一个转换,通过动画KeyFrame和Bone的World Information(可由骨骼的Pos,Quat,Scale计算出骨骼的World Matrix)计算出动画KeyFrame在父骨骼下的World Information,在导出动画时候计算出这些数据,方便后续Animation Track的直接应用。