关于使用 Open Asset Import Library 读取fbx文件动画数据的问题

Magic_Light 2016-08-22 09:50:57
最近刚刚将《Introduction to 3D Game Programming with Directx 11》看完,由于个人对3D动画比较感兴趣,就使用3ds Max 2013对一个模型进行蒙皮,之后以.fbx的格式导出数据。在考虑使用API方面一开始我选了Autodesk 的 FBX SDK,才发现这个API相当不好用,想到《Introduction to 3D Game Programming with Directx 11》有介绍Open Asset Import Library这个SDK,所以才转而使用这个,虽然在使用过程中仍感迷茫,但相对FBX SDK已经好了很多,但是到了加载骨骼以及动画数据的时候就出问题了,最后整个模型被不正确的矩阵变换弄成不知道什么样子,只能大概地看到一个人上下颠倒地在动...
好了,废话少说,下面将我加载骨骼和动画数据的代码贴上来,麻烦看过这本书或者使用过Open Asset Import Library对fbx文件进行转换的高手指点一二。(我的程序框架使用的是《Introduction to 3D Game Programming with Directx 11》第25章的例程的框架,为了使用这个框架我利用Open Asset Import Library将.fbx转成书中的.m3d格式)

//
///该函数用于构建骨骼层级,获取偏移矩阵以及每个顶点的权重表
//
void Convertor::LoadBone(const aiScene* scene, std::map<std::string,int>& maps,std::vector<int>& Hierarchy, std::vector<Matrix4x4>& Offsets, std::vector<BoneVertex>& Vertices)
{
//
//建立以数组形式储存的骨骼树结构
//
auto meshs = scene->mMeshes;
auto meshnum = scene->mNumMeshes;
std::set<std::string> names;
std::map<std::string, decltype(meshs[0]->mBones[0]->mOffsetMatrix[0])>matrixmaps;
//
//下面这部分代码找到所有骨骼的节点名称
//为避免重复使用了std::set<std::string>中
//同时利用std::map<std::string,Matrix4x4>建立节点名称和偏移矩阵的映射
//
for (decltype(meshnum) i = 0; i < meshnum; i++)
{
auto bonenum = meshs[i]->mNumBones;
for (decltype(bonenum) b = 0; b < bonenum; b++)
{
names.insert(meshs[i]->mBones[b]->mName.C_Str());
matrixmaps[meshs[i]->mBones[b]->mName.C_Str()] = meshs[i]->mBones[b]->mOffsetMatrix[0];
}
}
//
//浏览SDK文档时说离根节点最近的骨骼节点就是整个骨骼的根节点
//考虑到这点,为了寻找骨骼的根节点,所以利用层序遍历树结构
//
std::queue<aiNode*> queue;
queue.push(scene->mRootNode);
//
//由于骨骼的根节点(aiNode*)的父节点不为NULL,所以使用一个标记
//
bool RootBone = true;
while (queue.size())
{
aiNode* n = queue.front();
queue.pop();
//
//是骨骼节点
//
if (names.find(n->mName.C_Str()) != names.end())
{
//
//将骨骼在数组中的下标与名字联系起来
//
maps[n->mName.C_Str()] = Hierarchy.size();
if (RootBone)//骨骼的根节点
{
Hierarchy.push_back(-1);//不存在父节点,所以父节点在数组中的索引为-1
RootBone = false;
}
else
{
Hierarchy.push_back(maps[n->mParent->mName.C_Str()]);
}
}
auto num = n->mNumChildren;
for (decltype(num) i = 0; i < num; i++)
{
queue.push(n->mChildren[i]);
}
}
//
//复制偏移矩阵到vector中去
//
Offsets.resize(names.size());
Matrix4x4 offset;
for (auto & s: names)
{
auto index = maps[s];
auto m = matrixmaps[s];
memcpy(offset.float1d, m, sizeof(float)* 16);
TransposeMatrix(offset);//根据SDK文档,对矩阵进行转置
Offsets[index] = offset;
}
//
//下面处理权重
//由于顶点根据材质的不同而储存在对应的子集(Subset)中
//而在我的框架中,权重及对应的骨骼索引也属于顶点的一部分
//
std::vector<int>prenums(Subsets.size(), 0);
for (decltype(meshnum) m = 0; m < meshnum; m++)
{
auto Bonenum = meshs[m]->mNumBones;
auto materialindex = meshs[m]->mMaterialIndex;//对应于Subsets中的下标
auto vertices = &Subsets[materialindex].Vertices[prenums[materialindex]];
for (decltype(Bonenum) b = 0; b < Bonenum; b++)
{
int BoneIndex = maps[meshs[m]->mBones[b]->mName.C_Str()];//骨骼在表示骨骼关系的数组中的下标
auto weightnum = meshs[m]->mBones[b]->mNumWeights;
for (decltype(weightnum) w = 0; w < weightnum; w++)
{
auto weight = meshs[m]->mBones[b]->mWeights[w];
auto & v = vertices[weight.mVertexId];
v.weights.push_back(std::tuple<float,int>(weight.mWeight,BoneIndex));
}
}
prenums[materialindex] += meshs[m]->mNumVertices;
}

//
//对权重按降序排序
//当输出数据时只输出前4个数据,不足四个的补齐,保证每个顶点的权重表都仅有4个元素
//
for (auto &s :Subsets)
{
for (auto &v:s.Vertices)
std::sort(v.weights.begin(), v.weights.end(), compare);
}
}

//
///该函数读取帧数据
//
void Convertor::LoadAnimation(const aiScene* scene, const std::map<std::string,int>& IndexMap,std::vector<AnimationClip>& Clips)
{
auto num = scene->mNumAnimations;
auto BoneNum = IndexMap.size();
for (decltype(num) a = 0; a < num; a++)
{
AnimationClip clip;
clip.ClipName =scene->mAnimations[a]->mName.C_Str();
//
//对clipname的一些特殊处理,避免.m3d文件读取错误
//
for (auto & c : clip.ClipName)
{
if (c == ' ')
c = '_';
}
clip.BoneAnimations.resize(BoneNum);
auto animation = scene->mAnimations[a];
auto tps = animation->mTicksPerSecond;
if (tps <= 0.0f)tps = 1.0f;
auto channelnum = animation->mNumChannels;
for (decltype(channelnum) c = 0; c < channelnum; c++)
{
BoneAnimation boneanimation;
auto channel = animation->mChannels[c];
auto keyframenum = channel->mNumPositionKeys;
auto name = channel->mNodeName;
//
//fbx的文件格式比较特殊,有三个node需要特殊处理:
//Bip001_$AssimpFbx$_Translation
//Bip001_$AssimpFbx$_Rotation
//Bip001_$AssimpFbx$_Scaling
//需要特殊处理的原因是我发现在转换后的.m3d文件中位于根节点的骨骼一个关键帧都没有
//调试后发现名为Bip001的骨骼根节点没有被处理
//而这种情况在我的框架中是会出错的
//因此我猜想这三个节点的作用相当于一个根节点,也就将这三个节点当作一个节点来处理
//
std::string temp(name.C_Str());
if (temp.find("$AssimpFbx$_Translation") != std::string::npos)
{
auto positionkeynum = channel->mNumPositionKeys;
clip.BoneAnimations[0].Keyframes.resize(positionkeynum);
for (decltype(positionkeynum) pk = 0; pk < positionkeynum; pk++)
{
auto & pkey = channel->mPositionKeys[pk];
auto & frame = clip.BoneAnimations[0].Keyframes[pk];
frame.p[0]= pkey.mValue.x;
frame.p[1] = pkey.mValue.y;
frame.p[2] = pkey.mValue.z;
frame.timepos = pkey.mTime/tps;
}
}
if (temp.find("$AssimpFbx$_Rotation") != std::string::npos)
{
auto rotationkeynum = channel->mNumRotationKeys;
for (decltype(rotationkeynum) rk = 0; rk < rotationkeynum; rk++)
{
auto & rkey = channel->mRotationKeys[rk];
auto & frame = clip.BoneAnimations[0].Keyframes[rk];
frame.r[0] = rkey.mValue.x;
frame.r[1] = rkey.mValue.y;
frame.r[2] = rkey.mValue.z;
frame.r[3] = rkey.mValue.w;
}
}
if (temp.find("$AssimpFbx$_Scaling") != std::string::npos)
{
auto scalingkeynum = channel->mNumScalingKeys;
for (decltype(scalingkeynum) sk = 0; sk < scalingkeynum; sk++)
{
auto & skey = channel->mScalingKeys[sk];
auto & frame = clip.BoneAnimations[0].Keyframes[sk];
frame.s[0] = skey.mValue.x;
frame.s[1] = skey.mValue.y;
frame.s[2] = skey.mValue.z;
}
}
auto pair = IndexMap.find(name.C_Str());//indexmap储存每个骨骼对应在数组中的下标
if (pair == IndexMap.end())continue;
auto boneindex = pair->second;
//
//用于调试,除了上面那种特殊情况
//在这里位置关键帧、四元数关键帧和缩放关键帧的数目总相等
//
assert(channel->mNumPositionKeys == channel->mNumRotationKeys);
assert(channel->mNumScalingKeys == channel->mNumRotationKeys);
for (decltype(keyframenum) k = 0; k < keyframenum; k++)
{
KeyFrame key;
auto p = channel->mPositionKeys[k];
auto r = channel->mRotationKeys[k];
auto s = channel->mScalingKeys[k];
auto timepos = p.mTime;
key.p[0] = p.mValue.x; key.p[1] = p.mValue.y; key.p[2] = p.mValue.z;
key.r[0] = r.mValue.x; key.r[1] = r.mValue.y; key.r[2] = r.mValue.z; key.r[3] = r.mValue.w;
key.s[0] = s.mValue.x; key.s[1] = s.mValue.y; key.s[2] = s.mValue.z;
key.timepos = timepos/tps;
boneanimation.Keyframes.push_back(key);
}
clip.BoneAnimations[boneindex] = boneanimation;
}
Clips.push_back(clip);
}
}

如果你看到这里那就真心感谢你啦!希望你提供一些想法,谢谢!
...全文
2156 2 打赏 收藏 转发到动态 举报
写回复
用AI写文章
2 条回复
切换为时间正序
请发表友善的回复…
发表回复
jiqizaisikao 2017-07-15
  • 打赏
  • 举报
回复
能否请教下楼主,ms3d是不是一个顶点只能绑定一个骨骼,感觉很奇怪,现在的软件都是带有骨骼权重的。研究android程序解析动画,有机会交流加我Q:3617 28654
SlimTracy 2016-09-29
  • 打赏
  • 举报
回复
也是发现最近这个版本才支持FBX的,FBX SDK相当不好用。

456

社区成员

发帖
与我相关
我的任务
社区描述
其它游戏引擎
社区管理员
  • 其它游戏引擎社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧