教程 如何製作乙個可控制的人體骨骼模型

2021-04-01 10:53:56 字數 4100 閱讀 1679

經過一段時間的研究實現了如何將骨架放到目前的跟蹤系統上來,將骨架模型移植到系統上,替代原來的棍狀模型,也在網上看到有些人在嘗試做這個東西(好像都是d3d的骨骼動畫,基於opengl的沒有),我把自己的製作方法簡單介紹一下,希望能對正在做這方面的人有所幫助。

如何製作乙個可控制的人體骨骼模型

描述:本文提供一種將骨架動作向量對映到人體骨架模型的一種方法,通過輸入各個骨骼的當前方向,反饋給骨架模型,這樣就實現了動畫的效果。

實驗開發工具是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_3ds

3ds模型的初始化方向;這個是比較抽象一點的概念,它是指從父節點指向子節點的方向,例如左小臂的初始位置是平放向下,那麼對應的向量就是 (-0.2,-1,0)

以下是資料結構部分:

class bone

;第三步,初始化骨架結構

在定義了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()

第四步,學習3ds公共的類cload3ds,可以用來載入顯示模型

這個類是公用乙個類,詳細的類cload3ds的介面資訊可以到乙個open source專案裡參考。http://scourge.sourcefe.***

實際上在使用這個類時候,我做了一些修改,加了得到最大頂點的方法。這個在第五步會說明。

我們定義乙個opengl的類來做模型控制類,負責載入模型,

cload3ds* m_3ds;

int opengl::load3ds(int id, char *filename)

然後在顯示時候呼叫

int opengl::show3ds(int id)

第五步,使用遞迴方法分層次載入模型

這裡是重點的內容了,讓我們思考一些問題,實現骨骼會隨著輸入的方向而改變方向,需要做那些事情呢?

首先針對一塊骨骼來考慮:

第一,我們需要讓骨骼繞著它的節點旋轉到輸入的方向上

第二,我們需要知道骨骼目前節點的位置,才能旋轉。可是我們知道骨骼會跟著它的父骨骼轉動的,例如左小臂會跟著左臂轉動,當身體轉動時左臂也會跟著身體轉動的,這裡看起來像是有乙個父子連動的關係,所以當前節點的位置會與它的父骨骼有關,父骨骼轉動的角度,子骨骼也必須轉動,所以這裡自然想到了遞迴模型了,至於如何儲存這些轉動過程呢,還好opengl提供了glpushmatrix();glpopmatrix();那麼所有的子骨骼必須包含在父骨骼的glpushmatrix();glpopmatrix();好了,這個變成

//遞迴實現3d現實

int skelecton::render_skeleton_3d(int id)

} glpopmatrix(); //退出記錄堆疊

剩下需要解決的問題就是如何找到節點位置。

尋找節點位置,

我們看到上面** get_joint_point(id)就是找到節點了,其實如果不追求高的準確度,我們可以假設每個模型的最高的點即為骨骼的節點,當然這個假設前提是人體模型是正面站立的,手臂自然垂下,這樣可以近似認為每個模型的最高的點即為骨骼的節點,這樣函式就很簡單了,這個方法是修改了cload3ds類的方法,如下:

vector3f cload3ds::get_joint_point(int j0)

else}}

}vect.x = lastpoint.x ;

vect.y = lastpoint.y ;

vect.z = lastpoint.z ;

return vect;

比較特殊的是頭部節點是通過脖子連線的,所以它是取最低的點。

現在解決最後的問題了,如何旋轉了,具體來講就是骨骼從原來自然的狀態旋轉到目前的方向,例如手臂從自然垂下變成抬起,垂下和抬起兩個狀態的向量是不同的方向的,如何旋轉呢? 這裡就要用到了空間幾何裡的點積和叉積的概念了,簡單來講就是利用點積來求向量夾角余弦,利用叉積來求兩個向量的法向量,如果你忘記了這些概念,可以回去參考一下高等數學書,這個連線也提供了一些資料,可以幫助理解http://.gameres.***/articles/program/visual/other/shiliang.htm

然後呢,我們知道了兩個向量的夾角與它們的法向量,下面的事情就變得簡單了,我們讓骨骼原來的向量以法向量為旋轉軸,旋轉一定角度,這個角度就是兩個向量的夾角,這樣問題就解決了,所以這裡的**如下:

int opengl::rotate_bone(vector3f vvector1, vector3f vvector2, vector3f vvectorin)

{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(vvectorin,vt4);

double rarc12 = 180*arc12/pi;

float len= distance(vt1,vt2);

vector3f vt3 = cross(vvectorin,vt4);

glrotatef ((float)rarc12,vt3.x,vt3.y,vt3.z);

return 0;

好了所有問題解決了,我們可以松一口氣了。這裡我提供乙個參考的**,因為這個模型的執行需要輸入向量方向,我目前還不能把它從我的其他系統程式完整提取出來,只是提供所有的**,供讀者參考。

Arduino製作乙個人體感應燈

現在的很多家庭中都裝有感應燈,人體感應燈比聲控感應燈的優點是不會影響別人休息,安靜。接下來就是簡單的製作過程。1.需要的裝置 1.arduino uno主控板乙個 2.人體紅外感測器乙個 3.電阻乙個 4.麵包板乙個 5.杜邦線若干條 6.led燈乙個 7.資料線一條 2.電路圖 3.程式 cons...

如何製作乙個vagrant box

因為要用的視窗應用,基於伺服器的各種box不能使用 曾經嘗試安裝桌面,沒有成功 所以試著基於ubuntu的虛擬機器建立自己的box。主要過程簡述如下 1 安裝virtualbox 2 安裝 vagrant root密碼 vagrant 使用者名稱 vagrant 使用者密碼 vagrant 4 完成...

JS實現乙個可控制的進度條

寫在前面 原始碼已經放到github上 進度條原始碼 一如既往的看效果 好吧,效果還是一如既往的醜,簡單的說一下怎麼實現這樣的效果,還是和之前一樣我們分析一下難點在哪?第一 進度條是生成的,那麼就意味著div的寬度是不定的。第二 百分比是動態的,就意味著是計算出來的。第三 每次的改變,百分比都要自己...