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