OpenGL10-骨骼动画原理篇(1)
视频教程请关注 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;
}
OpenGL10-骨骼动画原理篇(1)的更多相关文章
- Unity3D 骨骼动画原理学习笔记
最近研究了一下游戏中模型的骨骼动画的原理,做一个学习笔记,便于大家共同学习探讨. ps:最近改bug改的要死要活,博客写的吭哧吭哧的~ 首先列出学习参考的前人的文章,本文较多的参考了其中的表述: 1. ...
- OpenGL10-骨骼动画原理篇(3)-Shader版本代码已经上传
视频教程请关注 http://edu.csdn.net/lecturer/lecturer_detail?lecturer_id=440 接上一个例程OpenGL10-骨骼动画原理篇(2),对骨骼动画 ...
- OpenGL10-骨骼动画原理篇(2)
接上一篇的内容,上一篇,简单的介绍了,骨骼动画的原理,给出来一个 简单的例程,这一例程将给展示一个最初级的人物动画,具备多细节内容 以人走路为例子,当人走路的从一个站立开始,到迈出一步,这个过程是 一 ...
- 骨骼动画的原理及在Unity中的使用
制作骨骼动画 我们看看这几步操作后,我们得到了那些数据: 1.每个皮肤顶点的初始世界坐标. 2.每个骨骼关节顶点的初始世界坐标. 3.每个顶点被骨骼顶点的影响信息. 4.骨骼如何移动. 骨骼动画原理 ...
- 浅谈Unity3D 骨骼动画
转载请标明出处http://www.cnblogs.com/zblade/ 最近研究了一下游戏中模型的骨骼动画的原理,做一个学习笔记,便于大家共同学习探讨. ps:最近改bug改的要死要活,博客写的吭 ...
- Spine Skeleton Animation(2D骨骼动画)
骨骼动画 首先我们来看到底什么是骨骼动画: 在早期的机器上,渲染本身已经占用了很多CPU资源,因此,对于渲染,往往采取的是一种空间换时间的策略,以避免在模型的渲染中继续加重CPU的负担.帧动画模型在这 ...
- 引擎设计跟踪(九.14.2b) 骨骼动画基本完成
首先贴一个介绍max的sdk和骨骼动画的文章, 虽然很早的文章, 但是很有用, 感谢前辈们的贡献: 3Ds MAX骨骼动画导出插件编写 1.Dual Quaternion 关于Dual Quatern ...
- MD5骨骼动画模型加载(一)
前面我们分析了静态模型OBJ格式,桢动画模型MD2,这篇主要分析骨骼动画MD5的一些概念并且实现. 混合桢动画有计算简单,容易实现等优点,但是在需要比较细致的效果时,则需要更多的关键桢,每桢都添加相同 ...
- MD5骨骼动画模型加载
前面我们分析了静态模型OBJ格式,桢动画模型MD2,这篇主要分析骨骼动画MD5的一些概念并且实现. 混合桢动画有计算简单,容易实现等优点,但是在需要比较细致的效果时,则需要更多的关键桢,每桢都添加相同 ...
随机推荐
- 存储过程传入datatable
存储过程传入一般的参数都很简单,今天要说一下存储过程传入datatable 类型 首先要自定义一个 表类型 CREATE TYPE [dbo].[servicedatableType] AS TABL ...
- Working days
form FRM_GET_WORKING_DAYS TABLES pt_days CHANGING pv_duration. DATA:ls_xt001w TYPE t001w, lv_sdate T ...
- thinkphp 视图(二)变量输出、赋值和替换
view下的html文件会编译成php文件 编译的文件在runtime 下的temp目录 <p>{$email}</p> 会编译成 <?php echo $email; ...
- HttpWebRequest 高效并发问题
默认请求连接数 是2,在服务器操作系统上默认为10. 如果不修改这个并发连接限制,那么客户端同时可以建立的 http 连接数就只有2个或10个. System.Net.ServicePointMana ...
- CentOS 7 Redis 内网 安装 卸载
# 不能连接外网, 安装Redis服务器的过程 https://redis.io/download (官网下载安装包, 最新版) redis-*.tar.gz 放在安装目录/usr/local/red ...
- 如何解决PHP的高并发和大流量的问题
基础知识 TFS : 吞吐量 (吞吐量是指系统在单位时间内处理请求的数量) RT : 响应时间 (从请求发出到收到响应时间) 并发数 : 在一段时间内同时访问站点的用户数 QPS : 每秒查询率 (每 ...
- js图片预加载、有序加载
<!DOCTYPE html><html lang="zh-CN"><head> <meta charset="UTF-8&qu ...
- python模块:json
r"""JSON (JavaScript Object Notation) <http://json.org> is a subset of JavaScri ...
- 桌面应用开发之WPF动态背景
因为项目需要,在WPF开发的桌面应用中,登陆页面需使用动态背景.由于没有前端开发人员,所以由半吊子的后端开发人员根据效果图写前端xaml.去掉页面上边框,抽离动态背景设置代码: <Windo ...
- Springboot高版本中@ConfigurationProperties注解取消location属性
在spring boot 1.5 版本之前 在@ConfigurationProperties注释中有两个属性:locations:指定配置文件的所在位置prefix:指定配置文件中键名称的前缀 sp ...