如何做人体骨架模型?


如何做人体骨架模型?

文章插图
本文提供一种将骨架动作矢量映射到人体骨架模型的一种方法 , 通过输入各个骨骼的当前方向 , 反馈给骨架模型 , 这样就实现了动画的效果 。实验开发工具是VC6.0在OpenGL平台上开发完成 。阅读对象:假定读者已经熟悉OpenGL编程 , 就算不熟悉 , 只要了解基本的旋转 , 平移 , 堆栈操作就好 。假定读者已经了解基本的c++编程,其中需要了解递归的算法 , 递归的方法请参考一下数据结构吧 。制作过程:第一步 , 3D模型准备这一步骤的目的是提供分解的骨骼模型 , 它需要导出多个组成身体结构的文件 , 模型可以不用自己制作 , 只要到网上找找吧 , 应该很多 , 最好是是人体模型 , 如果用动物的模型也可以 , 不过需要自己定义映射骨架了 , 比如图中的骷髅模型是我从人体动画软件poser 5.0找到的 。然后使用3d max 将身体的各个部位导出为3ds文件 , 这个步骤很简单 , 也不需要有什么3d max的基础 。这里有一个小的技巧就是可以选中多个部分作为一个3ds模型导出 , 比如我需要将左右肩胛骨与脊椎骨肋骨作为同一个部分导出 , 这样可以将它命名为身体躯干(body) 。这样我们就准备了各个3ds文件了 , 分别是:身体躯干 BODY.3DS头部 HEAD.3DS左臂 LSHOULDER.3DS右臂 RSHOULDER.3DS左小臂 LELBOW.3DS右小臂 RELBOW.3DS左大腿 LTHIGH.3DS右大腿 RTHIGH.3DS左小腿 LFEET.3DS右小腿 RFEET.3DS这样这些组成部分就可以灵活的拼接出一个人体来了 。第二步 , 定义相关的核心数据结构为了得到运动的各个身体部分数据信息 , 我们需要存储一些运动信息 , 主要有:骨骼ID骨骼关节的当前位置;r_x,r_y,r_z骨骼之间的关系 , 例如手臂是躯干的延伸 , 而左小臂是左臂的延伸;PID,CID我们可以通过下图来了解骨骼之间的结构关系存放3ds文件位置;file_name_3ds3ds模型的初始化方向;这个是比较抽象一点的概念 , 它是指从父节点指向子节点的方向 , 例如左小臂的初始位置是平放向下 , 那么对应的矢量就是 (-0.2,-1,0)以下是数据结构部分:class bone{public:int y;int x;int r_z; //现实世界z坐标int r_y;int r_x;int rotated_X; //旋转后的坐标int rotated_Y;int is_marked; //是否已经标记int PID; //父节点int CID; //子节点 , 目前针对轴关节和膝盖有效float start_arc_x,end_arc_x; //相对父节点的x 左右方向转动角度限制float start_arc_y,end_arc_y; //相对父节点的y 上下方向转动角度限制float start_arc_z,end_arc_z; //相对父节点的z 前后方向转动角度限制double LengthRatio;char name[80]; //名称char file_name_3ds[180]; //3ds文件名称int ID;bone(int ID,char *name,int PID);virtual ~bone();float bone_init_x,bone_init_y,bone_init_z; //初始化骨骼的矢量方向,3d max 模型};第三步 , 初始化骨架结构在定义了bone的结构以后 , 我们定义一个skeleton类来在第一次初始化时加载这些结构 , obone = bone (2,"head",1); //定义一个bonestrcpy(obone.file_name_3ds,"head.3DS"); //设置它的3ds文件名obone.bone_init_x = 0; //初始化骨骼的矢量方向obone.bone_init_y = 1;obone.bone_init_z = 0;bonevec.push_back (obone); //放入vector结构,这里用到了STL编程技术中的vector以下是实现的部分代码:skelecton::skelecton(){float fy = 0.56f ;float ftx = 0.19f;float ffx = 0.08f;bone obone = bone (1,"neck",0);bonevec.push_back (obone);obone = bone (2,"head",1);strcpy(obone.file_name_3ds,"head.3DS");obone.bone_init_x = 0;obone.bone_init_y = 1;obone.bone_init_z = 0;bonevec.push_back (obone);obone = bone (3,"rShoulder",1);bonevec.push_back (obone);obone = bone (4,"lShoulder",1);bonevec.push_back (obone);obone = bone (5,"rElbow",3);strcpy(obone.file_name_3ds,"rShoulder.3DS");obone.bone_init_x = fy;obone.bone_init_y = -1;obone.bone_init_z = 0;obone.CID = 7;bonevec.push_back (obone);obone = bone (6,"lElbow",4);strcpy(obone.file_name_3ds,"lShoulder.3DS");obone.bone_init_x = -fy;obone.bone_init_y = -1;obone.bone_init_z = 0;obone.CID = 8;bonevec.push_back (obone);//.............太长只给出部分的代码..........................}第四步 , 学习3ds公共的类CLoad3DS , 可以用来载入显示模型这个类是公用一个类 , 详细的类CLoad3DS的接口信息可以到一个open source项目里参考 。http://scourge.sourceforge.nethttp://scourge.sourceforge.net/api/3ds_8h-source.html实际上在使用这个类时候 , 我做了一些修改 , 加了得到最大顶点的方法 。这个在第五步会说明 。我们定义一个OpenGL的类来做模型控制类 , 负责载入模型 , CLoad3DS* m_3ds;int OpenGL::Load3DS(int ID, char *filename){if(m_3ds!=NULL) m_3ds->Init(filename,ID);return 0;}然后在显示时候调用int OpenGL::show3ds(int ID){m_3ds->show3ds(ID,0,0,0,2);return 0;}第五步 , 使用递归方法分层次载入模型这里是重点的内容了 , 让我们思考一些问题 , 实现骨骼会随着输入的方向而改变方向 , 需要做那些事情呢?首先针对一块骨骼来考虑:第一 , 我们需要让骨骼绕着它的节点旋转到输入的方向上第二 , 我们需要知道骨骼目前节点的位置 , 才能旋转 。可是我们知道骨骼会跟着它的父骨骼转动的 , 例如左小臂会跟着左臂转动 , 当身体转动时左臂也会跟着身体转动的 , 这里看起来像是有一个父子连动的关系 , 所以当前节点的位置会与它的父骨骼有关 , 父骨骼转动的角度 , 子骨骼也必须转动 , 所以这里自然想到了递归模型了 , 至于如何存储这些转动过程呢 , 还好openGL提供了glPushMatrix();glPopMatrix();那么所有的子骨骼必须包含在父骨骼的glPushMatrix();glPopMatrix();好了 , 这个变成//递归实现3d现实int skelecton::Render_skeleton_3D(int ID){glPushMatrix(); //开始记录堆栈joint_point = pgl->get_joint_point(ID); //找到节点位置glTranslatef(joint_point.x,joint_point.y,joint_point.z); //坐标移到节点位置pgl->rotate_bone (vt1,vt2,vto); //旋转骨骼到指定的方向glTranslatef(-joint_point.x,-joint_point.y,-joint_point.z);//坐标移回来pgl->show3ds(ID); //显示模型//遍历子节点for (theIterator = bonevec.begin(); theIterator != bonevec.end(); theIterator++){pbone = theIterator;if((pbone->PID == ID) ){Render_skeleton_3D(pbone->ID); //递归调用}}glPopMatrix(); //退出记录堆栈}剩下需要解决的问题就是如何找到节点位置 。寻找节点位置 , 我们看到上面代码 get_joint_point(ID)就是找到节点了 , 其实如果不追求高的准确度 , 我们可以假设每个模型的最高的点即为骨骼的节点 , 当然这个假设前提是人体模型是正面站立的 , 手臂自然垂下 , 这样可以近似认为每个模型的最高的点即为骨骼的节点,这样函数就很简单了 , 这个方法是修改了Cload3ds类的方法 , 如下:Vector3f CLoad3DS::get_joint_point(int j0){CVector3 LastPoint;Vector3f vect;LastPoint.y = -1000 ;if(j0==2) LastPoint.y = 1000 ;//头部节点朝下// 遍历模型中所有的对象for(int l = 0; l

推荐阅读