最近剛剛將《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);
}
}
如果你看到這里那就真心感謝你啦!希望你提供一些想法,謝謝!
uj5u.com熱心網友回復:
也是發現最近這個版本才支持FBX的,FBX SDK相當不好用。uj5u.com熱心網友回復:
能否請教下樓主,ms3d是不是一個頂點只能系結一個骨骼,感覺很奇怪,現在的軟體都是帶有骨骼權重的。研究android程式決議影片,有機會交流加我Q:3617 28654轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/60016.html
標籤:其它游戲引擎
下一篇:dx opengl
