OpenGL(十三) Alpha测试、剪裁测试


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的剪裁窗口中,显示效果如下:



原文地址:https://www.cnblogs.com/mtcnn/p/9411914.html