OpenGL_Qt学习笔记之_07(闪烁的星星)

 

     这一小节我们要完成的任务是:将一张背景是黑色,中间是白亮色的星星图片和五颜六色的颜色进行色彩融合,变成一颗彩色的星星。并且让这些星星自转和公转,可以控制自转和公转的速度,另外也能控制所有的星星是否能够闪烁。

 

     实验基础

  色彩融合知识:

      刚开始提到由黑白的星星变成彩色的星星用到的是色彩融合的知识,关于色彩融合,在上篇文章OpenGL_Qt学习笔记之_06(纹理滤波和光照)已经有了个简单的介绍,不过该知识点并没有想象中的那么简单。当我们有了一些简单的色彩混合知识后,可以通过下面这个函数来更一步加深我们的理解。

  void glColor4f(GLfloat red,GLfloat green, GLfloat blue,GLfloat alpha);

  这里以该参数为例glColor4f(0.1, 0.3, 0.2, 0.5);

  该函数是设置后面出现像素的颜色信息的,前面3个参数为rgb分量的比例,加入后面我们绘制某个点时,对应的分量都要乘以该比例值。比如,新来的像素值pix(0.2, 0.5, 0.3),其实它的像素值只有(0.02, 0.15, 0.05),因为各自乘了0.1, 0.3, 0.2.

     下面来看看glColor4f()函数alpha参数的作用,如果我们不开启色彩混合,即没有使用glEnable(GL_BLEND);语句的话,那么该参数没有什么作用。既然我们这里讨论该参数的作用,当然是假设已经启用了色彩融合了,另外这里顺便还假设下环境中没有开启任何光源。在开启了色彩融合,且设置色彩融合的源因子为GL_SRC_ALPHA后,这里假设使用了下面的代码:glBlendFunc(GL_SRC_ALPHA, GL_ONE);则我们上面新来的像素还要考虑它的透明度(该例子为50%),所以pix的实际值是为(0.01, 0.075, 0.025),因为还要都乘以0.5. 并且该像素的alpha因子为0.5。

     简单的说,用了glColor4f函数且开启了色彩混合后,新来的像素值先转换为被glColor4f作用过后的值,然后与该位置以前的像素值进行叠加(即混合),叠加的方式需要参照glBlendFunc()的参数来设置。

 

     星星闪烁的原理:

     看到的闪烁的星星,其闪烁效果是怎样形成的呢?NeHe采用的方法是不管是否采用闪烁模式,绘制出来的星星都会自转,即围绕自己的中心旋转,如果启动了闪烁模式的话,则在自转的星星上面继续绘制一个不转的星星,这2者叠加到一起看起来就像在闪烁了。因为星星的中间亮,周围暗,因此一个转一个不转就会有这种效果(自己可以跑下程序试一试就会明白)。

 

      星星的旋转原理:

      该程序中星星绕x轴旋转是为了方便在屏幕上显示给用户看的;绕y轴旋转完成的是公转;绕z轴旋转完成的是自转.

  注意在opengl中,glRotatef()移动的都是坐标系这个整体。我们有必要更一步认识该函数:

  glRotatef(GLfloat angle, GLfloat x, GLfloat y, GLfloat z);

  参数1是旋转的角度,这个不用多解释;参数2,3,4组合在空间构成一个三维向量,然后我们用右手法则,握住这个向量并且使大拇指的方向朝向该向量的方向,则另外4指的方向就是旋转的方向了。如果angle固定不变,那么不管窗口是否在刷新,该坐标只会旋转一次(也可以理解为旋转多次,只不过每次的转过的角度不变,感觉就像没有旋转一样),要让物体有旋转的感觉就必须不断的增加或者减小angle的值。

  如果我们同时有几个glRotatef()函数被执行,则后面那个函数旋转的向量坐标系是在前面那个已经旋转好了的坐标系下进行的,并不是一直使用开始固定的那个绝对坐标系的。总的来说,该函数旋转的整个坐标系,一旦旋转过,则后面的步骤都是在这个旋转过的坐标系下进行的。

  在NeHe的程序中,完成星星公转的代码是先绕x轴旋转,在绕y轴旋转,然后移动距离,接着又绕y轴反向旋转相同大小,继续绕x轴方向旋转相同角度,最后才开始绘制星星纹理。这样做的目的就是为了让星星公转的时候是在屏幕上进行的。如果我们不采用这种坐标抵消的方法,其实根本就不用旋转坐标轴,直接用三角函数计算好星星在屏幕中公转圆周上的坐标值,然后纹理映射的时候应用这些坐标值即可。这里NeHe采用的是空间坐标轴旋转,很巧妙的避开了数学计算这一过程。

  那么有些朋友可能会问,沿x轴和沿y轴旋转过后,开没开始绘画星星图样时又沿它们的反方向旋转回去,这样岂不是相当于没转一样,是多余的。真的是这样的吗?显然不是,大家可以用个物体,比如一张扑克牌动手实验一下就知道了,这里也不好画很多示意图解释。反正那些步骤不是多余的原因是因为在反向旋转抵消角度之前已经向x轴方向移动了dist的距离(x轴是指相对上一次旋转过后的坐标轴x),所以才会到达公转的目的。如果中间没有移动距离的实现,直接方向旋转抵消x轴和y轴的值,那么那些代码确实是多余的。

   公转理解了,自转就更容易理解了,这里不再做过多解释。

 

  实验说明:

  按键T是用来控制是否开启星星闪烁功能;按键B是用来控制是否开启色彩融合功能;PageUp键用来控制物体走向远处的距离,相反PageDown用来控制物体走向观察者的距离;向上的方向键和向下的方向键用来控制星星公转屏幕和屏幕的夹角(默认值2者是重合的);当然了F1键用来控制全屏切换,Ese键是退出程序键。

  开发环境:Windows+Qt4.8.2+QtCreator2.5.1

 

  实验结果:

  不开启色彩融合的效果:

  

 

  开启色彩融合,不开启闪烁功能的效果:

  

 

  开始闪烁功能的效果:

  

 

  开启闪烁功能动态效果(qq截图的,感觉有点失真):

  

  

实验主要部分代码及注释(附录有实验工程code下载地址):

#include "glwidget.h"
#include "ui_glwidget.h"

#include <QtGui>
#include <QtCore>
#include <QtOpenGL>
#include <glut>

/*c++中可以在类的外部定义变量*/
GLfloat light_ambient[4]={0.5, 0.5, 0.5, 1.0};
GLfloat light_diffuse[4]={1.0, 1.0, 1.0, 1.0};
GLfloat light_position[4]={0.0, 0.0, 2.0, 0.0};

GLWidget::GLWidget(QGLWidget *parent) :
    QGLWidget(parent),
    ui(new Ui::GLWidget)
{
  //  setCaption("The Opengl for Qt Framework");
    ui->setupUi(this);
    fullscreen = false;
    rotate_angle = 0.0;
    zoom = -15.0;
    title = 90.0;
    spin = 0.1;
    loop = 0;
    twinkle = false;
    blend = false;
 //   timer = new QTimer(this);
  //  connect(timer, SIGNAL(timeout()), this, SLOT(timerEvent()));
    startTimer(5);//开启5ms定时器
 //   timer->start(50);
}

//这是对虚函数,这里是重写该函数
void GLWidget::initializeGL()
{
    setGeometry(300, 150, 500, 500);//设置窗口初始位置和大小
    loadTextures();
    glEnable(GL_TEXTURE_2D);//允许采用2D纹理技术
    glShadeModel(GL_SMOOTH);//设置阴影平滑模式
    glClearColor(0.0, 0.0, 0.0, 0.5);//改变窗口的背景颜色
    glClearDepth(1.0);//设置深度缓存
    glEnable(GL_DEPTH_TEST);//允许深度测试
    glDepthFunc(GL_LEQUAL);//设置深度测试类型
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);//进行透视校正

    glBlendFunc(GL_SRC_ALPHA, GL_ONE);//源像素因子采用alpha通道值,目标像素因子采用1.0
    glEnable(GL_BLEND);

    /*为num个星星的数组结构体赋初值*/
    for(loop = 0; loop < num; loop++)
        {
            star[loop].angle = 0.0;
            star[loop].dist = (float(loop)/num)*5.0;//星星的离中心的距离越来越远,最大距离为5,接近屏幕的距离
            star[loop].r = rand()%256;
            star[loop].g = rand()%256;
            star[loop].b = rand()%256;
        }
}

void GLWidget::paintGL()
{
    //glClear()函数在这里就是对initializeGL()函数中设置的颜色和缓存深度等起作用
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glBindTexture(GL_TEXTURE_2D, texture[0]);//绑定纹理目标
    for(loop = 0; loop < num; loop++)
        {
            glLoadIdentity();
            glTranslatef(0.0, 0.0, zoom);//移向屏幕里面
            glRotatef(title, 1.0, 0.0, 0.0);//沿着x轴旋转了tilt度
            glRotatef(star[loop].angle, 0.0, 1.0, 0.0);//每个星星沿着y轴旋转自己的角度
            glTranslatef(star[loop].dist, 0.0, 0.0);
            glRotatef(-star[loop].angle, 0.0, 1.0, 0.0);//将沿着y轴旋转的角度又转回去
            glRotatef(-title, 1.0, 0.0, 0.0);//将沿着x轴旋转过的角度也转回去
            if(twinkle)//如果星星闪烁的话
                {
                    glColor4ub(star[num-loop-1].r, star[num-loop-1].g, star[num-loop-1].b, 255);//采用的是对称一头那边的星星的颜色
                    //将星星的纹理贴到一个小矩形上
                    glBegin(GL_QUADS);
                    glTexCoord2f(0.0, 0.0);
                    glVertex3f(-1.0, -1.0, 0.0);
                    glTexCoord2f(1.0, 0.0);
                    glVertex3f(1.0, -1.0, 0.0);
                    glTexCoord2f(1.0, 1.0);
                    glVertex3f(1.0, 1.0, 0.0);
                    glTexCoord2f(0.0, 1.0);
                    glVertex3f(-1.0, 1.0, 0.0);
                    glEnd();
                }
            /*不闪烁时*/
            glRotatef(spin, 0.0, 0.0, 1.0);//星星沿z轴自转spin度
            glColor4ub(star[loop].r, star[loop].g, star[loop].b, 255);//采用的是自己的颜色,完全不透明
            glBegin(GL_QUADS);
            glTexCoord2f(0.0, 0.0);
            glVertex3f(-1.0, -1.0, 0.0);
            glTexCoord2f(1.0, 0.0);
            glVertex3f(1.0, -1.0, 0.0);
            glTexCoord2f(1.0, 1.0);
            glVertex3f(1.0, 1.0, 0.0);
            glTexCoord2f(0.0, 1.0);
            glVertex3f(-1.0, 1.0, 0.0);
            glEnd();

            spin += 0.01f;
            star[loop].angle += float(loop)/num;//角度是在慢慢增大的
            star[loop].dist -= 0.01f;//距离慢慢减小,被吸向了屏幕中心
            if(star[loop].dist < 0)
                {
                    star[loop].dist += 5.0f;
                    star[loop].r = rand()%256;
                    star[loop].g = rand()%256;
                    star[loop].b = rand()%256;
                }
        }
}

//该程序是设置opengl场景透视图,程序中至少被执行一次(程序启动时).
void GLWidget::resizeGL(int width, int height)
{
    if(0 == height)
        height = 1;//防止一条边为0
    glViewport(0, 0, (GLint)width, (GLint)height);//重置当前视口,本身不是重置窗口的,只不过是这里被Qt给封装好了
    glMatrixMode(GL_PROJECTION);//选择投影矩阵
    glLoadIdentity();//重置选择好的投影矩阵
    gluPerspective(45.0, (GLfloat)width/(GLfloat)height, 0.1, 100.0);//建立透视投影矩阵
    glColor4f(1.0f, 1.0f, 1.0f, 0.5f);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE);
    glMatrixMode(GL_MODELVIEW);//以下2句和上面出现的解释一样
    glLoadIdentity();

}
void GLWidget::keyPressEvent(QKeyEvent *e)
{
    switch(e->key())
    {
        /*PageUp键为将木箱移到屏幕内部方向*/
        case Qt::Key_T:
        twinkle = !twinkle;
        updateGL();
        break;
        /*B键位选择是否采用色彩融合*/
        case Qt::Key_B:
            blend = !blend;
            if(blend)
                {
                    glEnable(GL_BLEND);//色彩融合和深度缓存不能同时开启
                    glDisable(GL_DEPTH_TEST);
                }
            else
                {
                    glDisable(GL_BLEND);
                    glEnable(GL_DEPTH_TEST);
                }
            updateGL();
            break;
        /*PageUp键为将木箱移到屏幕内部方向*/
        case Qt::Key_PageUp:
            zoom -= 0.2;
            updateGL();
            break;
        /*PageDown键为将木箱移到屏幕外部方向*/
        case Qt::Key_PageDown:
            zoom += 0.2;
            updateGL();
            break;
        /*Up键为加快立方体旋转的速度*/
        case Qt::Key_Up:
            title += 0.5;
            updateGL();
            break;
        /*Down键为减慢立方体旋转的速度*/
        case Qt::Key_Down:
            title -= 0.5;
            updateGL();
            break;
        /*F1键为全屏和普通屏显示切换键*/
        case Qt::Key_F1:
            fullscreen = !fullscreen;
            if(fullscreen)
                showFullScreen();
            else
            {
                setGeometry(300, 150, 500, 500);
                showNormal();
            }
            updateGL();
            break;
        /*Ese为退出程序键*/
        case Qt::Key_Escape:
            close();
    }
}

/*装载纹理*/
void GLWidget::loadTextures()
{
    QImage tex, buf;
    if(!buf.load("./Particle.bmp"))//这个时候因为debug没有在外面,所以图片文件夹就是本目录了
   // if(!buf.load("../opengl_qt_nehe_07/crate.bmp"))
    {
        qWarning("Cannot open the image...");
        QImage dummy(128, 128, QImage::Format_RGB32);//当没找到所需打开的图片时,创建一副128*128大小,深度为32位的位图
        dummy.fill(Qt::green);
        buf = dummy;
    }
    tex = convertToGLFormat(buf);//将Qt图片的格式buf转换成opengl的图片格式tex
    glGenTextures(1, &texture[0]);//开辟3个纹理内存,索引指向texture[0]

    /*建立第一个纹理*/
    glBindTexture(GL_TEXTURE_2D, texture[0]);//将创建的纹理内存指向的内容绑定到纹理对象GL_TEXTURE_2D上,经过这句代码后,以后对
                                            //GL_TEXTURE_2D的操作的任何操作都同时对应与它所绑定的纹理对象
    glTexImage2D(GL_TEXTURE_2D, 0, 3, tex.width(), tex.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, tex.bits());//开始真正创建纹理数据
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);//当所显示的纹理比加载进来的纹理小时,采用GL_NEAREST的方法来处理
                                                                      //GL_NEAREST方式速度非常快,因为它不是真正的滤波,所以占用内存非常
                                                                      // 小,速度就快了
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);//当所显示的纹理比加载进来的纹理大时,采用GL_NEAREST的方法来处理

}

//void GLWidget::timerEvent()
//{
//    spin += 0.01f;
//    for(loop = 0; loop < num; loop++)
//        {
//            star[loop].angle += float(loop)/num;
//            star[loop].dist -= 0.01f;
//            if(star[loop].dist < 0)
//                {
//                    star[loop].dist += 5.0f;
//                    star[loop].r = rand()%256;
//                    star[loop].g = rand()%256;
//                    star[loop].b = rand()%256;
//                }
//        }
//    updateGL();//执行改句就是刷新窗口了,其实内部调用的是paintGL()函数
//}

void GLWidget::timerEvent(QTimerEvent *)
{
    updateGL();
}

GLWidget::~GLWidget()
{
    delete ui;
}

 

 

  总结:本次实验是对前面知识的一个小小的综合,需要对色彩融合知识比较深入的理解,特别是深度信息在对绘图先后顺序的影响;另外理解星星公转和闪烁的实现方法是本程序的关键之处。通过本实验,可以简单的实现一些物体在3D空间中的移动。

 

 

    参考资料:  

      http://nehe.gamedev.net/ 

      http://www.owlei.com/DancingWind/

      http://www.qiliang.net/old/nehe_qt/

 

 

  附录:

  实验工程code下载

 

 

 

 

 

posted on 2012-08-26 10:26  tornadomeet  阅读(7941)  评论(0编辑  收藏  举报

阿萨德发斯蒂芬