1. 程式人生 > >[OpenGL] 利用精靈表製作動畫

[OpenGL] 利用精靈表製作動畫

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