zoukankan      html  css  js  c++  java
  • 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;
    }

  • 相关阅读:
    Nightmare Ⅱ HDU
    Full Tank? POJ
    2601 电路维修 (双端队列bfs优先队列bfs(最短路))
    Sudoku POJ
    Pushing Boxes POJ
    2501 矩阵距离 (bfs)
    【排序】绝境求生
    【排序】逆序对IV
    【排序】紧急集合
    【排序】常用排序法
  • 原文地址:https://www.cnblogs.com/zhanglitong/p/3196752.html
Copyright © 2011-2022 走看看