人的动作是如何实现的呢?这就要用到分级网格(mesh)的知识了。
所谓分级也属于场景管理的基本知识,就是把不同模型分级管理,比如人的手臂是一级,作为父母级,然后前臂是下一级,手掌又是一级,这样分开,以达到模拟人手动作的效果。如下:
相当于一个机械手,以各个节点为分界,分级。
跟一般的变形(transformation)差不多,只不过世界坐标作为root坐标,由root坐标分为一级一级的节点,这些节点的变形都不是根据世界坐标来转换了,而是以他们各自的父母节点来变形了。主要的原理就那么简单,不要想得太复杂了。
主要还是要看程序,着手实现,不然还是不会。
表示节点的数据结构:
struct BoneFrame { // 根节点就是世界坐标 D3DXVECTOR3 pos; // 相对于父母的相对位置 float zAngle; // 相对于父母的相对角度. D3DXMATRIX toParentXForm;//转换到父母空间的矩阵 D3DXMATRIX toWorldXForm;//转换到世界的变形矩阵 };
下面设置其初始化位置和角度
for(int i = 1; i < NUM_BONES; ++i) // Ignore root. { // Describe each bone frame relative to its parent frame. mBones[i].pos = D3DXVECTOR3(2.0f, 0.0f, 0.0f); mBones[i].zAngle = 0.0f; } // Root frame at center of world. mBones[0].pos = D3DXVECTOR3(0.0f, 0.0f, 0.0f); mBones[0].zAngle = 0.0f;
定义一个整数,可以通过directx input控制数值变化,然后控制相应的机器关节
int mBoneSelected;
下面看程序的详细注释:
void RobotArmDemo::buildBoneWorldTransforms()
{
// 计算当前节点到父母节点的矩阵。只是一个非常简单的循环 // the ith bone into the coordinate system of its parent.
D3DXMATRIX R, T;
D3DXVECTOR3 p;
for(int i = 0; i < NUM_BONES; ++i)
{
p = mBones[i].pos;//这个是当前节点的位置
D3DXMatrixRotationZ(&R, mBones[i].zAngle);//这个是DirectX API自动生成当前节点的旋转角度
D3DXMatrixTranslation(&T, p.x, p.y, p.z);//然后也是DirectXAPI自动生成当前节点转移的位置矩阵
mBones[i].toParentXForm = R * T;//然后把这两个矩阵合并起来就一步可以转换并旋转到最终想要得到的位置了,这个是本节重点注意的地方!!!就是这样确定各个节点的位置,及其位置关系的。 }
这里值得多解释几句:每个模型都有自己的坐标,自己的模型坐标是以自己为中心的,所以任何模型在自己坐标的位置都是(0,0,0),那么自己在父母的位置是多少就是个相对位置了,比如距离自己父母模型为x轴为2个单位的位置,那么就用转换矩阵把自己转换到离父母模型坐标为2的位置,然后再用同样的办法把父母转换到父母的父母空间坐标中,最后到了root空间,就是世界坐标了。
// 计算每个节点的世界转换矩阵,就是把各个父母以上的矩阵接起来就可以啦。 for(int i = 0; i < NUM_BONES; ++i) { // 世界矩阵是单位对角阵,对角线都是1,其他是零的矩阵。 D3DXMatrixIdentity(&mBones[i].toWorldXForm); // 连接各个父母以上的矩阵,多学算法就觉得这是个很简单的算法,看来算法还是非常有用的。 for(int j = i; j >= 0; --j) { mBones[i].toWorldXForm *= mBones[j].toParentXForm; } } }
控制我们的机械手代码:
if( gDInput->keyDown(DIK_1) )mBoneSelected = 0; //按下按键1,2,3,4分别选择四个不同级别的节点(或叫关节)。 if( gDInput->keyDown(DIK_2) )mBoneSelected = 1; if( gDInput->keyDown(DIK_3) )mBoneSelected = 2; if( gDInput->keyDown(DIK_4) )mBoneSelected = 3; if( gDInput->keyDown(DIK_5) )mBoneSelected = 4;
// 按下A或者D坐旋或右旋机械手。 if( gDInput->keyDown(DIK_A) ) mBones[mBoneSelected].zAngle += 1.0f * dt; if( gDInput->keyDown(DIK_D) ) mBones[mBoneSelected].zAngle -= 1.0f * dt; // 转的太猛了,过了360度就置零吧,角度0和360在现有的空间中是不变的。 if( fabsf(mBones[mBoneSelected].zAngle) >= 2.0f*D3DX_PI) mBones[mBoneSelected].zAngle = 0.0f;