深入了解OpenGL——颜色混合

时间:2021-03-02 17:35:05
 

大家好。这一讲,我们讲介绍OpenGL的颜色混合模式。

其实颜色混合用到的场合很多,比如多张图片的合成,动画游戏中的一些画面特效等都可以通过颜色混合进行实现。最常用的混合方式就是实现物体与背景的半透明效果。另外,在制作2D游戏时颜色混合可以用来通过制作目标物体的蒙板实现移动。通过蒙板来消除旧位置的物体对象可以不必重绘当前整帧内容,而仅仅是发生变化的那些物体。

为了各位从事iPhone开发的考虑。后面的代码例子对OpenGL API的使用都会用OpenGL2.1与OpenGL ES1.1相互兼容的接口。

首先介绍一下OpenGL对源对象和目标对象进行颜色混合的实现。
这里,源对象是指你将要绘制的对象;目标对象是指已在帧缓存中的颜色。比如调用glClear(GL_COLOR_BUFFER_BIT);后留在帧缓存中的颜色。
在进行计算时,源和目标的混合都是在绘制源对象时进行计算的,在绘制对象以外的帧缓存像素不会受任何影响。

为了方便颜色混合,我们往往采用RGBA这种颜色模式。其中RGB表示色彩分量,而A就是混合因子(blend factor)。A,我们在图形、图像处理中常常表示为:alpha,它在图像处理中常用作为透明系数。
我们指定了源和目标的混合因子后,OpenGL会对绘制对象的最终颜色做如下计算:
设:源对象的某个顶点的颜色为(Rs, Gs, Bs, As)
       目的对象对应此源对象顶点的颜色为(Rd, Gd, Bd, Ad)
       源混合因子为:(Sr, Sg, Sb, Sa)
      目的混合因子为:(Dr, Dg, Db, Da)
那么,该顶点最终目标颜色为:
(Rs * Sr  <op>  Rd * Dr,  Gs * Sg  <op>  Gd * Dg,  Bs * Sb  <op>  Bd * Db,  As * Sa  <op>  Ad * Da)
其中,<op>可以是加法(+),减法(-), 逆向减法,最小值,最大值或按位逻辑操作,并且其优先级小于乘法(*)。

下面,我们介绍相关的OpenGL接口。
首先是开启混合,使用glEnable(GL_BLEND);即可。

然后我们使用glBlendEquation()来指定混合操作,参数可以是:GL_FUNC_ADD, GL_FUNC_SUBTRACT, GL_FUNC_REVERSE_SUBTRACT, GL_MIN, GL_MAX。
但这里要注意的是,OpenGL ES1.1没有glBlendEquation接口,因此只能做加法操作。

glBlendFunc()接口用于指定源混合因子与目标混合因子。
参数请参考红宝石书中文版的第140页,表6-1。

下面给出示例代码:

////  MyView.m//  OpenGLTest////  Created by Zenny Chen on 4/25/10.//  Copyright 2010 GreenGames Studio. All rights reserved.//  P44#import "MyView.h"#include <OpenGL/OpenGL.h>#include <math.h>@implementation MyView- (id)initWithFrame:(NSRect)frame {    self = [super initWithFrame:frame];    if (self) {        // Initialization code here.    }    return self;}// Destination: a rectanglestatic c*****t struct VertexInfo{    GLfloat vertices[3];}vertexList[] = {    {-0.5f, 0.5f, -1.0f},    {-0.5f, -0.5f, -1.0f},    {0.5f, 0.5f, -1.0f},    {0.5f, -0.5f, -1.0f}};- (void)prepareOpenGL{    glEnable(GL_CULL_FACE);    glEnable(GL_BLEND);        glEnableClientState(GL_VERTEX_ARRAY);        glVertexPointer(3, GL_FLOAT, 0, vertexList);        glFrontFace(GL_CCW);    glCullFace(GL_BACK);        glShadeModel(GL_SMOOTH);        // Set Background color(frame buffer color)    glClearColor(1.0, 0.0, 0.0, 1.0);        glViewport(0, 0, 320, 320);        glMatrixMode(GL_PROJECTION);    glLoadIdentity();    glOrtho(-1.0, 1.0, -1.0, 1.0, 1.0, 5.0);        glMatrixMode(GL_MODELVIEW);    glLoadIdentity();        // destination color    glColor4f(1.0f, 1.0f, 1.0f, 0.3f);        glBlendEquation(GL_FUNC_ADD);        glBlendFunc(GL_SRC_ALPHA , GL_ONE_MINUS_SRC_ALPHA);}- (void)drawRect:(NSRect)dirtyRect {        // Drawing code here.    glClear(GL_COLOR_BUFFER_BIT);        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);        glFlush();}@end


上述代码第67行的glBlendFunc(GL_SRC_ALPHA , GL_ONE_MINUS_SRC_ALPHA);用于指定:
源混合因子:(As, As, As, As),这里是(0.3, 0.3, 0.3, 0.3)
目标混合因子则是:(1, 1, 1, 1) - (As, As, As, As),结果是(0.7, 0.7, 0.7, 0.7)。
这里我们能够看到,为目标和源指定混合因子可以是源、目标的任一颜色分量或alpha因子,或是[0, 1]范围内的补。

对于上述代码,我们可以用等价的方式实现。
已知,目的像素颜色值为:(1.0, 0.0, 0.0, 1.0);源像素颜色值为:(1.0f, 1.0f, 1.0f, 0.3f)。
目的混合因子为:(0.7, 0.7, 0.7, 0.7);源混合因子为:(0.3, 0.3, 0.3, 0.3)

OK。我们可以直接把最终目标像素颜色值给算出来——(1.0 * 0.3 + 1.0 * 0.7,  1.0 * 0.3 + 0.0 * 0.7,  1.0 * 0.3 + 0.0 * 0.7,  0.3 * 0.3 + 1.0 * 0.7) = 
(1.0,  0.3,  0.3,  0.79)。

因此,我们可以将67行的glBlendFunc(GL_SRC_ALPHA , GL_ONE_MINUS_SRC_ALPHA);给注释掉,然后将63行的glColor4f(1.0f, 1.0f, 1.0f, 0.3f);改为:
glColor4f(1.0f,  0.3f,  0.3f,  0.79f);能够获得相同的效果。

我们上面讲述了如何用glBlendFunc函数来做颜色混合。但是这会带来一个问题,当我们进行了颜色混合后,最终目标颜色分量A同时也被改变了——原来A是0.3,混合后变成了0.79。
下面我们将引入另一个混合接口——glBlendFuncSeparate用于分别指定源和目标的颜色(RGB)分量和A(alpha)分量。下面给出该函数原型:

voidglBlendFuncSeparate(GLenum srcRGB,
GLenum dstRGB,
GLenum srcAlpha,
GLenum dstAlpha);


如果我们要将1楼代码中最终目标颜色的alpha分量值与混合前保持一致,可以将第67行(glBlendFunc那行)替换为:

glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);


上面将源alpha混合因子设置为1,而将目的alpha因子设为0,这样做混合计算——As * 1.0 + Ad * 0.0后,结果为As,与原来的源alpha值保持一致。

另外,这里要注意的是OpenGL ES1.1没有glBlendFuncSeparate这个接口,而OpenGL ES2.0才开始支持这个接口。


我们在制作游戏过程中会碰到这么种情况。比如有一层玻璃,然后后面有一些物体。
我们以前讨论过深度缓存问题,在光照这一讲中。深度缓存能够帮助我们把挡在前面物体之后的物体都裁剪调。那么对于同时存在不透明物体和透明物体的遮挡该如何处理呢?
我们首先要记住以下两种情况:
1、如果不透明物体在前,透明物体在后,那么不透明物体会把透明物体完全遮挡掉;
2、如果透明物体在前,不透明物体在后,那么透明物体与不透明物体做颜色混合。

此时,我们将通过开关混合、以及深度测试功能来完成这个任务。
下面先介绍下保持深度缓存的函数——

?

voidglDepthMask( GLboolean flag )

flag的值为GL_TRUE和GL_FALSE。
如果用GL_TRUE来调用这个函数,那么深度缓存将会把后面绘制的顶点记录到深度缓存内;如果用GL_FALSE来调用此函数,那么深度缓存将不会记录后面绘制的顶点,而保持原来的记录。

由此,我们的绘制顺序为:
1、首先,关闭GL_BLEND功能以及打开glDepthMask,绘制所有不透明的物体;
2、打开GL_BLEND功能以及关闭glDepthMask,绘制所有透明物体。
这样,前面的透明物体将不会完全遮挡后面的不透明物体。
下面是示例代码。这个代码首先绘制白色矩形,作为背景。然后绘制前景红色矩形。该矩形是不透明的,因此会把后面白色背景完全遮盖掉。
最后绘制一个前景蓝色矩形。该矩形是透明的,因此将与白色背景做颜色混合。

//
// MyView.m
// OpenGLTest
//
// Created by Zenny Chen on 4/25/10.
// Copyright 2010 GreenGames Studio. All rights reserved.
// P44

#import "MyView.h"

#include <OpenGL/OpenGL.h>
#include <math.h>

@implementation MyView
- (id)initWithFrame:(NSRect)frame {
self= [superinitWithFrame:frame];
if(self) {
// Initialization code here.
}
returnself;
}
staticc*****t structVertexInfo
{
GLfloat vertices[3];
}vertexList[] = {
// background rectangle
{-0.8f, 0.8f, -2.0f},
{-0.8f, -0.8f, -2.0f},
{0.8f, 0.8f, -2.0f},
{0.8f, -0.8f, -2.0f},

// rectangle hidden
{-0.5f, 0.5f, -1.0f},
{-0.5f, -0.5f, -1.0f},
{0.0f, 0.5f, -1.0f},
{0.0f, -0.5f, -1.0f},

// rectangle blended
{0.0f, 0.5f, -1.0f},
{0.0f, -0.5f, -1.0f},
{0.5f, 0.5f, -1.0f},
{0.5f, -0.5f, -1.0f}
};
- (void)prepareOpenGL
{
glEnable(GL_CULL_FACE);
glEnable(GL_DEPTH_TEST);

glEnableClientState(GL_VERTEX_ARRAY);

glVertexPointer(3, GL_FLOAT, 0, vertexList);

glFrontFace(GL_CCW);
glCullFace(GL_BACK);

glShadeModel(GL_SMOOTH);

glClearColor(0.4, 0.4, 0.4, 1.0);

glViewport(0, 0, 320, 320);

glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(-1.0, 1.0, -1.0, 1.0, 1.0, 5.0);

glMatrixMode(GL_MODELVIEW);
glLoadIdentity();

glBlendEquation(GL_FUNC_ADD);
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);
}
- (void)drawRect:(NSRect)dirtyRect {

// Drawing code here.
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glDisable(GL_BLEND);
glDepthMask(GL_TRUE);

// Draw bakcgound rectangle
glColor4f(1.0f, 1.0f, 1.0f, 1.0f); // white
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

// Draw hidden rectangle
glColor4f(1.0f, 0.0f, 0.0f, 0.3f); // red
glDrawArrays(GL_TRIANGLE_STRIP, 4, 4);

glEnable(GL_BLEND);
glDepthMask(GL_FALSE);

// Draw blended rectangle
glColor4f(0.0f, 0.0f, 1.0f, 0.3f); // blue
glDrawArrays(GL_TRIANGLE_STRIP, 8, 4);

glFlush();
}
@end
取自: http://www.cocoachina.com/bbs/read.php?tid=26284&page=1