OpenGL笔记--融合

本文介绍OpenGL中的融合技术,包括Alpha通道的应用及glBlendFunc函数的使用方法,通过实例展示不同因子对融合效果的影响。

一、前置知识

融合:将两个或两个以上的物体或图像进行叠合,最后生成新物体或一张图像的过程;

  • 两个物体的融合,是通过目标物体和原物体颜色的组合产生新颜色的过程;
  • 最主要的方法是Alpha通道技术,Alpha通道是指在24位真彩色图像上加上另外8位信息,用它来描述256级不同的透明度值;
  • 如果一个像素的Alpha通道数值为0%,那么它是完全透明的;如果Alpha通道数值为100%,那么它是完全不透明的;

在OPenGL中,要进行融合处理,首先要生成用于融合计算的两个因子:一个是源因子,一个是目标因子,使用函数glBlendFunc()完成:

void glBlendFunc(GLenum sfactor, GLenum dfactor);
sfactor:说明如何计算源因子
dfactor:说明如何计算目标因子

将要画上去的颜色称为“源颜色”,受“源因子”影响;原来的颜色称为“目标颜色”,受“目标因子”影响;

Sfactor的参数取值

常数最终源因子
GL_ZERO(0,0,0,0)
GL_ONE(1,1,1,1)
GL_DST_COLOR(Rd,Gd,Bd,Ad)
GL_ONE_MINUS_DST_COLOR(1,1,1,1)-(Rd,Gd,Bd,Ad)
GL_SRC_ALPHA(As,As,As,As)
GL_ONE_MINU_SRC_ALPHA(1,1,1,1)-(As,As,As,As)
GL_DST_ALPHA(Ad,Ad,Ad,Ad)
GL_ONE_MINUS_DST_ALPHA(1,1,1,1)-(Ad,Ad,Ad,Ad)
GL_SRC_ALPHA_SATURATE(f,f,f,1)

Dfactor的参数取值

常数最终目标因子
GL_ZERO(0,0,0,0)
GL_ONE(1,1,1,1)
GL_SRC_COLOR(Rs,Gs,Bs,As)
GL_ONE_MINUS_SRC_COLOR(1,1,1,1)-(Rs,Gs,Bs,As)
GL_SRC_ALPHA(As,As,As,As)
GL_ONE_MINU_SRC_ALPHA(1,1,1,1)-(As,As,As,As)
GL_DST_ALPHA(Ad,Ad,Ad,Ad)
GL_ONE_MINUS_DST_ALPHA(1,1,1,1)-(Ad,Ad,Ad,Ad)

设融合的源因子为(Rs,Gs,Bs,As),融合的目标因子为(Rd,Gd,Bd,Ad)
原物体的颜色为(Sr,Sg,Sb,Sa),目标物体在同一点的颜色为(Dr,Dg,Db,Da),
最后融合的颜色为:(Rs*Sr+Rd*Dr,Gs*Sg+Gd*Dg,Bs*Sb+Bd*Db,As*Sa+Ad*Da)

计算过程
假设源物体的一个点的颜色值为(0.7, 0.6, 0.8, 0.6),目标物体在同一个点的颜色值为(0.4, 0.3, 0.6, 1.0),
glBlendFunc(GL_SRC_ALPHA , GL_ONE_MINU_SRC_ALPHA );

  • OPenGL首先获得源物体颜色向量中的Alpha值,为0.6,用它作为融合的源因子;
  • 然后用源因子乘以源物体的颜色向量,即0.6x(0.7, 0.6, 0.8, 0.6)=(0.42, 0.36, 0.48, 0.36);
  • 通过GL_ONE_MINU_SRC_ALPHA 获得用于融合的目标因子,1-0.6=0.4;
  • 然后用目标因子乘以目标物体的颜色向量,即0.4x(0.4, 0.3, 0.6, 1.0)=(0.16, 0.12, 0.24, 0.4);
  • 最后将两个颜色向量相加,获得最终的颜色,即(0.42, 0.36, 0.48, 0.36)+(0.16, 0.12, 0.24, 0.4)=(0.58, 0.48, 0.72, 0.76);

OPenGL通过glEnable(GL_BLEND)glDisable(GL_BLEND)来启动、关闭融合功能;


二、运行效果

在这里插入图片描述


三、完整代码

#ifndef GL_TEST_H
#define GL_TEST_H

#include <qgl.h>        //因为QGLWidget类被包含在qgl.h头文件中
#include <glut.h>       //使用glut库中的API
#include <QKeyEvent>
#include <QTimer>

//继承QGLWidget得到OPenGL窗口部件类
class GL_Test : public QGLWidget
{
public:
    GL_Test(QWidget* parent = 0, bool fs = false);
    ~GL_Test();

protected:
    /*************************************************************************************************
    QGLWidget 类已经内置了对 OpenGL 的处理,就是通过对 initializeGL()、 paintGL()和 resizeGL()这个三个函数实现
    *************************************************************************************************/
    void initializeGL() override;           //用来初始化OPenGL窗口,可以在里面设定一些有关选项
    void paintGL() override;                //用来绘制OPenGL的窗口,只要有更新发生,这个函数就会被调用
    void resizeGL(int w, int h) override;   //用来处理窗口大小变换这一事件,resizeGL()在处理完后会自动刷新屏幕

    void keyPressEvent(QKeyEvent* e) override;  //Qt键盘事件处理函数

    void loadGLTextures();  //载入指定的图片并生成相应的纹理

protected:
    bool fullscreen;    //用来保存窗口是否处于全屏状态的变量
    GLfloat xRot, yRot, zRot;   //用来处理立方体的旋转
    GLuint texture[6];  //用来存储纹理(长度为3的数组)
    GLfloat zoom;       //场景深入屏幕的距离
    GLfloat xSpeed, ySpeed; //立方体在X轴和Y轴上旋转的速度
    GLuint filter;  //表明是使用哪个纹理

    bool light; //表明现在是否使用光源
    bool blend; //表明是否融合
};

#endif // GL_TEST_H

#include "gl_test.h"

//描述和光源有关的信息
GLfloat lightAmbient[4] = {0.5, 0.5, 0.5, 1.0};     //环境光
GLfloat lightDiffuse[4] = {1.0, 1.0, 1.0, 1.0};     //漫射光
GLfloat lightPosition[4] = {0.0, 0.0, 2.0, 1.0};    //保存光源的位置

GL_Test::GL_Test(QWidget* parent, bool fs)
    : QGLWidget(parent)
{
    fullscreen = fs;

    xRot = 0.0;
    yRot = 0.0;
    zRot = 0.0;

    zoom = -5.0;

    xSpeed = 0.0;
    ySpeed = 0.0;

    filter = 0;

    light = false;

    blend = false;

    setGeometry(500,500,640,480);               //设置窗口大小、位置

    setWindowTitle("The first OpenGL Window");  //设置窗口标题

    if(fullscreen) {
        showFullScreen();
    }

    QTimer* timer = new QTimer(this);
    connect(timer,&QTimer::timeout,[=]{
        updateGL();
    });
    timer->start(50);
}

GL_Test::~GL_Test()
{

}

void GL_Test::initializeGL()
{
    loadGLTextures();   //载入纹理

    glEnable(GL_TEXTURE_2D);    //启用纹理

    glShadeModel(GL_SMOOTH);    //启用smooth shading(阴影平滑)

    glClearColor(0.0, 0.0, 0.0, 0.5);   //清除屏幕时所用的颜色,rgba【0.0(最黑)~1.0(最亮)】

    glClearDepth(1.0);  //设置深度缓存

    glEnable(GL_DEPTH_TEST);    //启动深度测试

    glDepthFunc(GL_LEQUAL); //所作深度测试的类型

    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);  //真正精细的透视修正,告诉OPenGL我们希望进行最好的透视修正,这会十分轻微的影响性能,但使得透视图看起来好一点

    //启动1号光源
    glLightfv(GL_LIGHT1, GL_AMBIENT, lightAmbient);

    glLightfv(GL_LIGHT1, GL_DIFFUSE, lightDiffuse);

    glLightfv(GL_LIGHT1, GL_POSITION, lightPosition);

    glEnable(GL_LIGHT1);

    glColor4f(1.0, 1.0, 1.0, 0.5);  //以全亮绘制此物体,并对其进行50%alpha融合(半透明)

    glBlendFunc(GL_SRC_ALPHA, GL_ONE);  //设置所采用的融合类型
}

void GL_Test::paintGL()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //清除屏幕和深度缓存

    //-----------------------------------------
    glLoadIdentity();   //重置当前的模型观察矩阵

    glTranslatef(0.0, 0.0, zoom);

    glRotatef(xRot, 1.0, 0.0, 0.0); //绕X轴旋转xRot度
    glRotatef(yRot, 0.0, 1.0, 0.0); //绕X轴旋转yRot度

//    //选择我们使用的纹理
//    glBindTexture(GL_TEXTURE_2D, texture[filter]);
//    //如果您在您的场景中使用多个纹理,您应该使用来 glBindTexture(GL_TEXTURE_2D, texture[所使用纹理对应的数字]) 选择要绑定的纹理;
//    //当您想改变纹理时,应该绑定新的纹理。并且您不能在glBegin()和glEnd()之间绑定纹理,必须在glBegin()之前或glEnd()之后绑定;

    glBindTexture(GL_TEXTURE_2D, texture[0]);
    //绘制正方形开始
    glBegin(GL_QUADS);
    //前面
    glNormal3f(0.0, 0.0, 1.0);  //x、y、z,垂直于面的法线向量
    glTexCoord2f( 0.0, 0.0 ); glVertex3f( -1.0, -1.0, 1.0 );
    glTexCoord2f( 1.0, 0.0 ); glVertex3f( 1.0, -1.0, 1.0 );
    glTexCoord2f( 1.0, 1.0 ); glVertex3f( 1.0, 1.0, 1.0 );
    glTexCoord2f( 0.0, 1.0 ); glVertex3f( -1.0, 1.0, 1.0 );
    glEnd();
    //绘制正方形结束

    glBindTexture(GL_TEXTURE_2D, texture[1]);
    //绘制正方形开始
    glBegin(GL_QUADS);
    //后面
    glNormal3f(0.0, 0.0, -1.0); //x、y、z,垂直于面的法线向量
    glTexCoord2f( 1.0, 0.0 ); glVertex3f( -1.0, -1.0, -1.0 );
    glTexCoord2f( 1.0, 1.0 ); glVertex3f( -1.0, 1.0, -1.0 );
    glTexCoord2f( 0.0, 1.0 ); glVertex3f( 1.0, 1.0, -1.0 );
    glTexCoord2f( 0.0, 0.0 ); glVertex3f( 1.0, -1.0, -1.0 );
    glEnd();
    //绘制正方形结束

    glBindTexture(GL_TEXTURE_2D, texture[2]);
    //绘制正方形开始
    glBegin(GL_QUADS);
    //顶面
    glNormal3f(0.0, 1.0, 0.0);  //x、y、z,垂直于面的法线向量
    glTexCoord2f( 0.0, 1.0 ); glVertex3f( -1.0, 1.0, -1.0 );
    glTexCoord2f( 0.0, 0.0 ); glVertex3f( -1.0, 1.0, 1.0 );
    glTexCoord2f( 1.0, 0.0 ); glVertex3f( 1.0, 1.0, 1.0 );
    glTexCoord2f( 1.0, 1.0 ); glVertex3f( 1.0, 1.0, -1.0 );
    glEnd();
    //绘制正方形结束

    glBindTexture(GL_TEXTURE_2D, texture[3]);
    //绘制正方形开始
    glBegin(GL_QUADS);
    //底面
    glNormal3f(0.0, -1.0, 0.0); //x、y、z,垂直于面的法线向量
    glTexCoord2f( 1.0, 1.0 ); glVertex3f( -1.0, -1.0, -1.0 );
    glTexCoord2f( 0.0, 1.0 ); glVertex3f( 1.0, -1.0, -1.0 );
    glTexCoord2f( 0.0, 0.0 ); glVertex3f( 1.0, -1.0, 1.0 );
    glTexCoord2f( 1.0, 0.0 ); glVertex3f( -1.0, -1.0, 1.0 );
    glEnd();
    //绘制正方形结束

    glBindTexture(GL_TEXTURE_2D, texture[4]);
    //绘制正方形开始
    glBegin(GL_QUADS);
    //右面
    glNormal3f(1.0, 0.0, 0.0);  //x、y、z,垂直于面的法线向量
    glTexCoord2f( 1.0, 0.0 ); glVertex3f( 1.0, -1.0, -1.0 );
    glTexCoord2f( 1.0, 1.0 ); glVertex3f( 1.0, 1.0, -1.0 );
    glTexCoord2f( 0.0, 1.0 ); glVertex3f( 1.0, 1.0, 1.0 );
    glTexCoord2f( 0.0, 0.0 ); glVertex3f( 1.0, -1.0, 1.0 );
    glEnd();
    //绘制正方形结束

    glBindTexture(GL_TEXTURE_2D, texture[5]);
    //绘制正方形开始
    glBegin(GL_QUADS);
    //左面
    glNormal3f(-1.0, 0.0, 0.0);
    glTexCoord2f( 0.0, 0.0 ); glVertex3f( -1.0, -1.0, -1.0 );
    glTexCoord2f( 1.0, 0.0 ); glVertex3f( -1.0, -1.0, 1.0 );
    glTexCoord2f( 1.0, 1.0 ); glVertex3f( -1.0, 1.0, 1.0 );
    glTexCoord2f( 0.0, 1.0 ); glVertex3f( -1.0, 1.0, -1.0 );
    glEnd();
    //绘制正方形结束

    //-----------------------------------------
    xRot += xSpeed;
    yRot += ySpeed;
}

void GL_Test::resizeGL(int w, int h)
{
    if(h == 0) {    //防止h为0
        h = 1;
    }

    glViewport(0, 0, (GLint)w, (GLint)h);   //重置当前的视口(Viewport)

    glMatrixMode(GL_PROJECTION);    //选择投影矩阵

    glLoadIdentity();   //重置投影矩阵

    gluPerspective( 45.0, (GLfloat)w/(GLfloat)h, 0.1, 100.0 );  //建立透视投影矩阵

    glMatrixMode(GL_MODELVIEW); //选择模型观察矩阵

    glLoadIdentity();   //重置模型观察矩阵
}

void GL_Test::keyPressEvent(QKeyEvent* e)
{
    switch (e->key()) {
        case Qt::Key_L: {
            light = !light;
            if(!light) {
                glDisable(GL_LIGHTING);
            }else {
                glEnable(GL_LIGHTING);
            }
            updateGL();
            break;
        }

        case Qt::Key_B: {
            blend = !blend;
            if(blend) {
                glEnable(GL_BLEND);
                glDisable(GL_DEPTH_TEST);
            }else {
                glEnable(GL_DEPTH_TEST);
                glDisable(GL_BLEND);
            }
        }

        case Qt::Key_F: {
            filter += 1;
            if(filter > 2) {
                filter = 0;
            }
            updateGL();
            break;
        }

        case Qt::Key_Equal: {
            zoom += 1;
            updateGL();
            break;
        }

        case Qt::Key_Minus: {
            zoom -= 1;
            updateGL();
            break;
        }

        case Qt::Key_Up: {
            xSpeed -= 1;
            updateGL();
            break;
        }

        case Qt::Key_Down: {
            xSpeed += 1;
            updateGL();
            break;
        }

        case Qt::Key_Left: {
            ySpeed -= 1;
            updateGL();
            break;
        }

        case Qt::Key_Right: {
            ySpeed += 1;
            updateGL();
            break;
        }

        case Qt::Key_Q: {
            xSpeed = 0;
            ySpeed = 0;
            updateGL();
            break;
        }

        case Qt::Key_S: {
            fullscreen = !fullscreen;
            if(fullscreen) {
                showFullScreen();
            }else {
                showNormal();
                setGeometry(500,500,640,480);
            }
            updateGL();
            break;
        }//case Qt::Key_S

        case Qt::Key_Escape: {
            close();
        }//Qt::Key_Escape

    }//switch (e->key())
}

void GL_Test::loadGLTextures()
{
    QStringList Images;
    Images << "://Images/1.jpg";
    Images << "://Images/3.jpg";

	//创建5个相同的纹理,1个不同的纹理
    for(int i=0; i<6; ++i) {
        QImage tex,buf;
        if(i==0) {
            if(!buf.load(Images.at(1))) {    //用QImage类载入纹理图片
                QImage dummy(128,128,QImage::Format_RGB32); //如果载入不成功,生成一个128*128的32位色的绿色图片
                dummy.fill(Qt::green);
                buf = dummy;
            }
        }else {
            if(!buf.load(Images.at(0))) {    //用QImage类载入纹理图片
                QImage dummy(128,128,QImage::Format_RGB32); //如果载入不成功,生成一个128*128的32位色的绿色图片
                dummy.fill(Qt::green);
                buf = dummy;
            }
        }

        tex = QGLWidget::convertToGLFormat(buf);    //QGLWidget的静态函数,专门用来转换图片

        glGenTextures(1,&texture[i]);   //创建3个纹理

        //---------------------------------------------------------------------------------------
        glBindTexture(GL_TEXTURE_2D,texture[i]);    //使用来自位图数据生成的典型纹理
        /*告诉OPenGL将纹理名字texture[0]绑定到纹理目标上;2D纹理只有高度(在Y轴上)和宽度(在X轴上)*/

        //真正的创建纹理
        //GL_TEXTURE_2D:告诉 OpenGL 此纹理是一个 2D 纹理;
        //数字0:代表图像的详细程度;
        //数字3:是数据的成分数;
        //tex.width():是纹理的宽度
        //tex.height():是纹理的高度;
        //GL_RGBA:告诉 OpenGL 图像数据由红、绿、蓝三色数据以及 alpha 通道数据组成;
        //GL_UNSIGNED_BYTE:意味着组成图像的数据是无符号字节类型的;
        //tex.bits():告诉 OpenGL 纹理数据的来源;
        glTexImage2D(GL_TEXTURE_2D,
                     0,
                     3,
                     tex.width(),
                     tex.height(),
                     0, GL_RGBA,
                     GL_UNSIGNED_BYTE,
                     tex.bits());

        //告诉 OpenGL 在显示图像时,当它比放大得原始的纹理大(GL_TEXTURE_MAG_FILTER)或缩小得比原始得纹理小(GL_TEXTURE_MIN_FILTER)时,
        //OpenGL 采用的滤波方式
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        //我们都采用 GL_LINEAR,这使得纹理从很远处到离屏幕很近时都平滑显示;
        //使 用 GL_LINEAR 需要 CPU 和显卡做更多的运算,如果您的机器很慢,您也许应该采用 GL_NEAREST;
        //过滤的纹理在放大的时候,看起来斑驳的很,您也可以结合这两种滤波方式: 在近处时使用 GL_LINEAR,远处时 GL_NEAREST;
    }
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

贝勒里恩

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值