Qt下的OpenGL 编程(6)混合、雾、抗锯齿

来源:互联网 发布:软件的数据接口 编辑:程序博客网 时间:2024/06/05 03:14
一、提要
    混合:对颜色进行混合,实现像“半透明”一样的效果。
    抗锯齿:使直线和多边形的锯齿状边缘变得平滑。
    雾:创建具有大气效果的场景。
二、混合
    混合的最终效果是使场景看上去像是半透明的。
    一个通俗的方式来解释混合的话,例如透过绿色的玻璃观察一个物体,我们看到的颜色部分来自于玻璃的绿色,部分来自于
物体的颜色。这两种颜色所占的比例取决于玻璃的传比属性:如果玻璃传播撞击它的光线的80%(即alpha值是0.2),我们看到
的颜色就是玻璃颜色的20%加上玻璃后面那个物体颜色的80%。
    融合的公式 
    (Rs Sr + Rd Dr, Gs Sg + Gd Dg, Bs Sb + Bd Db, As Sa + Ad Da)
  
  OpenGL按照上面的公式计算这两个象素的融合结果。小写的s和r分别代表源象素和目标象素。大写的S和D则是相应的融合因子,即透明度。这些决定了您如何对这些象素融合。绝大多数情况下,各颜色通道的alpha融合值大小相同,这样对源象素就有 (As, As, As, As),目标象素则有1, 1, 1, 1) - (As, As, As, As)。上面的公式就成了下面的模样:
    (Rs As + Rd (1 - As), Gs As + Gd (1 - As), Bs As + Bs (1 - As), As As + Ad (1 - As))
    这个公式会生成透明/半透明的效果。
    混合因子的值位于[0,1]之间。在源颜色和目标颜色进行混合之后,它们的值将进行截取限制在[0,1]的范围之内。
    在进行三维 混合的时候,多边形的绘图顺序将极大地 影响最终的混合效果。在绘制三维的半透明物体时,根据从前向后还是从后向前绘制多个多边形,最终结果可能大相径庭。正确的混色过程应该是先绘制全部的场景之后再绘制透明的图形。并且要按照与深度缓存相反的次序来绘制(先画最远的物体)。在深度缓存启用时,您应该将透明图形按照深度进行排序,并在全部场景绘制完毕之后再绘制这些透明物体。否则您将得到不正确的结果。
    
    下面来实现一下混合纹理的木箱。
     首先在nehewiget.h中加入变量isBlend和几个函数:
     public:
          //判断是否启用混合
         bool isBlending();
         //启用混合
         void enableBlend();
         //关闭混合
         void disableBLend();
     protected:
          bool isBlend;
        函数实现:
        void NeHeWidget::enableBlend()
        {
            this->isBlend=true;
            glEnable(GL_BLEND);// 打开混合
            glDisable(GL_DEPTH_TEST);// 关闭深度测试
              updateGL();
        }
        void NeHeWidget::disableBLend()
        {
            this->isBlend=false;
            glDisable(GL_BLEND);// 关闭混合
            glEnable(GL_DEPTH_TEST);// 打开深度测试
              updateGL();
        }
        bool NeHeWidget::isBlending()
        {
            return this->isBlend;
        }
在initializeGL()中加入混合设置:
// 全亮度, 50% Alpha 混合
     glColor4f(1.0f,1.0f,1.0f,0.5f);
// 基于源象素alpha通道值的半透明混合函数
     glBlendFunc(GL_SRC_ALPHA,GL_ONE);
加入键盘事件,按键B来控制是否混合:
case Qt::Key_B:
    if (!neheWidget->isBlending())   neheWidget->enableBlend();
   else neheWidget->disableBLend();
    break;
最后运行的结果就像这样:
三、雾
        有些情况下,添加一些雾可以是图像更加逼真,比如模拟战争,污染,飞行等等。
        插入一下颜色模式的概念:
        RGBA显示模式
        在RGBA模式中,硬件分配一定数量的位面给R、G、B和A成分(每个成分的数量不一定一样)如图所示。R、G、B的值通常以整型存储,而不是浮点数,并且它们被扩展成可以方便存储和获取的位数。例如,在一个R成分有8位的系统中,从0到255的成分就可以存储,这样,0,1,2,……,255就对应R的值0/255 = 0.0, 1/255, 2/255, ..., 255/255 = 1.0。无论位面的数量是多少,0.0总是最小的亮度值,1.0总是表示最大的亮度值。
         颜色索引显示模式
         在颜色索引模式下,OpenGL使用一个颜色表(或查找表),就像用一个调色板来调出场景需要的各种颜色。画家的调色板提供了很多小格子用于调色;类似的,计算机的颜色表提供很多索引,供RGB值进行混合,如下图所示。
           模式的选择
           选择RGBA模式还是颜色索引模式
           你要基于可用的硬件和应用程序的需要来决定使用哪种颜色模式。对大多数系统而言,RGBA模式比颜色索引模式能够同时表示更多的颜色。同样,对于大多数效果,例如阴影、光照、纹理映射、雾化,RGBA模式比颜色索引模式提供更加丰富的功能。
      下面我们继续来实现雾的效果.
     首先在neheWidget.h 中添加一个变量,用于所选择的雾的类型的索引,然后还需要一个函数来变换雾的类型。
     protected:
       GLuint fogFilter;
    public:
        void changeFog();
    在neheWidget.cpp中添加雾的参数:
    GLuint fogMode[3] = { GL_EXP, GL_EXP2, GL_LINEAR };
    GLfloat fogColor[4] = { 0.5, 0.5, 0.5, 1.0 };
     变量fogMode,用来保存3种有关雾的类型:GL_EXP,GL_EXP2,GL_LINEAR。这个变量将在代码的开头声明。变量fogColor会保存任何您想要的雾的颜色。
      关于雾的类型
GL_EXP:简单渲染在屏幕上显示的雾的模式。它无法给予我们非常漂亮的雾的效果,但是却可以在古老的电脑上工作的很好。
GL_EXP2:比1提高了一点,将渲染全屏幕的雾,然而她会给予场景更深的效果。
GL_LINEAR:这是最好的雾的渲染模式,对象在雾中消隐的很好。
       接着在cpp中初始化fogFileter,添加函数实现。
      fogFilter = 0;
void NeHeWidget::changeFog()
{
    fogFilter+=1;
    if (fogFilter>2) fogFilter=0;
    glFogi( GL_FOG_MODE, fogMode[fogFilter] );
    updateGL();
}
在initializeGL()中对雾进行设置:
//选定雾的类型
  glFogi( GL_FOG_MODE, fogMode[fogFilter] );
 //设置雾的颜色
 glFogfv( GL_FOG_COLOR, fogColor );
 //设置雾的浓度
  glFogf( GL_FOG_DENSITY, 0.35 );
  //确定雾的渲染方式
 glHint( GL_FOG_HINT, GL_DONT_CARE );
 //确定了雾的开始初离屏幕有多近
 glFogf( GL_FOG_START, 1.0 );
    //确定了雾的开始初离屏幕有多
 glFogf( GL_FOG_END, 5.0 );
      //开启雾
  glEnable( GL_FOG );
      关于雾的渲染方式有三个不同的值:
GK_DONT_CARE:让OPENGL自己来确定雾的渲染方式,每顶点或是每像素。

GL_NICEST:对每一像素进行雾的渲染,它看起来是极棒的。
GL_FASTEST:对每一顶点进行雾的渲染,它速度较快,效果就一般了。


    最后,在mainwindow.cpp中添加键盘事件,来改变雾的效果。
        case Qt::Key_G:
           neheWidget->changeFog();
           break;
        最后的效果像这样:
        感觉雾的实现和灯光的实现有些类似,都是先设置参数,然后添加到场景中去。
 实现的原理还是有点复杂,大家有兴趣的话可以翻阅相应的资料。
四、抗锯齿
        我们在绘制一条1像素的直线的时候,通常它并不是一条直线,
有些地方甚至断掉了,这个就是锯齿,或是走样。抗锯齿又称反走样,在游戏中的效果调整中通常
有多少倍抗锯齿,用的就是这种技术,它可以使图形
的边缘显得更加平滑一些,增加了逼真感,但代价是损失了性能。
        OpenGL中开启抗锯齿有两个步骤:
        启用抗锯齿
        还是以glEnable来启用抗锯齿,可以根据不同图形进行处理
        GL_POINT_SMOOTH 
        GL_LINE_SMOOTH 线
        GL_POLYGON_SMOOTH 多边形
        抗锯齿质量
        当然效果越好,那么计算机速度就越慢,即有一个参数设置
        glHint用于对点,线,多边形的抗锯齿程度进行设置
        GL_DONT_CARE 放弃,应该是系统默认吧
        GL_FASTEST 速度优先
        GL_NICEST 图形显示质量优先
      下面用代码来实现一下,回到第二篇那个最初始的框架。
      首先将点和线的大小设大一下,效果会比较明显:
    glPointSize(20);
    glLineWidth(20);
      绘制一些点和线:
void NeHeWidget::paintGL()
{
    // 清除屏幕和深度缓存
    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
    glLoadIdentity();
    //坐标转移
    glTranslatef(-1.5f,0.0f,-4.0f);
    //设置颜色
    glColor3f( 1.0, 1.0, 1.0 );
    glBegin(GL_POINTS) ;
    glColor3f(1.0,1.0,1.0); //设置点颜
    glVertex2f(-0.5, -0.5);
    glColor3f(1.0,0.0,0.0);
    glVertex2f(-0.5,0.5);
    glColor3f(0.0,0.0,1.0);
    glVertex2f(0.5,0.5);
    glColor3f(0.0,1.0,0.0);
    glVertex2f(0.5,-0.5);
    glEnd();
    glLoadIdentity();
    //坐标转移
    glTranslatef(1.5f,0.0f,-4.0f);
    glBegin(GL_LINES);
        glVertex3f(-1.0, 1.0,0.0);
        glVertex3f(1.0,- 1.0,0.0);
        glColor3f(0.0,0.0,1.0);
        glVertex3f(1.0, 1.0,0.0);
        glVertex3f(-1.0, -1.0,0.0);
    glEnd();
}
在没有设置抗锯齿的时候,效果是这样的:
在initializeGL()中加入抗锯齿的代码:
glEnable (GL_POINT_SMOOTH);
glHint (GL_POINT_SMOOTH, GL_NICEST);
glEnable(GL_POINT_SMOOTH);
glEnable(GL_LINE_SMOOTH);
glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);
glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
最后效果是这样:

四.参考资料

1.      《 OpenGL Reference Manual 》, OpenGL 参考手册

2.      《 OpenGL 编程指南》(《 OpenGL Programming Guide 》), Dave Shreiner , Mason Woo , Jackie Neider , Tom Davis 著,徐波译,机械工业出版社

3.         《win32 OpenGL编程 》   一个大牛的博客     http://blog.csdn.net/vagrxie/article/category/628716/3
4.       《OpenGL函数思考 》   里面有很多OpenGL函数的通俗解释     http://blog.csdn.net/shuaihj
5.       《nehe的OpenGL教程》 比较通俗易懂的OpenGL教程    http://nehe.gamedev.net/