[OpenGL] 利用精灵表制作动画

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/ZJU_fish1996/article/details/51902091

        说到OpenGL动画绘制,我们首先想到的就是读取多张图片并加载为纹理,然后再绘制的时候不断切换纹理图片即可。作为练习,这是可以的,但是,当我们的动画非常丰富的时候,我们就要涉及到大量的图片读取和纹理加载,这是非常低效并且麻烦的。

        更为常用的一种方法,就是把所有帧的图片存到一张图里,制作成精灵表,然后在绘制的时候,根据行列索引快速读取到我们需要的精灵图片,这就是我们所称的精灵表。提到精灵表,最经典的就是下面这张爆炸精灵了:

       

        (图片来自网络)


        利用索引找到精灵所在位置是一件非常容易的事情,它涉及到的运算量非常小。

        在这里,我们主要在void drawRect(GLuint texture,int i,int j)这个函数中做了文章,传入的i,j就是精灵的行列索引(从0开始),我们通过修改dir这个参数来指定纹理映射方式,如果想要完整地映射一张图片到矩形上,dir的取值为


       


        可以看出来它是把纹理图片左下角坐标当作(0,0),右上角当作(1,1),按逆时针顺序读取的。

        我们设x为每个精灵的长,y为每个精灵的宽(假设整张图片长宽均为1,对于爆炸精灵而言,长为1/6,宽为1/5),i,j是精灵的行列索引,我们只要对dir参数稍加修改:


       


        以下是我用最近的飞船消散的精灵表做的一个样例:

       

         

        制作精灵表时,我们可以用matlab对每个帧图像进行拼合,这里,num是指总共的帧数,col是每行帧数。

        所有图片命名为1.jpg ~ num.jpg,为了防止图片太大,我对图片进行了隔三行三列的下采样,具体可以根据情况自己修改。

       

function  genSpriteTable( num,col ) 

for i = 1:num
    a = imread([num2str(i),'.jpg']);
    m = floor((i-1)/col+1);
    n = floor(mod(i,col));
    if(n==0)
        n=col;
    end
    if (i==1)
        [h,w,~] = size(a);
        height = ceil(h/3);
        width = ceil(w/3);
        f = zeros(height*ceil(num/col),width*col,3);
    end
    g = a(1:3:h,1:3:w,:);
    f((m-1)*height + 1:m*height,(n-1)*width + 1:n*width,:) = g(:,:,:);  
end
f = uint8(f);
imwrite(f,'b.bmp','bmp');



test.h


#pragma once  
#define GLUT_DISABLE_ATEXIT_HACK    
#include "GL/GLUT.H"    
void loadTex(int i, char *filename, GLuint* texture);

texture.cpp(加载纹理用,可以当作模板代码)

#define _CRT_SECURE_NO_WARNINGS    
#include<stdio.h>    
#include<windows.h>    
#include"test.h"    
#define BITMAP_ID 0x4D42     


//读纹理图片      
static unsigned char *LoadBitmapFile(char *filename, BITMAPINFOHEADER *bitmapInfoHeader)
{

	FILE *filePtr;    // 文件指针      
	BITMAPFILEHEADER bitmapFileHeader;    // bitmap文件头      
	unsigned char    *bitmapImage;        // bitmap图像数据      
	int    imageIdx = 0;        // 图像位置索引      
	unsigned char    tempRGB;    // 交换变量      

								 // 以“二进制+读”模式打开文件filename       
	filePtr = fopen(filename, "rb");
	if (filePtr == NULL) {
		printf("file not open\n");
		return NULL;
	}
	// 读入bitmap文件图      
	fread(&bitmapFileHeader, sizeof(BITMAPFILEHEADER), 1, filePtr);
	// 验证是否为bitmap文件      
	if (bitmapFileHeader.bfType != BITMAP_ID) {
		fprintf(stderr, "Error in LoadBitmapFile: the file is not a bitmap file\n");
		return NULL;
	}
	// 读入bitmap信息头      
	fread(bitmapInfoHeader, sizeof(BITMAPINFOHEADER), 1, filePtr);
	// 将文件指针移至bitmap数据      
	fseek(filePtr, bitmapFileHeader.bfOffBits, SEEK_SET);
	// 为装载图像数据创建足够的内存      
	bitmapImage = new unsigned char[bitmapInfoHeader->biSizeImage];
	// 验证内存是否创建成功      
	if (!bitmapImage) {
		fprintf(stderr, "Error in LoadBitmapFile: memory error\n");
		return NULL;
	}

	// 读入bitmap图像数据      
	fread(bitmapImage, 1, bitmapInfoHeader->biSizeImage, filePtr);
	// 确认读入成功      
	if (bitmapImage == NULL) {
		fprintf(stderr, "Error in LoadBitmapFile: memory error\n");
		return NULL;
	}
	//由于bitmap中保存的格式是BGR,下面交换R和B的值,得到RGB格式      
	for (imageIdx = 0; imageIdx < bitmapInfoHeader->biSizeImage; imageIdx += 3) {
		tempRGB = bitmapImage[imageIdx];
		bitmapImage[imageIdx] = bitmapImage[imageIdx + 2];
		bitmapImage[imageIdx + 2] = tempRGB;
	}
	// 关闭bitmap图像文件     
	fclose(filePtr);
	return bitmapImage;
}

//加载纹理的函数      
void loadTex(int i, char *filename, GLuint* texture)
{

	BITMAPINFOHEADER bitmapInfoHeader;                                 // bitmap信息头      
	unsigned char*   bitmapData;                                       // 纹理数据      

	bitmapData = LoadBitmapFile(filename, &bitmapInfoHeader);

	glBindTexture(GL_TEXTURE_2D, texture[i]);
	// 指定当前纹理的放大/缩小过滤方式      
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

	glTexImage2D(GL_TEXTURE_2D,
		0,         //mipmap层次(通常为,表示最上层)       
		GL_RGB,    //我们希望该纹理有红、绿、蓝数据      
		bitmapInfoHeader.biWidth, //纹理宽带,必须是n,若有边框+2       
		bitmapInfoHeader.biHeight, //纹理高度,必须是n,若有边框+2       
		0, //边框(0=无边框, 1=有边框)       
		GL_RGB,    //bitmap数据的格式      
		GL_UNSIGNED_BYTE, //每个颜色数据的类型      
		bitmapData);    //bitmap数据指针      

}


main.cpp

#define _CRT_SECURE_NO_WARNINGS    

#include <stdio.h>    
#include <string.h>    
#include<time.h>  
#include <stdlib.h>  
#include"test.h"    


//纹理缓冲区
GLuint texture[1];

//视区    
float whRatio;
int wHeight = 0;
int wWidth = 0;


//视点    
float center[] = { 0, 0, 0 };
float eye[] = { 0, 0, 5 };

int count = 0;
int times = 0;

//帧动画参数
int num = 33;//一共多少帧
int col = 3;//一行有多少帧

void drawRect(GLuint texture,int i,int j)
{
	glEnable(GL_TEXTURE_2D);
	glBindTexture(GL_TEXTURE_2D, texture);  //选择纹理texture[status]     

	const GLfloat x1 = -0.5, x2 = 0.5;
	const GLfloat y1 = -0.5, y2 = 0.5;
	const GLfloat x = 1.0 / col, y = 1.0 / (num/col);
	const GLfloat point[4][2] = { { x1,y1 },{ x2,y1 },{ x2,y2 },{ x1,y2 } };
	const GLfloat dir[4][2] = { { j*x,1 - (i + 1)*y },{ (j + 1)*x,1 - (i + 1)*y },{ (j + 1)*x ,1 - i*y },{ j*x,1 - i*y } };
	glBegin(GL_QUADS);

	for (int k = 0; k < 4; k++) {
		glTexCoord2fv(dir[k]);
		glVertex2fv(point[k]);
	}
	glEnd();

	glDisable(GL_TEXTURE_2D);
}


void drawScene()
{
	count++;
	if (count >= 100) {
		count = 0;
		times++;
		if (times >= num)times = 0;
	
	}
	glPushMatrix();
	glScalef(1.0f, 1.0f, 1.0f);
	drawRect(texture[0],times/col,times%col);
	glPopMatrix();


}

void updateView(int height, int width)
{
	glViewport(0, 0, width, height);
	glMatrixMode(GL_PROJECTION);//设置矩阵模式为投影         
	glLoadIdentity();   //初始化矩阵为单位矩阵            
	whRatio = (GLfloat)width / (GLfloat)height;  //设置显示比例       
	glOrtho(-3, 3, -3, 3, -100, 100); //正投影  
	glMatrixMode(GL_MODELVIEW);  //设置矩阵模式为模型      
}

void reshape(int width, int height)
{
	if (height == 0)      //如果高度为0        
	{
		height = 1;   //让高度为1(避免出现分母为0的现象)        
	}

	wHeight = height;
	wWidth = width;

	updateView(wHeight, wWidth); //更新视角        
}


void idle()
{
	glutPostRedisplay();
}



void init()
{
	srand(unsigned(time(NULL)));
	glEnable(GL_DEPTH_TEST);//开启深度测试     

	glEnable(GL_LIGHTING);  //开启光照模式     

	glClearColor(1.0f, 1.0f, 1.0f, 1.0f);

	glGenTextures(1, texture);
	loadTex(0, "b.bmp", texture);
	

}



void redraw()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);//清除颜色和深度缓存      
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();   //初始化矩阵为单位矩阵        
	gluLookAt(eye[0], eye[1], eye[2], center[0], center[1], center[2], 0, 1, 0);                // 场景(0,0,0)的视点中心 (0,5,50),Y轴向上    
	glPolygonMode(GL_FRONT, GL_FILL);

	glFrontFace(GL_CCW);
	glEnable(GL_CULL_FACE);
	// 启用光照计算  
	glEnable(GL_LIGHTING);
	// 指定环境光强度(RGBA)  
	GLfloat ambientLight[] = { 2.0f, 2.0f, 2.0f, 1.0f };

	// 设置光照模型,将ambientLight所指定的RGBA强度值应用到环境光  
	glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambientLight);
	// 启用颜色追踪  
	glEnable(GL_COLOR_MATERIAL);
	// 设置多边形正面的环境光和散射光材料属性,追踪glColor  
	glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);

	drawScene();//绘制场景     
	glutSwapBuffers();//交换缓冲区    
}

int main(int argc, char *argv[])
{

	glutInit(&argc, argv);//对glut的初始化           
	glutInitDisplayMode(GLUT_RGBA | GLUT_DEPTH | GLUT_DOUBLE);
	//初始化显示模式:RGB颜色模型,深度测试,双缓冲             
	glutInitWindowSize(500, 500);//设置窗口大小           
	int windowHandle = glutCreateWindow("Simple GLUT App");//设置窗口标题             
	glutDisplayFunc(redraw); //注册绘制回调函数           
	glutReshapeFunc(reshape);   //注册重绘回调函数           

	glutIdleFunc(idle);//注册全局回调函数:空闲时调用         

	init();
	glutMainLoop();  // glut事件处理循环         
	return 0;
}



展开阅读全文

没有更多推荐了,返回首页