Alpha测试测试就是测试每一个像素的Alpha值是否满足某一个特定的条件,如果满足,则该像素会被绘制,如果不满足则不绘制,跟深度测试的机制是一样的,只不过深度测试考察的是像素的“深度”属性,Alpha测试考察的是像素的“Alpha”属性。
利用Alpha测试的这一特性,可以模拟两幅图像叠加,但是又要求前景图像有一部分是透明的场景,这时候可以把要求透明的区域内的每一个像素的Alpha值设置为0,在之后的Alpha测试的通过条件设置为大于0.5才通过,这样Alpha值为0的像素就不会被绘制,达到透明的效果。
通过glEnable(GL_ALPHA_TEST)启用Alpha测试,通过GLDisable(GL_ALPHA_TEST)禁用Alpha测试。
设置Alpha测试的通过条件的函数是glAlphaFunc(GLenum_func,GLclampf ref),func是参数的比较方式,ref是参数,可以取的参数以及含义如下:
- GL_ALWAYS(始终通过)
- GL_NEVER(始终不通过)
- GL_LESS(小于则通过)
- GL_LEQUAL(小于等于则通过)
- GL_EQUAL(等于则通过)
- GL_GEQUAL(大于等于则通过)
- GL_NOTEQUAL(不等于则通过)
例如有如下两幅图像需要叠加,第一个是前景图像,第二个是背景图像,场景要求是叠加之后的效果是从窗户里看过去的效果。
背景:
前景:
可以利用Alpha测试,设置前景图像中白色部分的Alpah值为0,其他部分的Alpha值为1,在测试条件中选用GL_GREATER(大于则通过):
#define WindowWidth 400
#define WindowHeight 400
#define WindowTitle "OpenGLAlpha测试"
#define BMP_Header_Length 54
#include <gl/glut.h>
#include <stdio.h>
#include <stdlib.h>
GLuint texGround;
GLuint texWall;
int power_of_two(int n)
{
if( n <= 0 )
return 0;
return (n & (n-1)) == 0;
}
/* 函数load_texture
* 读取一个BMP文件作为纹理
* 如果失败,返回0,如果成功,返回纹理编号
*/
GLuint load_texture(const char* file_name)
{
GLint width, height, total_bytes;
GLubyte* pixels = 0;
GLint last_texture_ID;
GLuint texture_ID = 0;
// 打开文件,如果失败,返回
FILE* pFile = fopen(file_name, "rb");
if( pFile == 0 )
return 0;
// 读取文件中图象的宽度和高度
fseek(pFile, 0x0012, SEEK_SET);
fread(&width, 4, 1, pFile);
fread(&height, 4, 1, pFile);
fseek(pFile, BMP_Header_Length, SEEK_SET);
// 计算每行像素所占字节数,并根据此数据计算总像素字节数
{
GLint line_bytes = width * 3;
while( line_bytes % 4 != 0 )
++line_bytes;
total_bytes = line_bytes * height;
}
// 根据总像素字节数分配内存
pixels = (GLubyte*)malloc(total_bytes);
if( pixels == 0 )
{
fclose(pFile);
return 0;
}
// 读取像素数据
if( fread(pixels, total_bytes, 1, pFile) <= 0 )
{
free(pixels);
fclose(pFile);
return 0;
}
{
GLint max;
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max);
if( !power_of_two(width)
|| !power_of_two(height)
|| width > max
|| height > max )
{
const GLint new_width = 1024; //修改为2的整数次幂
const GLint new_height = 1024; // 规定缩放后新的大小为边长的正方形
GLint new_line_bytes, new_total_bytes;
GLubyte* new_pixels = 0;
// 计算每行需要的字节数和总字节数
new_line_bytes = new_width * 3;
while( new_line_bytes % 4 != 0 )
++new_line_bytes;
new_total_bytes = new_line_bytes * new_height;
// 分配内存
new_pixels = (GLubyte*)malloc(new_total_bytes);
if( new_pixels == 0 )
{
free(pixels);
fclose(pFile);
return 0;
}
// 进行像素缩放
gluScaleImage(GL_RGB,
width, height, GL_UNSIGNED_BYTE, pixels,
new_width, new_height, GL_UNSIGNED_BYTE, new_pixels);
// 释放原来的像素数据,把pixels指向新的像素数据,并重新设置width和height
free(pixels);
pixels = new_pixels;
width = new_width;
height = new_height;
}
}
// 分配一个新的纹理编号
glGenTextures(1, &texture_ID);
if( texture_ID == 0 )
{
free(pixels);
fclose(pFile);
return 0;
}
// 绑定新的纹理,载入纹理并设置纹理参数
// 在绑定前,先获得原来绑定的纹理编号,以便在最后进行恢复
glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture_ID);
glBindTexture(GL_TEXTURE_2D, texture_ID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0,
GL_BGR_EXT, GL_UNSIGNED_BYTE, pixels);
glBindTexture(GL_TEXTURE_2D, last_texture_ID);
// 之前为pixels分配的内存可在使用glTexImage2D以后释放
// 因为此时像素数据已经被OpenGL另行保存了一份(可能被保存到专门的图形硬件中)
free(pixels);
return texture_ID;
}
void texture_colorkey(GLubyte r, GLubyte g, GLubyte b, GLubyte absolute)
{
GLint width, height;
GLubyte* pixels = 0;
// 获得纹理的大小信息
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width);
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height);
// 分配空间并获得纹理像素
pixels = (GLubyte*)malloc(width*height*4);
if( pixels == 0 )
return;
glGetTexImage(GL_TEXTURE_2D, 0, GL_BGRA_EXT, GL_UNSIGNED_BYTE, pixels);
// 修改像素中的Alpha值
// 其中pixels[i*4], pixels[i*4+1], pixels[i*4+2], pixels[i*4+3]
// 分别表示第i个像素的蓝、绿、红、Alpha四种分量,0表示最小,255表示最大
{
GLint i;
GLint count = width * height;
for(i=0; i<count; ++i)
{
if( abs(pixels[i*4] - b) <= absolute
&& abs(pixels[i*4+1] - g) <= absolute
&& abs(pixels[i*4+2] - r) <= absolute )
pixels[i*4+3] = 0;
else
pixels[i*4+3] = 255;
}
}
// 将修改后的像素重新设置到纹理中,释放内存
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0,
GL_BGRA_EXT, GL_UNSIGNED_BYTE, pixels);
free(pixels);
}
void display(void)
{
static int initialized = 0;
static GLuint texWindow = 0;
static GLuint texPicture = 0;
// 执行初始化操作,包括:读取背景,读取前景,将相框由BGR颜色转换为BGRA,启用二维纹理
if( !initialized )
{
texPicture = load_texture("backImage.bmp");
texWindow = load_texture("frontImage.bmp");
glBindTexture(GL_TEXTURE_2D, texWindow);
texture_colorkey(255, 255, 255, 10);
glEnable(GL_TEXTURE_2D);
initialized = 1;
}
// 清除屏幕
glClear(GL_COLOR_BUFFER_BIT);
// 绘制背景,此时不需要进行Alpha测试,所有的像素都进行绘制
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(65,1,2,50);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(0,0,4.5,0,0,0,0,1,0);
glBindTexture(GL_TEXTURE_2D, texPicture);
glDisable(GL_ALPHA_TEST);
glBegin(GL_QUADS);
glTexCoord2f(0, 0); glVertex3f(-6.0f, -6.0f,-3);
glTexCoord2f(0, 1); glVertex3f(-6.0f, 6.0f,-3);
glTexCoord2f(1, 1); glVertex3f( 6.0f, 6.0f,-3);
glTexCoord2f(1, 0); glVertex3f( 6.0f, -6.0f,-3);
glEnd();
// 绘制前景,此时进行Alpha测试,只绘制不透明部分的像素
glBindTexture(GL_TEXTURE_2D, texWindow);
glEnable(GL_ALPHA_TEST);
glAlphaFunc(GL_GREATER, 0.5f);
glBegin(GL_QUADS);
glTexCoord2f(0, 0); glVertex3f(-3.0f, -3.0f,0);
glTexCoord2f(0, 1); glVertex3f(-3.0f, 3.0f,0);
glTexCoord2f(1, 1); glVertex3f( 3.0f, 3.0f,0);
glTexCoord2f(1, 0); glVertex3f( 3.0f, -3.0f,0);
glEnd();
// 交换缓冲
glutSwapBuffers();
}
int main(int argc, char* argv[])
{
// GLUT初始化
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
glutInitWindowPosition(100, 100);
glutInitWindowSize(WindowWidth, WindowHeight);
glutCreateWindow(WindowTitle);
glutDisplayFunc(&display);
// 开始显示
glutMainLoop();
return 0;
}
实现效果,左侧视角:
正视视角:
右侧视角:
剪裁测试:
所谓剪裁测试就是限定一个矩形绘制窗口,只有在这个窗口范围内的像素才会被绘制,窗口外的像素被忽略。使用glEnable(GL_SCISSOR_TEST)开启剪裁测试,使用GLDisable(GL_SCISSOR_TEST)关闭。使用
glScissor (GLint x, GLint y, GLsizei width, GLsizei height)设定剪裁窗口。
在上例代码的基础上,只需要在显示部分函数display清屏之后,加入以下两句:
glEnable(GL_SCISSOR_TEST);
glScissor(0,0,300,300);
就可以实现把显示画面划定在以左下角为起点的300*300的剪裁窗口中,显示效果如下: