如何做人體骨架模型?
閱讀對象:
假定讀者已經熟悉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_3ds
3ds模型的初始化方向;這個是比較抽象壹點的概念,它是指從父節點指向子節點的方向,例如左小臂的初始位置是平放向下,那麽對應的矢量就是 (-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); //定義壹個bone
strcpy(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項目裏參考。/Articles/Program/Visual/Other/shiliang.htm
然後呢,我們知道了兩個矢量的夾角與它們的法向量,下面的事情就變得簡單了,我們讓骨骼原來的矢量以法向量為旋轉軸,旋轉壹定角度,這個角度就是兩個矢量的夾角,這樣問題就解決了,所以這裏的代碼如下:
int OpenGL::rotate_bone(Vector3f vVector1, Vector3f vVector2, Vector3f vVectorOrgin)
{
Vector3f vt1 = Vector3f(vVector1.x,vVector1.y,vVector1.z);
Vector3f vt2 = Vector3f(vVector2.x,vVector2.y,vVector2.z);
Vector3f vt4 = vt2-vt1;
double arc12 = AngleBetweenVectors(vVectorOrgin,vt4);
double rarc12 = 180*arc12/pi;
float len= Distance(vt1,vt2);
Vector3f vt3 = Cross(vVectorOrgin,vt4);
glRotatef ((float)rarc12,vt3.x,vt3.y,vt3.z);
return 0;
}