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;
    }

  • 相关阅读:
    Java实现 LeetCode 394 字符串解码
    Java实现 LeetCode 394 字符串解码
    Java实现 LeetCode 392 判断子序列
    Java实现 LeetCode 392 判断子序列
    Java实现 LeetCode 392 判断子序列
    Java实现 LeetCode 391 完美矩形
    Java实现 LeetCode 391 完美矩形
    Java实现 LeetCode 391 完美矩形
    Java实现 LeetCode 390 消除游戏
    Java实现 LeetCode 390 消除游戏
  • 原文地址:https://www.cnblogs.com/zhanglitong/p/3196752.html
Copyright © 2011-2022 走看看