如何做人體骨架模型?


如何做人體骨架模型?


本文提供一種將骨架動作矢量映射到人體骨架模型的一種方法 , 通過輸入各個骨骼的當前方向 , 反饋給骨架模型 , 這樣就實現了動畫的效果 。實驗開發工具是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

猜你喜歡