視頻教程請關注 http://edu.csdn.net/lecturer/lecturer_detail?lecturer_id=440
本例程展示如何建立骨骼動畫,有些人叫蒙皮動畫
定義如下:
當前有兩種模型動畫的方式:頂點動畫和骨骼動畫。頂點動畫中,每幀動畫其實
就是模型特定姿態的一個“快照”。通過在幀之間插值的方法,引擎可以得到平滑
的動畫效果。在骨骼動畫中,模型具有互相連接的“骨骼”組成的骨架結構,通過
改變骨骼的朝向和位置來為模型生成動畫。
骨骼動畫比頂點動畫要求更高的處理器性能,但同時它也具有更多的優點,
骨骼動畫可以更容易、更快捷地創建。不同的骨骼動畫可以被結合到一起——
比如,模型可以轉動頭部、射擊並且同時也在走路。一些引擎可以實時操縱單
個骨骼,這樣就可以和環境更加准確地進行交互——模型可以俯身並向某個方
向觀察或射擊,或者從地上的某個地方撿起一個東西。多數引擎支持頂點動畫,
但不是所有的引擎都支持骨骼動畫。
1. 關鍵幀動畫,早期的cs就是用關鍵幀動畫
優點:
計算量小,速度快,在早期計算機性能滿足不了要求的時候采用的,
最具代表性的就是Quake(雷神之錘),采用的md2文件格式。
缺點:
畫面表現不過好,會有穿刺的情況出現
2.骨骼動畫(蒙皮動畫)
優點:
畫面表現細膩,真實感很強,目前大多數游戲都采用該中類型的動畫,典型的代表,
Quake推出的md3文件格式,就是采用骨骼動畫
缺點:
所有的定點都是根據骨骼的變化實時計算,計算量非常大。
骨骼動畫的原理:
正如其名,這種動畫中包含骨骼(Bone)和蒙皮(Skinned Mesh)兩個部分
一部分是Bone(骨頭),一部分是Skin(皮).就像人體的組成一樣。人要想做動作,
骨頭要動起來,然后皮就被骨頭帶動起來,按照這樣的理論,就產生了蒙皮動畫。
在三維模型當中。bone就是骨頭,皮就是skin mesh,mesh的其實就是模型來,
加上skin,說明這個模型的意義,做表皮的。
我們在看待問題,學習東西的時候,要站在設計者的角度去考慮問題,很多
問題就不是問題了,很多問題就更加容易的理解,順利成章。
現在我們就站在設計者的角度上來看待骨骼動畫,首相設計意圖我們已經知
道,就是骨頭帶動肉動起來,那怎么個帶動法呢 ?
來看下一,當我們的彎曲手臂的時候,就是肘關節動,其他的關節不動,而隨着
肘關節的彎曲,我們肱二頭肌會動,但幅度最大的是手臂,那我們想一下,是不
是這樣來描述,當我們動一個關節的時候,會帶動一部分肌肉動起來,而不是只
要動一個關節全身都在動。那么我們就可以這樣來說,一個骨頭動,會影響到一
部分的肉和皮動。逆向思路來思考下,肱二頭肌要受到幾個骨頭的影響,會使得
肱二頭肌的形狀發生變化,影響最大的肘關節,其次是肩關節。肱二頭肌是什么?
在程序中,他就是一些列的點數據。
我們定義個如下結構體(偽代碼)
class Point
{
float x,y,z; //! 肌肉的位置
int arBone[n]; //! 影響肌肉的骨頭
float arWeight[n] //! 每一個骨頭對肌肉的影響度,例如 肘關節的影響度對肱二頭肌很多,而肩關節要少一點。
};
如何來描述肌肉的位置呢?
for( int i = 0 ;i < n ; ++ i)
{
(x,y,z) += 骨頭[i] * 骨頭的影響度[i];
}
那有如何來描述骨頭呢 ?在游戲中,骨頭有位置,可以旋轉,顯示生活中骨頭不能縮放,但游戲中可以。
所以描述一個骨頭需要三個要素,位置,旋轉,和縮放,最容易想到的就是使用一個矩陣來描述他了。
class Bone :public Matrix
{
};
從上面的描述,我們知道要想繪制出來一模型,我們要存儲的信息,所有的定點,所有的骨頭,還有
那么每一個點被那么骨頭影響,影響度是都少。具體計算如下。
一個人的模型有2000個頂點組成,有20快骨頭組成。我們要做的計算如下:
for( int i = 0 ;i < 2000 ; ++ i )
{
for( int x = 0 ; x < 4(假設一個定點被四個骨頭影響) ; ++ x )
{
(x1,y1,z1) += (x,y,z) * bone * weight;
}
}
我們可以看出這個計算量是非常大的,幾乎都在做矩陣的計算。
圖中有兩個骨頭,一個是藍色的,一個是黃色的,有三個長方形,一個是藍色的,一個是綠色的,一個是換色的,
藍色的長方形表示被藍色的骨頭影響,黃色的長方形表示被換色的骨頭影響,綠色的表示受兩個骨頭的影響。
右鍵按住進行旋轉,操作骨頭。
可執行文件及源代碼 :下載
#include "CELLWinApp.hpp"
#include <assert.h>
#include <math.h>
#include "matrix4x4f.h"
#pragma comment(lib,"opengl32.lib")
float g_fSpinX_R = 0.0f;
float g_fSpinY_R = 0.0f;
struct Vertex
{
//! 顏色
float r, g, b, a;
//! 位置
float x, y, z;
//! 影響度
float weights[2];
//! 矩陣的索引
short matrixIndices[2];
//! 影響整個定點的骨頭個數
short numBones;
};
Vertex g_quadVertices[12] =
{
{ 1.0f,1.0f,0.0f,1.0f, -1.0f,0.0f,0.0f, 1.0f,0.0f, 0,0, 1 }, // 藍色
{ 1.0f,1.0f,0.0f,1.0f, 1.0f,0.0f,0.0f, 1.0f,0.0f, 0,0, 1 },
{ 1.0f,1.0f,0.0f,1.0f, 1.0f,2.0f,0.0f, 0.5f,0.5f, 0,1, 2 },
{ 1.0f,1.0f,0.0f,1.0f, -1.0f,2.0f,0.0f, 0.5f,0.5f, 0,1, 2 },
{ 0.0f,1.0f,0.0f,1.0f, -1.0f,2.0f,0.0f, 0.5f,0.5f, 0,1, 2 }, // 綠色
{ 0.0f,1.0f,0.0f,1.0f, 1.0f,2.0f,0.0f, 0.5f,0.5f, 0,1, 2},
{ 0.0f,1.0f,0.0f,1.0f, 1.0f,4.0f,0.0f, 0.5f,0.5f, 0,1, 2 },
{ 0.0f,1.0f,0.0f,1.0f, -1.0f,4.0f,0.0f, 0.5f,0.5f, 0,1, 2 },
{ 0.0f,0.0f,1.0f,1.0f, -1.0f,4.0f,0.0f, 0.5f,0.5f, 0,1, 2 }, // 黃色
{ 0.0f,0.0f,1.0f,1.0f, 1.0f,4.0f,0.0f, 0.5f,0.5f, 0,1, 2 },
{ 0.0f,0.0f,1.0f,1.0f, 1.0f,6.0f,0.0f, 1.0f,0.0f, 1,0, 1 },
{ 0.0f,0.0f,1.0f,1.0f, -1.0f,6.0f,0.0f, 1.0f,0.0f, 1,0, 1 }
};
float arBone[] =
{
0.0f, 0.0f, 0.0f,
-0.2f, 0.2f,-0.2f,
0.2f, 0.2f,-0.2f,
0.0f, 3.0f, 0.0f,
-0.2f, 0.2f,-0.2f,
-0.2f, 0.2f, 0.2f,
0.0f, 0.0f, 0.0f,
0.2f, 0.2f,-0.2f,
0.2f, 0.2f, 0.2f,
0.0f, 0.0f, 0.0f,
-0.2f, 0.2f, 0.2f,
0.0f, 3.0f, 0.0f,
0.2f, 0.2f, 0.2f,
-0.2f, 0.2f, 0.2f,
};
matrix4x4f g_boneMatrix[2];
matrix4x4f g_matrixToRenderBone[2];
inline vector3f operator * (const vector3f& v, const matrix4x4f& mat)
{
return vector3f
(
v.x*mat.v[0][0] + v.y*mat.v[1][0] + v.z*mat.v[2][0] + 1*mat.v[3][0],
v.x*mat.v[0][1] + v.y*mat.v[1][1] + v.z*mat.v[2][1] + 1*mat.v[3][1],
v.x*mat.v[0][2] + v.y*mat.v[1][2] + v.z*mat.v[2][2] + 1*mat.v[3][2]
);
}
class Tutorial10 :public CELL::Graphy::CELLWinApp
{
public:
Tutorial10(HINSTANCE hInstance)
:CELL::Graphy::CELLWinApp(hInstance)
{
_lbtnDownFlag = false;
_fSpinY = 0;
_fSpinX = 0;
_bMousing_R = 0;
}
virtual void render()
{
do
{
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
glTranslatef( 0.0f, 0.0f, -15 );
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
{
g_boneMatrix[0].identity();
g_matrixToRenderBone[0].identity();
matrix4x4f rotationMatrixY;
matrix4x4f rotationMatrixZ;
matrix4x4f boneRotationMatrix;
g_boneMatrix[1].identity();
g_matrixToRenderBone[1].identity();
matrix4x4f offsetMatrix_toBoneEnd;
matrix4x4f offsetMatrix_backFromBoneEnd;
offsetMatrix_toBoneEnd.translate_y( 3.0f );
offsetMatrix_backFromBoneEnd.translate_y( -3.0f );
rotationMatrixY.rotate_y( g_fSpinY_R);
rotationMatrixZ.rotate_z(-g_fSpinX_R);
boneRotationMatrix = rotationMatrixY * rotationMatrixZ;
g_boneMatrix[1] = g_boneMatrix[0] * offsetMatrix_toBoneEnd * boneRotationMatrix;
g_matrixToRenderBone[1] = g_boneMatrix[1];
g_boneMatrix[1] = g_boneMatrix[1] * offsetMatrix_backFromBoneEnd;
}
/**
* 繪制表皮,保存臨時點數據
*/
Vertex calQuadVertices[12];
memcpy(calQuadVertices,g_quadVertices,sizeof(g_quadVertices));
for (int i = 0 ;i < 12 ; ++ i )
{
vector3f vec(0,0,0);
vector3f vecSrc(g_quadVertices[i].x,g_quadVertices[i].y,g_quadVertices[i].z);
for (int x = 0 ; x < calQuadVertices[i].numBones ; ++ x)
{
//! 計算位置
vector3f temp = vecSrc* g_boneMatrix[g_quadVertices[i].matrixIndices[x]];
//! 計算權重位置
vec += temp * g_quadVertices[i].weights[x];
}
calQuadVertices[i].x = vec.x;
calQuadVertices[i].y = vec.y;
calQuadVertices[i].z = vec.z;
}
glColorPointer(4,GL_FLOAT,sizeof(Vertex),calQuadVertices);
glVertexPointer(3,GL_FLOAT,sizeof(Vertex),((float*)calQuadVertices) + 4);
for (int i = 0 ;i < 3 ; ++ i )
{
glDrawArrays(GL_LINE_LOOP,i * 4,4);
}
glDisableClientState(GL_COLOR_ARRAY);
/**
* 繪制骨頭
*/
glVertexPointer(3,GL_FLOAT,0,arBone);
glPushMatrix();
{
//! 綠色骨頭
glMultMatrixf( g_matrixToRenderBone[0].m );
glColor3f( 1.0f, 1.0f, 0.0 );
glDrawArrays(GL_LINE_STRIP,0,sizeof(arBone)/12);
}
glPopMatrix();
glPushMatrix();
{
//! 藍色骨頭
glMultMatrixf( g_matrixToRenderBone[1].m );
glColor3f( 0.0f, 0.0f, 1.0 );
glDrawArrays(GL_LINE_STRIP,0,sizeof(arBone)/12);
}
glPopMatrix();
SwapBuffers( _hDC );
} while (false);
}
/**
* 生成投影矩陣
* 后面為了重用性,我們會寫一個專門的matrix類,完成矩陣的一系列擦做
* 這個是很有必須要的,當你對Opengl了解的不斷深入,你會發現,很多都是和數學有關的
*/
void perspective(float fovy,float aspect,float zNear,float zFar,float matrix[4][4])
{
assert(aspect != float(0));
assert(zFar != zNear);
#define PI 3.14159265358979323f
float rad = fovy * (PI / 180);
float halfFovy = tan(rad / float(2));
matrix[0][0] = float(1) / (aspect * halfFovy);
matrix[1][1] = float(1) / (halfFovy);
matrix[2][2] = -(zFar + zNear) / (zFar - zNear);
matrix[2][3] = -float(1);
matrix[3][2] = -(float(2) * zFar * zNear) / (zFar - zNear);
#undef PI
}
virtual void onInit()
{
/**
* 調用父類的函數。
*/
CELL::Graphy::CELLWinApp::onInit();
glMatrixMode( GL_PROJECTION );
GLfloat matrix[4][4] =
{
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0
};
perspective(45.0f, (GLfloat)_winWidth / (GLfloat)_winHeight, 0.1f, 100.0f,matrix);
glLoadMatrixf((float*)matrix);
glClearColor(0.35f, 0.53f, 0.7f, 1.0f);
}
virtual int events(unsigned msg, unsigned wParam, unsigned lParam)
{
switch(msg)
{
case WM_LBUTTONDOWN:
{
_mousePos.x = LOWORD (lParam);
_mousePos.y = HIWORD (lParam);
_lbtnDownFlag = true;
SetCapture(_hWnd);
}
break;
case WM_LBUTTONUP:
{
_lbtnDownFlag = false;
ReleaseCapture();
}
break;
case WM_RBUTTONDOWN:
{
_ptLastMousePosit_R.x = _ptCurrentMousePosit_R.x = LOWORD (lParam);
_ptLastMousePosit_R.y = _ptCurrentMousePosit_R.y = HIWORD (lParam);
_bMousing_R = true;
}
break;
case WM_RBUTTONUP:
{
_bMousing_R = false;
}
break;
case WM_MOUSEMOVE:
{
int curX = LOWORD (lParam);
int curY = HIWORD (lParam);
if( _lbtnDownFlag )
{
_fSpinX -= (curX - _mousePos.x);
_fSpinY -= (curY - _mousePos.y);
}
_mousePos.x = curX;
_mousePos.y = curY;
_ptCurrentMousePosit_R.x = LOWORD (lParam);
_ptCurrentMousePosit_R.y = HIWORD (lParam);
if( _bMousing_R )
{
g_fSpinX_R -= (_ptCurrentMousePosit_R.x - _ptLastMousePosit_R.x);
g_fSpinY_R -= (_ptCurrentMousePosit_R.y - _ptLastMousePosit_R.y);
}
_ptLastMousePosit_R.x = _ptCurrentMousePosit_R.x;
_ptLastMousePosit_R.y = _ptCurrentMousePosit_R.y;
}
break;
}
return __super::events(msg,wParam,lParam);
}
protected:
unsigned _primitiveType;
/**
* 保存紋理Id
*/
unsigned _textureId;
float _fSpinX ;
float _fSpinY;
POINT _mousePos;
bool _lbtnDownFlag;
POINT _ptLastMousePosit_R;
POINT _ptCurrentMousePosit_R;
bool _bMousing_R;
};
int CALLBACK _tWinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nShowCmd
)
{
(void*)hInstance;
(void*)hPrevInstance;
(void*)lpCmdLine;
(void*)nShowCmd;
Tutorial10 winApp(hInstance);
winApp.start(640,480);
return 0;
}