OpenGL中alpha测试GL_ALPHA_TEST

我们知道像素的Alpha值可以用于混合操作。其实Alpha值还有一个用途,这就是Alpha测试。当每个像素即将绘制时,如果启动了Alpha测试,OpenGL会检查像素的Alpha值,只有Alpha值满足条件的像素才会进行绘制(严格的说,满足条件的像素会通过本项测试,进行下一种测试,只有所有测试都通过,才能进行绘制),不满足条件的则不进行绘制。这个“条件”可以是:始终通过(默认情况)、始终不通过、大于设定值则通过、小于设定值则通过、等于设定值则通过、大于等于设定值则通过、小于等于设定值则通过、不等于设定值则通过。
如果我们需要绘制一幅图片,而这幅图片的某些部分又是透明的(想象一下,你先绘制一幅相片,然后绘制一个相框,则相框这幅图片有很多地方都是透明的,这样就可以透过相框看到下面的照片),这时可以使用Alpha测试。将图片中所有需要透明的地方的Alpha值设置为0.0,不需要透明的地方Alpha值设置为1.0,然后设置Alpha测试的通过条件为:“大于0.5则通过”,这样便能达到目的。当然也可以设置需要透明的地方Alpha值为1.0,不需要透明的地方Alpha值设置为0.0,然后设置条件为“小于0.5则通过”。Alpha测试的设置方式往往不只一种,可以根据个人喜好和实际情况需要进行选择。

可以通过下面的代码来启用或禁用Alpha测试:

glEnable(GL_ALPHA_TEST);   // 启用Alpha测试
glDisable(GL_ALPHA_TEST); // 禁用Alpha测试


可以通过下面的代码来设置Alpha测试条件为“大于0.5则通过”:

glAlphaFunc(GL_GREATER, 0.5f);


该函数的第二个参数表示设定值,用于进行比较。第一个参数是比较方式,除了GL_LESS(小于则通过)外,还可以选择:
GL_ALWAYS(始终通过),
GL_NEVER(始终不通过),
GL_LESS(小于则通过),
GL_LEQUAL(小于等于则通过),
GL_EQUAL(等于则通过),
GL_GEQUAL(大于等于则通过),
GL_NOTEQUAL(不等于则通过)。
现在我们来看一个实际例子。一幅照片图片,一幅相框图片,如何将它们组合在一起呢?为了简单起见,我们使用前面两课一直使用的24位BMP文件来作为图片格式。(因为发布到网络上,为了节约容量,我所发布的是JPG格式。大家下载后可以用Windows XP自带的画图工具打开,并另存为24位BMP格式)

注:第一幅图片是著名网络游戏《魔兽世界》的一幅桌面背景,用在这里希望没有涉及版权问题。如果有什么不妥,请及时指出,我会立即更换。

在24位的BMP文件格式中,BGR三种颜色各占8位,没有保存Alpha值,因此无法直接使用Alpha测试。注意到相框那幅图片中,所有需要透明的位置都是白色,所以我们在程序中设置所有白色(或很接近白色)的像素Alpha值为0.0,设置其它像素Alpha值为1.0,然后设置Alpha测试的条件为“大于0.5则通过”即可。这种使用某种特殊颜色来代表透明颜色的技术,有时又被成为Color Key技术。
利用前面第11课的一段代码,将图片读取为纹理,然后利用下面这个函数来设置“当前纹理”中每一个像素的Alpha值。

/* 将当前纹理BGR格式转换为BGRA格式
* 纹理中像素的RGB值如果与指定rgb相差不超过absolute,则将Alpha设置为0.0,否则设置为1.0
*/
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("pic.bmp");
         texWindow   = load_texture("window.bmp");
         glBindTexture(GL_TEXTURE_2D, texWindow);
         texture_colorkey(255, 255, 255, 10);

         glEnable(GL_TEXTURE_2D);

         initialized = 1;
     }

     // 清除屏幕
     glClear(GL_COLOR_BUFFER_BIT);

     // 绘制相片,此时不需要进行Alpha测试,所有的像素都进行绘制
     glBindTexture(GL_TEXTURE_2D, texPicture);
     glDisable(GL_ALPHA_TEST);
     glBegin(GL_QUADS);
         glTexCoord2f(0, 0);      glVertex2f(-1.0f, -1.0f);
         glTexCoord2f(0, 1);      glVertex2f(-1.0f,   1.0f);
         glTexCoord2f(1, 1);      glVertex2f( 1.0f,   1.0f);
         glTexCoord2f(1, 0);      glVertex2f( 1.0f, -1.0f);
     glEnd();

     // 绘制相框,此时进行Alpha测试,只绘制不透明部分的像素
     glBindTexture(GL_TEXTURE_2D, texWindow);
     glEnable(GL_ALPHA_TEST);
     glAlphaFunc(GL_GREATER, 0.5f);
     glBegin(GL_QUADS);
         glTexCoord2f(0, 0);      glVertex2f(-1.0f, -1.0f);
         glTexCoord2f(0, 1);      glVertex2f(-1.0f,   1.0f);
         glTexCoord2f(1, 1);      glVertex2f( 1.0f,   1.0f);
         glTexCoord2f(1, 0);      glVertex2f( 1.0f, -1.0f);
     glEnd();

     // 交换缓冲
     glutSwapBuffers();
}
其中:load_texture函数是从第11课中照搬过来的(该函数还使用了一个power_of_two函数,一个BMP_Header_Length常数,同样照搬),无需进行修改。main函数跟其它课程的基本相同,不再重复。
程序运行后,会发现相框与相片的衔接有些不自然,这是因为相框某些边缘部分虽然肉眼看上去是白色,但其实RGB值与纯白色相差并不少,因此程序计算其Alpha值时认为其不需要透明。解决办法是仔细处理相框中的每个像素,在需要透明的地方涂上纯白色,这也许是一件很需要耐心的工作。
大家可能会想:前面我们学习过混合操作,混合可以实现半透明,自然也可以通过设定实现全透明。也就是说,Alpha测试可以实现的效果几乎都可以通过OpenGL混合功能来实现。那么为什么还需要一个Alpha测试呢?答案就是,这与性能相关。Alpha测试只要简单的比较大小就可以得到最终结果,而混合操作一般需要进行乘法运算,性能有所下降。另外,OpenGL测试的顺序是:剪裁测试、Alpha测试、模板测试、深度测试。如果某项测试不通过,则不会进行下一步,而只有所有测试都通过的情况下才会执行混合操作。因此,在使用Alpha测试的情况下,透明的像素就不需要经过模板测试和深度测试了;而如果使用混合操作,即使透明的像素也需要进行模板测试和深度测试,性能会有所下降。还有一点:对于那些“透明”的像素来说,如果使用Alpha测试,则“透明”的像素不会通过测试,因此像素的深度值不会被修改;而使用混合操作时,虽然像素的颜色没有被修改,但它的深度值则有可能被修改掉了。
因此,如果所有的像素都是“透明”或“不透明”,没有“半透明”时,应该尽量采用Alpha测试而不是采用混合操作。当需要绘制半透明像素时,才采用混合操作。

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值