首页 > 代码库 > opengl 反走样 混合 多重采样 blend multisample

opengl 反走样 混合 多重采样 blend multisample

1. 反走样

        在光栅图形显示器上绘制非水平且非垂直的直线或多边形边界时,或多或少会呈现锯齿状或台阶状外观。这是因为直线、多边形、色彩边界等是连续的,而光栅则是由离散的点组成,在光栅显示设备上表现直线、多边形等,必须在离散位置采样。由于采样不充分重建后造成的信息失真,就叫走样(aliasing)。而用于减少或消除这种效果的技术,就称为反走样(antialiasing)。

2. OpenGL反走样的实现

OpengL中的反走样采用的是融合的技术,来实现点、线和图形的边沿以及雾和颜色和纹理的插值运算。OpenGL实现反走样需要满足两个条件,一是启用混合,二是启用针对几何图元的反走样处理。
3. OpenGL混合Blend
混合是什么呢?混合就是把两种颜色混在一起。具体一点,就是把某一像素位置原来的颜色和将要画上去的颜色,通过某种方式混在一起,从而实现特殊的效果。
假设我们需要绘制这样一个场景:透过红色的玻璃去看绿色的物体,那么可以先绘制绿色的物体,再绘制红色玻璃。在绘制红色玻璃的时候,利用“混合”功能,把将要绘制上去的红色和原来的绿色进行混合,于是得到一种新的颜色,看上去就好像玻璃是半透明的。

要使用OpenGL的混合功能,只需要调用:glEnable(GL_BLEND);即可。
要关闭OpenGL的混合功能,只需要调用:glDisable(GL_BLEND);即可。
glBlendFunc(源因子, 目标因子)完成混合方法的定义。
举例来说:
如果设置了glBlendFunc(GL_ONE, GL_ZERO);,则表示完全使用源颜色,完全不使用目标颜色,因此画面效果和不使用混合的时候一致(当然效率可能会低一点点)。如果没有设置源因子和目标因子,则默认情况就是这样的设置。
如果设置了glBlendFunc(GL_ZERO, GL_ONE);,则表示完全不使用源颜色,因此无论你想画什么,最后都不会被画上去了。(但这并不是说这样设置就没有用,有些时候可能有特殊用途)
如果设置了glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);,则表示源颜色乘以自身的alpha 值,目标颜色乘以1.0减去源颜色的alpha值,这样一来,源颜色的alpha值越大,则产生的新颜色中源颜色所占比例就越大,而目标颜色所占比例则减小。这种情况下,我们可以简单的将源颜色的alpha值理解为“不透明度”。这也是混合时最常用的方式。
如果设置了glBlendFunc(GL_ONE, GL_ONE);,则表示完全使用源颜色和目标颜色,最终的颜色实际上就是两种颜色的简单相加。例如红色(1, 0, 0)和绿色(0, 1, 0)相加得到(1, 1, 0),结果为黄色。
注意:
所谓源颜色和目标颜色,是跟绘制的顺序有关的。假如先绘制了一个红色的物体,再在其上绘制绿色的物体。则绿色是源颜色,红色是目标颜色。如果顺序反过来,则红色就是源颜色,绿色才是目标颜色。在绘制时,应该注意顺序,使得绘制的源颜色与设置的源因子对应,目标颜色与设置的目标因子对应。不要被混乱的顺序搞晕了。
 
三维混合的注意事项
 问题
:也许你迫不及待的想要绘制一个三维的带有半透明物体的场景了。但是现在恐怕还不行,还有一点是在进行三维场景的混合时必须注意的,那就是深度缓冲。深度缓冲是这样一段数据,它记录了每一个像素距离观察者有多近。在启用深度缓冲测试的情况下,如果将要绘制的像素比原来的像素更近,则像素将被绘制。否则,像素就会被忽略掉,不进行绘制。这在绘制不透明的物体时非常有用——不管是先绘制近的物体再绘制远的物体,还是先绘制远的物体再绘制近的物体,或者干脆以混乱的顺序进行绘制,最后的显示结果总是近的物体遮住远的物体。然而在你需要实现半透明效果时,发现一切都不是那么美好了。如果你绘制了一个近距离的半透明物体,则它在深度缓冲区内保留了一些信息,使得远处的物体将无法再被绘制出来。虽然半透明的物体仍然半透明,但透过它看到的却不是正确的内容了。
方案:要解决以上问题,需要在绘制半透明物体时将深度缓冲区设置为只读,这样一来,虽然半透明物体被绘制上去了,深度缓冲区还保持在原来的状态。如果再有一个物体出现在半透明物体之后,在不透明物体之前,则它也可以被绘制(因为此时深度缓冲区中记录的是那个不透明物体的深度)。以后再要绘制不透明物体时,只需要再将深度缓冲区设置为可读可写的形式即可。嗯?你问我怎么绘制一个一部分半透明一部分不透明的物体?这个好办,只需要把物体分为两个部分,一部分全是半透明 的,一部分全是不透明的,分别绘制就可以了。
注意: 即使使用了以上技巧,我们仍然不能随心所欲的按照混乱顺序来进行绘制。必须是先绘制不透明的物体,然后绘制透明的物体。否则,假设背景为蓝色,近处一块红色玻璃,中间一个绿色物体。如果先绘制红色半透明玻璃的话,它先和蓝色背景进行混合,则以后绘制中间 的绿色物体时,想单独与红色玻璃混合已经不能实现了。
总结:绘制顺序就是首先绘制所有不透明的物体。如果两个物体都是不透明的,则谁先谁后都没有关系。然后,将深度缓冲区设置为只读。接下来,绘制所有半透明的物体。如果两个物体都是半透明的,则谁先谁后只需要根据自己的意愿(注意了,先绘制的将成为“目标颜色”,后绘制的将成为“源颜色”,所以绘制的顺序将会对结果造成一些影响)。最后,将深度缓冲区设置为可读可写形式。
调用glDepthMask(GL_FALSE);可将深度缓冲区设置为只读形式。调用glDepthMask(GL_TRUE);可将深度缓冲区设置为可读可写形式。
4.OpenGL的反走样函数

void glHint(GLenum target,GLenum hint); 

         hint定义了反走样的方法
          GL_FASTEST 给出最有效的选择 
          GL_NICEST 给出最高质量的选择 
          GL_DONT_CARE 没有选择

          target定义反走样的对象

 

         GL_POINT_SMOOTH_HINT 指定点

            GL_LINE_SMOOTH_HINT 线
            GL_POLYGON_SMOOTH_HINT 多边形的采样质量 
            GL_FOG_HINT 指出雾化计算是按每个象素进行(GL_NICEST),还是按每个顶点进行(GL_FASTEST) 
            GL_PERSPECTIVE_CORRECTION_HINT 指定颜色纹理插值的质量 
            其中GL_PERSPECTIVE_CORRECTION_HINT用以纠正单纯线性插值带来的观察错误。

5.OpenGL的反走样实例1---点的反走样
#include<windows.h>
#include<gl/glut.h>
#include<math.h>
#define x_Screen 800
#define y_Screen    600
#define little 50
#define middle 20
#define large   8
void myBackground()
{
glClearColor(0.0,0.0,0.0,1.0);
glColor3f(1.0,1.0,1.0);
}
 
void myDisplay()
{
glEnable ( GL_DEPTH_TEST );
//如果没有抗锯齿,则点为方形的。如果我们启动抗锯齿设置,则点是一个圆点。
glEnable(GL_POINT_SMOOTH);
glEnable(GL_LINE_SMOOTH);
glHint(GL_POINT_SMOOTH_HINT, GL_NICEST); // Make round points, not square points
glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);  // Antialias the lines
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
 
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
int i;
glBegin(GL_POINTS);
for(i=0;i<little;i++)
glVertex2f(50.0+rand()%x_Screen,50.0+rand()%y_Screen);
glEnd();
glPointSize(2);
glBegin(GL_POINTS);
for(i=0;i<middle;i++)
glVertex2f(50.0+rand()%x_Screen,50.0+rand()%y_Screen);
glEnd();
 
glPointSize(8);
glBegin(GL_POINTS);
for(i=0;i<large;i++)
glVertex2f(50.0+rand()%x_Screen,50.0+rand()%y_Screen);
glEnd();
 
glBegin(GL_POLYGON);
for(i=0;i<64;i++)
glVertex2f(600+50.0*cos((float)i/10) ,500+50.0*sin((float)i/10) );
glEnd();
glLineWidth(3);
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_BLEND);
glEnable(GL_LINE_SMOOTH);
glBegin(GL_LINE_STRIP);
for(i=0;i<19;i++){
glVertex2f(rand()%10+i*70,rand()%50+50.0+(i%2)*80);
 
}
glEnd();
 
glutSwapBuffers();
}
void myChange(int w,int h)
glViewport(0,0,w,h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
 
gluOrtho2D(0.0,x_Screen,0.0,y_Screen);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
 
 
void main()
{
glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGB|GLUT_DEPTH);
glutInitWindowSize(x_Screen,y_Screen);
glutCreateWindow("Star");
glutDisplayFunc(myDisplay);
glutReshapeFunc(myChange);
myBackground();
glutMainLoop();
 
}
6.OpenGL的反走样实例2---线的反走样
#include <GL/glut.h>
#include <stdio.h>
 
static float rotAngle = 0.;
 
void init(void)
{
GLfloat values[2];
glGetFloatv(GL_LINE_WIDTH_GRANULARITY, values);
printf("GL_LINE_WIDTH_GRANULARITY value is %3.1f\n", values[0]);
 
glGetFloatv(GL_LINE_WIDTH_RANGE, values);
printf("GL_LINE_WIDTH_RANGE values are %3.1f %3.1f\n", values[0], values[1]);
 
glEnable(GL_LINE_SMOOTH);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glHint(GL_LINE_SMOOTH_HINT, GL_DONT_CARE);
 
glLineWidth(1.5);
 
glClearColor(0.0, 0.0, 0.0, 0.0);
}
 
void display(void)
{
   glClear(GL_COLOR_BUFFER_BIT);
 
   glColor3f(0.0, 1.0, 0.0);
   glPushMatrix();
   glRotatef(-rotAngle, 0.0, 0.0, 0.1);
   glBegin(GL_LINES);
   glVertex2f(-0.5, 0.5);
   glVertex2f(0.5, -0.5);
   glEnd();
   glPopMatrix();
   
   glColor3f(0.0, 0.0, 1.0);
   glPushMatrix();
   glRotatef(rotAngle, 0.0, 0.0, 0.1);
   glBegin(GL_LINES);
   glVertex2f(0.5, 0.5);
   glVertex2f(-0.5, -0.5);
   glEnd();
   glPopMatrix();
   
   glFlush();
}
 
void reshape(int w, int h)
{
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if (w <= h) 
gluOrtho2D (-1.0, 1.0, -1.0*(GLfloat)h/(GLfloat)w, 1.0*(GLfloat)h/(GLfloat)w);
else 
gluOrtho2D (-1.0*(GLfloat)w/(GLfloat)h, 1.0*(GLfloat)w/(GLfloat)h, -1.0, 1.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
 
void keyboard(unsigned char key, int x, int y)
{
switch (key) 
{
case ‘r‘: case ‘R‘:
rotAngle += 20.;
if (rotAngle >= 360.0) rotAngle = 0.0;
glutPostRedisplay(); 
break;
case 27:
exit(0);
break;
default:
break;
}
}
 
int main(int argc, char** argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
glutInitWindowSize(200, 200);
glutCreateWindow(argv[0]);
init();
glutReshapeFunc(reshape);
glutKeyboardFunc(keyboard);
glutDisplayFunc(display);
glutMainLoop();
return 0;
}

 

------------------------------------------------------------------------------------------------------------------------------------------------------------------

 

1.       使用颜色混合来消除一些锯齿, 主要针对点和线以及不相互重叠的多边形的反锯齿。

反锯齿设置代码如下:

glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

glEnable(GL_BLEND);

glEnable(GL_POINT_SMOOTH);

glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);

glEnable(GL_LINE_SMOOTH);

glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);

glEnable(GL_POLYGON_SMOOTH);

glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST);

取消反锯齿代码如下:

glDisable(GL_BLEND);

glDisable(GL_LINE_SMOOTH);

glDisable(GL_POINT_SMOOTH);

glDisable(GL_POLYGON_SMOOTH);

2.       多重采样(Multisample)

并不是所有的平台都支持通过颜色混合来消除多边形锯齿,此外,多边形混合还有顺序的问题,使用起来不方便。OpenGL引入了多重采样来解决多边形锯齿的问题,并增加了一个包含颜色、尝试以及模块缓存值的帧缓存。开启多重采样功能的代码如下:

//申请一个采用了双重缓存,包含颜色,深度的帧缓存和多重采样。

glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH | GLUT_MULTISAMPLE);

   glEnable(GL_MULTISAMPLE);//开启多重缓存

   glDisable(GL_MULTISAMPLE);//关闭多重缓存

  

   注: 多重采样和混合不能同时开启, 这两种方法只能互斥使用。即使用任何一个方法前需要禁用另一个方法。

opengl 反走样 混合 多重采样 blend multisample