骨骼动画入门----BVH文件的载入和播放

来源:互联网 发布:整人锁屏软件 编辑:程序博客网 时间:2024/05/22 02:28

阅读本文的基础

1 c++

2 OpenGL

3 图形学的基本概念

 

骨骼动画基本概念概括

  传统的帧动画将模型中的点的坐标信息存取为一帧,播放下一帧时,就读取下一帧的全部点的新的坐标信息。这样的方式浪费了很大的空间,而且模型之间的层级关系无法体现,不利于和场景以及其他模型的交互。

 骨骼动画也是一帧一帧的,但是每一帧存取的数据不是坐标信息,而是层级结构中,子节点相对父亲节点的平移和旋转信息。递归画出层级结构是很容易实现的。

这样存取克服了帧动画的缺点。骨骼动画的缺点是实现复杂,新数据生成需要一定的时间。

 选取BVH文件格式作为入门是不错的选择,因为这个格式的简单,不含有其他信息,只含有动作数据,让你化繁为简,一目了然的理解骨骼动画的算法。

 

BVH文件格式

细节1 每行的最后有一个回车符和一个换行符,用16进制观察会很明显的发现

部分一 层级结构信息

用文本文件打开后,一目了然的会出现一棵树的结构。

offset 表示当前节点相对于父节点的位置信息

channels 表示对下面motion数据的解释 除父节点外 channels都有三个信息( 父节点为六个) 对应了子节点对父节点的旋转量 这些数据放在motion中

部分二 motion数据

有多少帧 就有多少行数据

在每一帧中,也就是每一行中,数据的前3项为根节点的位移信息

其余数据三个为一组代表旋转信息,和层级中的对应顺序为: 文本文件中从上到下channels出现的顺序

也就是这个层级树先序遍历的顺序

细节2 End节点没有这些旋转的信息 root中有这些信息

 

说明:一组的3个数据中 要根据channels中的信息来解析

例如 当channels 中为 Xrotation Zrotation Yrotation

操作时就要

glRotatef(pFrame[0],1.0f,0.0f,0.0f);
glRotatef(pFrame[1],0.0f,0.0f,1.0f);
glRotatef(pFrame[2],0.0f,1.0f,0.0f);

pFrame 指向当前的旋转信息组

细节3 offset 后的数据之间和channels后的解释信息间可能为空格也可能为tab

强烈建议用16进制查看器看多个文件 这样对文件的具体格式将会有清晰的认识 避免不必要的错误代码的编写

 

下面是代码和注释

#ifndef __BVH_H__#define __BVH_H__#include <stack>using namespace std;class BVHJoint;class BVH{public:BVH();~BVH();void clear();bool loadFile(const char * pfile);void print();void setCurrentFrame(unsigned c);void draw();unsigned getFrameCount();void addFrame();private:BVHJoint * root;//根节点stack<BVHJoint*> father;//载入时用到的栈 根据前序构建树BVHJoint* currentNode;//载入时当前的节点unsigned char* p;//载入时读buffer的当前位置unsigned jointCount;//节点的数量包括rootunsigned frameCount;//帧数unsigned currentFrame;//当前帧float **frameData;//存取motion数据的二维数组
float frameTime;//float *pFrame;//绘制使用的当前motion信息位置
void drawRecursive(BVHJoint * r);void roateSpace(unsigned char);void deleteRecursive(BVHJoint* r);bool processLeftBrace();bool processRightBrace();bool processJoint();bool processOffset();bool processChannels();bool processEnd();unsigned char getFlags();BVHJoint * newNode();void printRecursive(BVHJoint* r,int n);};#endif


 

#include <cassert>#include <cstdio>#include <cstdlib>#include <gl/glut.h>#include "BVH.h"enum { CHILDSIZE = 16 ,NAMESIZE = 24};enum { ZYX = 1, YZX = 2, ZXY = 3, XZY = 5, YXZ = 6, XYZ = 7};//------------------------------------------------------class BVHJoint{public:friend class BVH;BVHJoint():x(0),y(0),z(0),childNum(0),flags(0){name[0] = 0;}bool addChild(BVHJoint* pc){assert(childNum != NAMESIZE);child[childNum] = pc;childNum++;return true;}BVHJoint* getChild(int i){assert( i>=0 && i<CHILDSIZE);return child[i];}private:float x;float y;float z;//三个值表示距离父节点的偏移量BVHJoint * child[CHILDSIZE];int childNum;unsigned char name[NAMESIZE];unsigned char flags;//记录了channels 所代表的含义};//-------------------------------------------------------BVH::BVH():currentNode(nullptr),p(nullptr),root(nullptr),frameCount(0),frameData(nullptr),pFrame(nullptr),frameTime(1),jointCount(1),currentFrame(0){}//------------------------------------------------------BVH::~BVH(){clear();}//------------------------------------------------------void BVH::deleteRecursive(BVHJoint* r){for(int i = 0; i < r->childNum; i++){deleteRecursive(r->getChild(i));}delete r;}//------------------------------------------------------void BVH:: clear(){currentNode = nullptr;p = nullptr;while(!father.empty()) father.pop();frameTime = 1;for(unsigned int i = 0; i < frameCount; i++)delete []frameData[i];delete []frameData;frameData = nullptr;pFrame = nullptr;frameCount = 0;currentFrame = 0;jointCount = 1;if(root != nullptr){deleteRecursive(root);}root = nullptr;}//------------------------------------------------------//-------------------------------------------------------//根据当前p指向的名字构建一个节点,读取文件的buffer时 //必须通过这个函数来产生新节点BVHJoint* BVH:: newNode(){BVHJoint * node = new BVHJoint();int i;for(i = 0; i < NAMESIZE && *p != '\n';i++){node->name[i] = *p;p++;}if(i == NAMESIZE) --i;node->name[i-1] = 0;//为了吃掉一个回车符!!p++;return node;}//------------------------------------------------------//获得每个节点的通道信息,采用1,2,3,5,6,7表示6种可能的情况unsigned char BVH::getFlags(){return (unsigned char)((*p - 'X' + 1)*1 + (*(p+10) - 'X' + 1)*2 + (*(p+20) - 'X' + 1)*4 - 10);}//------------------------------------------------------//载入一个文件,生成BVH的一棵树形结构和motion旋转信息的二位数组bool BVH::loadFile(const char* pfile){if(pfile == 0)return false;FILE *f;if(!(f = fopen(pfile,"rb"))){printf("file load failed!\n");return false;}//获取文件长度int iStart = ftell(f);fseek(f,0,SEEK_END);int iEnd = ftell(f);rewind(f);int iFileSize =  iEnd -iStart;//结束获取长度信息//分配文件长的动态数组unsigned char *buffer = new unsigned char[iFileSize]; if(!buffer){printf("mem alloc failed!!\n");return false;}//载入文件到bufferif(fread(buffer,1,iFileSize,f)!=(unsigned)iFileSize){printf("failed!!\n");delete []buffer;return false;}//验证文件是否为BVHconst char * fileheader = "HIERARCHY";p = buffer;for(int i = 0; i < 9 ; i++ ){if( *p != fileheader[i]){delete []buffer;return false;}p++;}//验证文件结束//载入根节点名字p += 7;root = newNode();//保持栈顶元素和当前节点同步father.push(root);currentNode = root;//载入根节点的偏移offset信息 这里的offset信息和joint不同需要特化处理while(*p != 'O') p++;p += 7;root->x = (float)atof((char*)p);p += 5;if(*p == ' ') p++;root->y = (float)atof((char*)p);p += 5;if(*p == ' ') p++;root->z = (float)atof((char*)p);p += 5;//结束offset信息的载入//跳到*rotation 位置 越过根节点的位移通道量 这里认为root是固定的XYZ位移模式while(*p != 'r') p++;p--;//获得根节点的旋转通道信息root->flags = getFlags();p += 30;//结束载入根节点//根据字符流的首字符 构建状态处理逻辑 载入子节点int counter = 1; //大括号的数量 初始化为1因为根节点之后已经有了一个for(bool running = true; running ; ){//文件格式出错才会这样if(*p == 0){ delete []buffer;clear();return 0;}//根据首字母 分发状态处理switch(*p){case 13://回车case '\n'://换行case ' ':case '':p++;break;case '{':processLeftBrace();counter++;break;case '}':processRightBrace();//判断层级数据载入是否结束counter--;if(counter == 0) running = false;break;case 'J':jointCount++;processJoint();break;case 'O':processOffset();break;case 'C':processChannels();break;case 'E':processEnd();break;default:printf("_%c_ _%d_ file format error!! \n",*p,*p);delete []buffer;clear();return false;}}while(*p!= 'F') p++;p += 8;frameCount = (unsigned)atoi((char *)p);while(*p != 'F') p++;p += 12;frameTime = (float)atof((char*)p);//结束载入 层级关系的数据while(*p++ != '\n');// 现在 p 指向了真正的motion数据// 先分配空间frameData = new float*[frameCount];if(frameData == nullptr) {delete []buffer;clear();return false;}//每一帧的数据包括3个root节点的平移数据 和所有节点的旋转数据if(jointCount == 1){delete []buffer;clear();return false;}int dataCount = jointCount*3 + 3;//printf("dataCount is : %d\n", dataCount );for(unsigned int i = 0; i < frameCount; i++){frameData[i] = new float[dataCount];if(frameData == nullptr){delete []buffer;clear();return false;}}// 开始载入 motion 的数据// 细节:每一帧的数据为一行 每个数据之间有一个空格或tab //行最后有一个空格或tab然后是一个回车符和一个换行符for(unsigned int i = 0; i < frameCount; i++){for(int j = 0; j < dataCount; j++){frameData[i][j] = (float)atof((char*)p);p += 8;while(*p != ' ' && *p != '') p++;p++;//跳过空格或tab}//跳过回车和换行符p+=2;}//载入motion数据结束//载入全部数据结束delete []buffer;fclose(f);return true;}//------------------------------------------------------bool BVH::processLeftBrace(){//栈中存放了一系列的父节点 栈顶为当前节点,father.push(currentNode);p++;return true;}//------------------------------------------------------bool BVH::processRightBrace(){//栈顶为当前节点所以一定要先出栈(被debug1)father.pop();//保持当前节点和栈顶元素同步if(!father.empty())currentNode = father.top();p++;return true;}//------------------------------------------------------bool BVH::processJoint(){p += 6;BVHJoint *node = newNode();//发现一个子节点添加父子关系if(!currentNode->addChild(node))return false;currentNode = node;//马上会处理‘{’ 使得栈顶与当前节点同步return true;}//------------------------------------------------------//由于offset在这里的文件格式不太统一 所以需要注意 因为root的offset格式特殊bool BVH::processOffset(){p += 7;currentNode->x = atof((const char *)p);p += 8;while(*p != ' ' && *p != '' ) p++;p++;currentNode->y = atof((const char *)p);p += 8;while(*p != ' ' && *p != '' ) p++;p++;currentNode->z = atof((const char *)p);while(*p++ != '\n');return true;}//------------------------------------------------------//getFlags 将根据三个roation前面的三个大写字母判断通道的格式bool BVH::processChannels(){p += 11;currentNode->flags = getFlags();p += 30;return true;}//------------------------------------------------------bool BVH::processEnd(){//其实和joint节点的处理是一样的 就是 p+=4 这里前进的不同p += 4;BVHJoint *node = newNode();if(!currentNode->addChild(node))return false;currentNode = node;return true;}//-----------------------------------------------------//绘制骨骼 root节点还是多了处理void BVH::draw(){pFrame = frameData[currentFrame];root->x = pFrame[0];root->y = pFrame[1];root->z = pFrame[2];pFrame += 3;drawRecursive(root);}//-----------------------------------------------------//绘制函数的核心 递归的绘制 void BVH::drawRecursive(BVHJoint* r){glPushMatrix();//要先平移后旋转(被debug3)//平移 根据父节点空间glTranslatef(r->x,r->y,r->z);//根据motion中的信息和节点的通道类型进行旋转roateSpace(r->flags);//画出点glutSolidSphere(1.0f,20,16);//递归绘制子节点for(int i = 0; i < r->childNum; i++){drawRecursive(r->getChild(i));} glPopMatrix();}//-----------------------------------------------------void BVH::roateSpace(unsigned char flags){//enum {ZYX = 1, YZX = 2,ZXY = 3,XZY = 5,YXZ = 6,XYZ = 7};switch (flags){case ZYX:glRotatef(pFrame[0],0.0f,0.0f,1.0f);glRotatef(pFrame[1],0.0f,1.0f,0.0f);glRotatef(pFrame[2],1.0f,0.0f,0.0f);break;case YZX:glRotatef(pFrame[0],0.0f,1.0f,0.0f);glRotatef(pFrame[1],0.0f,0.0f,1.0f);glRotatef(pFrame[2],1.0f,0.0f,0.0f);break;case ZXY:glRotatef(pFrame[0],0.0f,0.0f,1.0f);glRotatef(pFrame[1],1.0f,0.0f,0.0f);glRotatef(pFrame[2],0.0f,1.0f,0.0f);break;case XZY:glRotatef(pFrame[0],1.0f,0.0f,0.0f);glRotatef(pFrame[1],0.0f,0.0f,1.0f);glRotatef(pFrame[2],0.0f,1.0f,0.0f);break;case YXZ:glRotatef(pFrame[0],0.0f,1.0f,0.0f);glRotatef(pFrame[1],1.0f,0.0f,0.0f);glRotatef(pFrame[2],0.0f,0.0f,1.0f);break;case XYZ:glRotatef(pFrame[0],1.0f,0.0f,0.0f);glRotatef(pFrame[1],0.0f,1.0f,0.0f);glRotatef(pFrame[2],0.0f,0.0f,1.0f);break;default:break;}//End叶节点时不能前进 因为它没有旋转信息(被debug2)//pFrame初始化为当前帧root节点的旋转信息//每旋转一个节点的空间后 自动+3跳到下一个节点的旋转信息//说明: BVH 存放旋转的信息按照文件中从上到下channel出现的顺序存储//就是递归遍历时的前序周游顺序if(flags != 0) pFrame+=3;}//----------------------------------------------------unsigned BVH::getFrameCount(){return frameCount;}//-----------------------------------------------------void BVH::addFrame(){currentFrame ++;if(currentFrame == frameCount) currentFrame = 0;}//-----------------------------------------------------void BVH::setCurrentFrame(unsigned c){assert(c>=0&&c<frameCount);currentFrame = c;}//------------------------------------------------------//将载入好的层级信息打印出来void BVH::printRecursive(BVHJoint* r,int n){for(int i = 0; i < n; i++) printf(" -"); printf("%s",r->name);printf(" : %f,%f,%f -%d- ",r->x,r->y,r->z,r->flags);switch(r->flags){case 1:printf("zyx");break;case 2:printf("yzx");break;case 3:printf("zxy");break;case 5:printf("xzy");break;case 6:printf("yxz");break;case 7:printf("xyz");break;default:break;}printf("\n");for(int i = 0; i < r->childNum ; i++){printRecursive(r->getChild(i) , n+1);}}//------------------------------------------------------void BVH::print(){printRecursive(root , 0);//int datacount = jointCount*3+3;//for(int j= 0 ; j < frameCount; j++)//{//printf("frame %d:\n",j);//for(int i = 0; i < datacount; i++)//printf("%f ",frameData[j][i]);//printf("\n");//}}//------------------------------------------------------
#include <cstdio>#include <cstdlib>#include <gl/glut.h>#include "BVH.h"BVH model;float R = 200.0f;float angle = 10;void init(){glClearColor( 0.0f, 0.0f, 0.0f, 0.0f );glEnable(GL_DEPTH_TEST);GLfloat position[] = {0.0f,0.0f,1.0f,1.0f};glLightfv(GL_LIGHT0,GL_POSITION,position);glEnable(GL_LIGHTING);glEnable(GL_LIGHT0);glLineWidth(3.0f);glColor3f(0.0f,1.0f,0.0f);model.loadFile("sexy.bvh");model.print();}void uninit(){}void display(){glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );glLoadIdentity();gluLookAt( R*cos(angle), 20, R*sin(angle), 0, 20, 0, 0, 1, 0 );model.draw();glBegin(GL_QUADS);glVertex3f(-100,-10,100);glVertex3f(100,-10,100);glVertex3f(100,-10,-100);glVertex3f(-100,-10,-100);glEnd();glutSwapBuffers();}void reshape( int w, int h ){glViewport( 0, 0, GLsizei( w ), GLsizei( h ) );glMatrixMode( GL_PROJECTION );glLoadIdentity();gluPerspective( 45, ( GLdouble ) w / ( GLdouble ) h, 1.0f, 1000.0f );glMatrixMode( GL_MODELVIEW );glLoadIdentity();gluLookAt( R*cos(angle), 0, R*sin(angle), 0, 0, 0, 0, 1, 0 );}void keyboard( unsigned char key, int x, int y ){switch( key  ){case 27:exit( 0 );case 'a':case 'A':angle += 0.1;if(angle >= 360.0f) angle = 0;break;case 'd':case 'D':angle -= 0.1;if(angle <=0.0f) angle = 359.9f;break;case 'w':case 'W':model.addFrame();}glutPostRedisplay();}int main( int argc, char *argv[] ){glutInit( &argc, argv );glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH );glutInitWindowPosition( 300, 75 );glutInitWindowSize( 600, 600 );glutCreateWindow( "OpenGL  Test" );init();glutReshapeFunc( reshape );glutKeyboardFunc( keyboard );glutDisplayFunc( display );//glutIdleFunc(display);glutMainLoop();uninit();return 0;}



 

原创粉丝点击