[OpenGL] 基于纹理绘制的透明精灵(透明纹理)

        在二维游戏中,我们几乎绕不开精灵绘制这一过程,除了直接在opengl读入图像并绘制外,我们更常使用纹理来完成这一过程,把纹理贴到在xy平面上的面片,做出二维游戏的效果。

        这样我们可以很方便的使用opengl提供给我们的一些方法来执行精灵的变换,而不是使用大量的贴图来手工完成变换过程;同时,还可以通过调节深度信息来确定物体的遮挡关系,而不用花心思考虑绘制的先后顺序,因为我们知道,在二维世界里,谁遮挡谁是和绘制顺序相关的。

        但是,我们也发现,我们的精灵不总是四四方方的,所以纹理加载进来后会有背景色,我们希望去除这个背景色,一个非常好的想法就是使用带透明(alpha)通道的图片,可是非常遗憾的是,opengl不能直接支持这种格式,所以这个方法不可行。

        还有一种比较常见的想法是这样的:我们使用一种颜色代表背景色,在opengl中开启alpha测试,把纹理图片设置为rgba格式,读取到这种颜色后,我们把alpha手动设为0,也就是全透明;否则设为1,也就是不透明。我们可以意识到:这个背景色的选取是有要求的,它不能与图片不透明部分rgb相同,否则它们会被错误识别为透明的。

        以下是我最近正在写的一个基于opengl的小游戏,这里使用了透明纹理,其中火箭是网上暂时找的图片,用于测试效果,下面的飞船图片是自己做的。由于火箭没有进行特殊处理,所以我们发现它的中间部分也有被镂空的,这就是本次代码存在的一些缺陷,它需要我们对图片进行特殊的处理才能使用。


        上图的代码会在完成后放出,为了演示透明纹理效果,我又单独给出了一个测试代码:

        原图(图1来自网络):

       

        使用透明纹理后效果:

       

        在这个代码中,背景色是通过参数指定的。

        需要注意的是,在绘制的时候,我们尽可能先绘制一般纹理的面片,然后统一绘制透明纹理的面片,在绘制透明纹理时,需要把深度测试设置为只读模式,最后再修改回原来的状态,防止在进行处理的时候破坏了深度信息。


test.h

#pragma once
#define GLUT_DISABLE_ATEXIT_HACK  
#include "GL/GLUT.H"  
void loadTex(int i, char *filename, GLuint* texture);//一般纹理
void loadTex(int i, char *filename, GLuint* texture,unsigned char* backgroundColor);//透明纹理

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

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

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

                                // 以“二进制+读”模式打开文件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;
    }
    unsigned char*   bitmapData;   // 纹理数据 

    bitmapData = new unsigned char[bitmapInfoHeader->biSizeImage / 3 * 4];

    int count = 0;
    //添加alpha通道
    for (imageIdx = 0; imageIdx < bitmapInfoHeader->biSizeImage; imageIdx += 3) {
        bitmapData[count] = bitmapImage[imageIdx + 2];
        bitmapData[count + 1] = bitmapImage[imageIdx + 1];
        bitmapData[count + 2] = bitmapImage[imageIdx];
        if (bitmapData[count] == backgroundColor[0]
            && bitmapData[count + 1] == backgroundColor[1] 
            && bitmapData[count + 2] == backgroundColor[2]){
            bitmapData[count + 3] = 0;
        }
        else bitmapData[count + 3] = 255;
        count += 4;
    }

    // 关闭bitmap图像文件   
    fclose(filePtr);
    return bitmapData;
}

//加载纹理的函数    
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数据指针    

}

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

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

    bitmapData = LoadBitmapFile(filename, &bitmapInfoHeader,backgroundColor);

    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_RGBA,    //我们希望该纹理有红、绿、蓝、alpha数据    
        bitmapInfoHeader.biWidth, //纹理宽带,必须是n,若有边框+2     
        bitmapInfoHeader.biHeight, //纹理高度,必须是n,若有边框+2     
        0, //边框(0=无边框, 1=有边框)     
        GL_RGBA,    //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"  

  
#include <math.h>       /* for cos(), sin(), and sqrt() */    
GLuint texture[2];
//视区  
float whRatio;
int wHeight = 0;
int wWidth = 0;


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


void drawRect(GLuint texture)
{
	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 point[4][2] = { { x1,y1 },{ x2,y1 },{ x2,y2 },{ x1,y2 } };
	int dir[4][2] = { { 0,0 },{ 1,0 },{ 1,1 },{ 0,1 } };
	glBegin(GL_QUADS);

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

	glDisable(GL_TEXTURE_2D);
}


void drawScene()
{
	glPushMatrix();
	glScalef(8, 6, 1);
	drawRect(texture[0]);
	glPopMatrix();

	glDepthMask(GL_FALSE);

	glPushMatrix();
	glTranslatef(0.0f,-1.0f, 1.0f);
	glScalef(1.8, 2, 0);
	drawRect(texture[1]);
	glPopMatrix();

	glDepthMask(GL_TRUE);
}

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_ALPHA_TEST);
	glAlphaFunc(GL_GREATER, 0.5);
	glEnable(GL_LIGHTING);  //开启光照模式   

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


	unsigned char color[] = { 255,255,255 };
	glGenTextures(2, texture);
	loadTex(0, "1.bmp", texture);
	loadTex(1, "2.bmp", texture,color);

}



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(800, 600);//设置窗口大小         
	int windowHandle = glutCreateWindow("Simple GLUT App");//设置窗口标题           
	glutDisplayFunc(redraw); //注册绘制回调函数         
	glutReshapeFunc(reshape);   //注册重绘回调函数         

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

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


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值