请教骨骼动画及使用IGame导出骨骼、动作的问题

dolaham1982 2008-08-27 11:50:15
先说一下我对骨骼动画貌似很简单的原理的理解。下面的阐述也能看得出我对这些概念的把握其实很混乱,都是模模糊糊的。
骨骼像一棵树形结构那样,有父骨骼、子骨骼。每个骨骼保存的都是相对于父骨骼的转换矩阵,因此他们的世界矩阵都需要把自己的矩阵乘上父骨骼的世界矩阵才能得到(这是在d3d的情况。opengl的程序往往是反过来的,父骨骼的世界矩阵乘上本骨骼的矩阵,好像跟opengl使用矩阵的方式有关(压入堆栈))。
Mesh的每一个顶点都可以受多根骨骼的影响,使用权重值来决定影响的程度。

在程序里面,可以先对一个顶点使用骨骼转换到正确的动作姿态(这时仍然只是在模型空间),再用该游戏物体的世界矩阵转换到游戏世界的正确位置上

现在先不考虑游戏物体的世界矩阵,只考虑模型空间。

这就有了一个问题:对顶点应当使用什么矩阵才能把它转换到正确的动作姿态上?想象一个简单的人物模型,裸模,身体各部位都是连贯的一个mesh。那么这些顶点都是在模型空间里定义的,而每个骨骼的那些缩放、旋转和位置信息都是在父骨骼的空间里定义的,这就不能拿骨骼的世界矩阵直接转换顶点。我认为,只有顶点是在骨骼的本地空间里定义的才可以使用骨骼的世界矩阵转换它。确实,有一个bind pose的概念,它其实就是矩阵,是骨骼初始的世界矩阵。矩阵是把坐标从空间A转换到空间B,那么这个矩阵的逆矩阵就能把坐标从空间B转换回空间A。所以bind pose矩阵的逆矩阵就应当能把模型空间的顶点转换到骨骼的本地空间去,然后在某一时刻,使用骨骼的世界矩阵转换这些在骨骼空间中的顶点,应该就能把它们转换到正确的动作姿态了。

在3DSMAX导出插件,使用IGame。如何得到各骨骼的bind pose呢?由于在游戏里,骨骼的空间信息仍然是相对于父骨骼定义的,所以这里求Bind pose,也是一样。而且一般bind pose取第0帧时即可。
使用IGameNode::GetWorldTM()函数(参数应该传0,也就相当于第0帧了),得到一个GMatrix结构,在调用GMatrix的ExtractMatrix3()函数就能得到一个Matrix3结构(它是以列为主的,跟d3d不一样)tm,这就是该IGameNode在第0帧时的世界矩阵了。本来tm应该就是该骨骼的bind pose矩阵的,但因为要保存相对于父骨骼的值,所以还要先使用同样方法得到父骨骼在第0帧时的世界矩阵ptm,让tm乘上ptm的逆矩阵。

接下来,实际保存的时候,由于我的Bone结构实际存储的是缩放、旋转、位置,所以要把这个tm矩阵拆开来。那么在游戏里要得到一个Bone的Bind pose矩阵,就是从这3个值构造一个矩阵,再乘上父骨骼的bind pose矩阵。这里,我发现我和Ogre引擎的一个非常显著的分歧。

我的做法,如上所述
Matrix IBone::GetBindPoseMatrix()
{
Matrix mtxScale = MtxScale(m_BindPoseScale); // m_BindPoseScale是一个三维向量,代表了bind pose的xyz方向上的缩放
Matrix mtxRot = MtxRotFromQuaternion(m_BindPoseRot); // m_BindPoseRot是一个Quaternion,MtxRotFromQuaternion函数可以根据一个Quaternion创建一个旋转矩阵
Matrix mtxTrans = MtxTranslation(m_BindPoseTrans);
m_BindPoseMatrix = mtxScale * mtxRot * mtxTrans; // 保存一下计算出的bind pose矩阵,如果愿意,使用一点技巧,可以使此函数不必每次都计算,这里省掉了
if(m_pParent)
{
m_BindPoseMatrix *= m_pParent->GetBindPoseMatrix();
}

return m_BindPoseMatrix;
}

可以看到,我先把本骨骼相对于父骨骼的bindpose缩放、旋转、平移合并为一个矩阵,再乘上父骨骼的bind pose矩阵,得到本骨骼的在世界空间的bind pose矩阵。
可是ogre,是先把本骨骼的bindpose缩放乘上父骨骼的bindpose缩放,本骨骼的bindpose旋转乘上父骨骼的bindpose旋转,本骨骼的bindpose位置加上父骨骼的bindpose位置,得出三个新的缩放、旋转、位置,再合并为一个矩阵。
这两种方法得出来的结果是不一样的。

这就是我的困惑。ogre的运行效果是没错的,我的运行效果是错误的,不过我之前用过ogre的方式,也不对。我想不对的原因不仅仅在bind pose矩阵的重新合并上,还在于动作的导出上。因为ogre的导出插件用的INode借口,没用IGame的IGameNode。
INode有GetNodeTM函数,文档上说能得到合并了父结点矩阵的矩阵,而IGameNode有三个函数:GetWorldTM、GetLocalTM、GetObjectTM,文档也没有说明哪个函数对应INode的GetNodeTM,我想当然的认为是GetWorldTM。

下面是我导出动作的做法。很羞愧,我其实不知道control、modifier到底是干什么的。

但是要导出骨骼的每一帧动作,似乎必须通过control。
首先IGameNode->GetIGameControl()得到一个IGameControl指针,然后调用IGameControl的GetFullSampledKeys函数就可以得到各帧的转换。这个函数也很让我困惑,我认为把参数Type设为IGAME_TM,就可以得到矩阵形式的转换,参数Relative,我不知道是相对于谁,相对于父骨骼?相对于前一帧?相对于第0帧?相对于bind pose?由于这个函数是计算各帧的转换,所以我倾向于认为是相对于前一帧的。那么我要的是绝对值,所以这个就设为false


IGameKeyTab selfKeys; // 在每一帧,本骨骼的世界转换
IGameKeyTab parentKeys; // 在每一帧,父骨骼的世界转换
IGameNode* pParent = pGameNode->GetNodeParent(); // pGameNode就是当前正在处理的骨骼, pParent是父骨骼

IGameControl* pControl = pGameNode->GetIGameControl();
pControl->GetFullSampledKeys(selfKeys, frameRate, TGAME_TM, false);

if(pParent)
{
pControl = pParent->GetIGameControl();
pControl->GetFullSampledKeys(parentKeys, frameRate, TGAME_TM, false);
}

for(uint k = startKeyFrame; k <= endKeyFrame; ++k)
{
Matrix3 tm = selfKeys[k].sampleKey.gval.ExtractMatrix3(); // 我觉得这就是本骨骼在这一帧的世界矩阵了,但别忘了最终要存储的是骨骼相对于父骨骼的转换信息(嗯,不一定是矩阵的形式,为了节约空间)
if(pParent)
{
Matrix3 ptm = parentKeys[k].sampleKey.gval.ExtractMatrix3(); // 同上,我认为这就是父骨骼在这一帧的世界矩阵了
tm = tm * Inverse(ptm); // 我认为把本骨骼在这一帧的世界矩阵乘上父骨骼在这一帧的世界矩阵的逆矩阵,就可以得到本骨骼在这一帧相对于父骨骼的矩阵了,这就是我要保存的
}

// 又一个困惑来了,要把矩阵拆成缩放、旋转、平移,要用到这个函数,可是这个函数返回的结构体中多出一个u来,虽然据说大多数时候无用
AffineParts ap;
affine_decom(tm, &ap); // 函数名称我可能记错了,因为这篇文章不是在编译器里写的
Point3 scale = ap.k; // 我认为这就是从矩阵里拆出来的缩放了,ap.u是什么?无视!
Quat rot = ap.q; // 这就是旋转
Point3 trans = ap.t; // 平移

// 下面保存本骨骼在这一帧的scale、rot、trans信息

}

下面进入游戏里

根据之前所述,使用bind pose的逆矩阵先把顶点转换到骨骼的本地空间,再使用骨骼的当前世界矩阵转换,就是能吧顶点转换到正确的动作姿态上了。
那么我的做法是:

IBone::GetMatrixForVertex()
{
Matrix mtxBindPose = GetBindPoseMatrix(); // 参见之前对此函数的叙述和代码
Matrix invMtx = mtxBindPose.Inverse();
Matrix ret = invMtx * GetWorldMatrix(); // 还有这个函数没说
return ret;
}

IBone::GetWorldMatrix() // 得到本骨骼当前的世界矩阵,其实实现原理跟GetBindPoseMatrix一样,也因此跟Ogre的方式截然不同
{
Matrix mtxScale = MtxScale(m_Scale);
Matrix mtxRot = MtxRotFromQuaternion(m_Rot);
Matrix mtxTrans = MtxTranslation(m_Pos);

m_WorldMatrix = mtxScale * mtxRot * mtxTrans;

if(m_pParent)
{
m_WorldMatrix *= m_pParent->GetWorldMatrix();
}

return m_WorldMatrix;
}


至此,我对骨骼动画的导出及渲染就是这样做的了,遗憾的是,运行结果是错误的。这两个多星期以来,我尝试修改了很多方式,都没有成功。也有参考Ogre的源代码,可是它坐标系不一样,它使用max sdk api而不是IGame,所以结果也不对。有谁能帮我看看是怎么回事?有谁曾经实现过,能指点一下吗?谢谢了。
...全文
1358 8 打赏 收藏 转发到动态 举报
写回复
用AI写文章
8 条回复
切换为时间正序
请发表友善的回复…
发表回复
xrtc111 2012-03-13
  • 打赏
  • 举报
回复
我用maxscript导入OGRE XML格式的骨骼动画,结果连骨骼初始位置都不对。请教一下计算方法
<skeleton>
<bones>
<bone id="0" name="Bip01">
<position x="-8.28224e-008" y="0.787" z="0.248506" />
<rotation angle="3.69054">
<axis x="0.678495" y="-0.678496" z="-0.281581" />
</rotation>
</bone>
<bone id="1" name="Bip01 Head">
<position x="0.172468" y="-0.00940375" z="4.35771e-006" />
<rotation angle="0.696372">
<axis x="-0.00622007" y="-0.00283151" z="0.999977" />
</rotation>
</bone>
以上是骨骼初始位置数据
position rotation angle axis 怎么计算进骨骼啊!

救助 给个号QQ 我的QQ号15347857
qq79402005 2010-12-11
  • 打赏
  • 举报
回复
楼主加好友 QQ:79402005
yingkk 2009-12-22
  • 打赏
  • 举报
回复
好复杂呀没看懂,我用OGRE的时候,根本就没去管过骨骼动画内部具体是怎么实现的。我用的是maya 2009设计的人物,动画是直接利用网上现成运动普抓数据,retatgeting一下就成,总共在Maya里生成大概有20多种人物动画,然后一个个导出来生成多个skeleton文件,自己编译个软件合并这些skeleton文件,因为一个动画文件的话便于管理和使用,但合并前提是导出的时候所有动画必须相同的标准pose才行。OGRE中的动画是在标准pose基础上进行一些骨骼旋转,所以如果两个动画在标准pose上有差异,合并的时候就会出问题。最后一个mesh文件,一个skeleton文件就包含了一个人物所有动画了。在场景中,人物在多个动画间的切换使用了每个动画的整体权重值来实现平滑过渡。如果有用就参考下吧。
littlight 2009-12-17
  • 打赏
  • 举报
回复
-qx,-qy,-qz,qw是导出到directx坐标系的值,至于opengl则需要另行推导。
littlight 2009-12-17
  • 打赏
  • 举报
回复
另外说明一点,我导出时用的是directx的坐标系。
littlight 2009-12-17
  • 打赏
  • 举报
回复
最近也在研究IGameExporter的导出,其中发现了一个问题,IGameExporter那个例子中有一点是错误的,在计算NodeTM时,输出的rotation四元数计算错误,假设输出的四元数是qx,qy,qz,qw,经过我个人推算以及验证,发现正确值应该是-qx,-qy,-qz,qw.这个问题困扰了我两个周,最后从eular角的方式出发发现了这个问题,修改后角色骨骼动画才得以正确,楼主可以试一下我的方法。
nomadli 2009-05-14
  • 打赏
  • 举报
回复
加个qqmsn吧,也在研究这个东东, 465852692 nomad.li@hotmail.com

control可以传入很多的参数,矩阵,旋转,平移,缩放,应该根据实际动画的实现来传递。
modifier是蒙皮动画中mesh各点受那个骨骼影响及其权重。
dolaham1982 2008-08-27
  • 打赏
  • 举报
回复
这是我发的第一贴,没有分给,请见谅,请赐教

8,306

社区成员

发帖
与我相关
我的任务
社区描述
游戏开发相关内容讨论专区
社区管理员
  • 游戏开发
  • 呆呆敲代码的小Y
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

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