OpenGL(十九)——Qt OpenGL波动纹理(旗子的飘动效果)

20 篇文章 67 订阅
13 篇文章 49 订阅

OpenGL(十九)——Qt OpenGL波动纹理(旗子的飘动效果)

一、场景

在日常的项目中,我们经常会实现波动的一些纹理效果,比如飘动的旗子,水的波纹,地图上某一点的波浪圈圈等...,本篇介绍波动纹理的实现,旗子的飘动。

二、波动

在实现波动效果的时候,常用的波动使用的就是正弦函数sin,然后再叠加纹理的实现,就能实现飘动的旗子了。

三、代码

头文件:

#include <QObject>
#include <QWidget>
#include <qgl.h>
#include <QTimer>
#include <QKeyEvent>
/*
*旗的效果(波动纹理).
*/

class NeHe_11_Widget : public QGLWidget
{
    Q_OBJECT
public:
    NeHe_11_Widget(QWidget* parent = 0);
    ~NeHe_11_Widget();

protected:
    void initializeGL(); 
    void paintGL();      
    void resizeGL( int width, int height ); 

    void loadGLTextures(); //在这个函数中我们会载入指定的图片并生成相应当纹理。

    void keyPressEvent( QKeyEvent *e );
    void timerEvent( QTimerEvent *e ); 

protected:
  GLfloat xRot, yRot, zRot;
  GLfloat hold;  //变量hold将存放一个用来对旗形波浪进行光滑的浮点数。
  GLuint texture[1];

  float points[45][45][3]; //points数组来存放网格各顶点独立的(x,y,z)坐标.这里网格由45×45点形成,换句话说也就是由44格×44格的小方格子依次组成了。
  int wiggle_count; //wiggle_count用来指定纹理波浪的运动速度,每3帧一次看起来很不错。
};

cpp文件的实现:

#include <GL/glu.h>
#include <GL/gl.h>

#include <QImage>
#include <QDebug>

NeHe_11_Widget::NeHe_11_Widget(QWidget *parent):QGLWidget(parent)
{
    xRot = yRot = zRot = 0.0;
    hold = 0.0;

    wiggle_count = 0;

    setGeometry( 100, 100, 640, 480 );

    startTimer( 50 );
}


NeHe_11_Widget::~NeHe_11_Widget()
{
}

void NeHe_11_Widget::initializeGL()
{

    loadGLTextures();
    //载入纹理.
    glEnable( GL_TEXTURE_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 );
    //真正精细的透视修正。这一行告诉OpenGL我们希望进行最好的透视修正。这会十分轻微的影响性能。但使得透视图看起来好一点。

    glPolygonMode( GL_BACK, GL_FILL );
    glPolygonMode( GL_FRONT, GL_LINE );
    //上面的代码指定使用完全填充模式来填充多边形区域的背面(或者叫做后表面吧)。相反,多边形的正面(前表面)则使用轮廓线填充了。\
    这些方式完全取决于您的个人喜好。并且与多边形的方位或者顶点的方向有关。详情请参考Red Book。这里我顺便推销一本推动我学习OpenGL\
    的好书-Addison-Wesley出版的《Programmer's Guide to OpenGL》。个人以为这是学习OpenGL的无价之宝。

    for ( int x = 0; x < 45; x++ )
    {
        for ( int y = 0; y < 45; y++ )
        {
            points[x][y][0] = float( ( x/5.0 ) - 4.5 );
            points[x][y][1] = float( ( y/5.0 ) - 4.5 );
            points[x][y][2] = float( sin( ( ( ( x/5.0 ) * 40.0 )/360.0 ) * 3.141592654 * 2.0 ) );
        }
    }

}

这里感谢Graham Gibbons关于使用整数循环变量消除波浪间的脉冲锯齿的建议。

上面的两个循环初始化网格上的点。使用整数循环可以消除由于浮点运算取整造成的脉冲锯齿的出现。我们将x和y变量都除以5,再减去4.5。这样使得我们的波浪可以“居中”(这样计算所得结果将落在区间[-4.5,4.5]之间)。

点[x][y][2]最后的值就是一个sin函数计算的结果。sin()函数需要一个弧度参变量。将float_x乘以40.0,得到角度值。然后除以360.0再乘以PI,乘以2.0,就转换为弧度了。

void NeHe_11_Widget::resizeGL( int width, int height )
{
    if ( height == 0 )
    {
      height = 1;
    }
    //防止height为0。

    glViewport( 0, 0, (GLint)width, (GLint)height );
    //重置当前的视口(Viewport)。

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

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

    gluPerspective( 45.0, (GLfloat)width/(GLfloat)height, 0.1, 100.0 );
    //建立透视投影矩阵。

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

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

}

void NeHe_11_Widget::paintGL()
{
    int x, y;
    float float_x, float_y, float_xb, float_yb;
    //x、y是循环变量,float_x、float_y、float_xb、float_yb是用来将旗形的波浪分割成很小的四边形。

    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
    //清楚屏幕和深度缓存。

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

    glTranslatef( 0.0, 0.0, -12.0 );

    glRotatef( xRot, 1.0, 0.0, 0.0 );
    glRotatef( yRot, 0.0, 1.0, 0.0 );
    glRotatef( zRot, 0.0, 0.0, 1.0 );

    glBindTexture( GL_TEXTURE_2D, texture[0] );


    glBegin( GL_QUADS );
    for ( x = 0; x < 44; x++ )
    { //沿X平面0-44循环(45点)
        for ( y = 0; y < 44; y++ )
        {
            //沿Y平面0-44循环(45点)
            float_x = float(x)/44.0;
            float_y = float(y)/44.0;
            float_xb = float(x+1)/44.0;
            float_yb = float(y+1)/44.0;
            //上面我们使用4个变量来存放纹理坐标。每个多边形(网格之间的四边形)分别映射了纹理的1/44 x 1/44部分。\
            循环首先确定左下顶点的值,然后我们据此得到其他三点的值。

            glTexCoord2f( float_x, float_y );
            glVertex3f( points[x][y][0], points[x][y][1], points[x][y][2] );

            glTexCoord2f( float_x, float_yb );
            glVertex3f( points[x][y+1][0], points[x][y+1][1], points[x][y+1][2] );

            glTexCoord2f( float_xb, float_yb );
            glVertex3f( points[x+1][y+1][0], points[x+1][y+1][1], points[x+1][y+1][2] );

            glTexCoord2f( float_xb, float_y );
            glVertex3f( points[x+1][y][0], points[x+1][y][1], points[x+1][y][2] );
            //上面四个坐标分别为左下、左上、右上、右下。

            //上面几行使用glTexCoord2f()和 glVertex3f()载入数据。提醒一点:四边形是逆时针绘制的。这就是说,\
            您开始所见到的表面是背面。后表面完全填充了,前表面由线条组成。

            //如果您按顺时针顺序绘制的话,您初始时见到的可能是前表面。也就是说您将看到网格型的纹理效果而不是完全填充的。
        }

    }
    glEnd();
    if ( wiggle_count == 2 )
    {
      for ( y = 0; y < 45; y++ )
      {
        hold = points[0][y][2];
        for ( x = 0; x < 44; x++ )
        {
          points[x][y][2] = points[x+1][y][2];
        }
        points[44][y][2] = hold;
      }
      wiggle_count = 0;
    }

    wiggle_count++;
    //上面所作的事情是先存储每一行的第一个值,然后将波浪左移一下,是图象产生波浪。存储的数值挪到末端以产生一个永无尽头\
    的波浪纹理效果。然后重置计数器 wiggle_count以保持动画的进行。

    //上面的代码由NeHe(2000年2月)修改过,以消除波浪间出现的细小锯齿。

      xRot += 0.3;
      yRot += 0.2;
      zRot += 0.4;
    //标准的NeHe旋转增量.

}

//loadGLTextures()函数就是用来载入纹理的。
void NeHe_11_Widget::loadGLTextures()
{
    QImage tex,buf;

    if (!buf.load( ":/data/Tim.bmp" ))
    {
        qWarning( "Could not read image file, using single-color instead." );
        QImage dummy( 128, 128, QImage::Format_RGB32 );
        dummy.fill( Qt::green );
        buf = dummy;
        //如果载入不成功,自动生成一个128*128的32位色的绿色图片。
    }

    tex = QGLWidget::convertToGLFormat(buf);

    glGenTextures( 1, &texture[0] );

    glBindTexture( GL_TEXTURE_2D, texture[0] );

    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
    glTexImage2D( GL_TEXTURE_2D, 0, 3, tex.width(), tex.height(), 0,
                  GL_RGBA, GL_UNSIGNED_BYTE, tex.bits() );

}

//这里就是定时操作函数timerEvent(),执行的操作就是updateGL(),就是刷新窗口了,其实它也会调用paintGL(),所以就实现了每5毫秒刷新一次的动画效果。
void NeHe_11_Widget::timerEvent(QTimerEvent*)
{
  updateGL();
}


void NeHe_11_Widget::keyPressEvent( QKeyEvent *e )
{
    switch ( e->key() )
    {
    case Qt::Key_Escape:
      close();

    }

}

四、运行效果

正在上面的代码实现后,我们需要再运行看一下效果怎么样。

 

 

 

 五、资料

通过上面的结果展示,我们可以看到旗子飘动的效果,文章所需的资源,我放到了百度网盘,如果链接失效,大家可以私信我。

链接:https://pan.baidu.com/s/1515gb2lUC-NdWQcF60nWNg 
提取码:j1lm

 

上一篇:OpenGL(十八)——Qt OpenGL绘制一个3D世界

下一篇:

本文原创作者:冯一川(ifeng12358@163.com),未经作者授权同意,请勿转载。

  • 4
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

冯一川

谢谢老板对我的支持!

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

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

打赏作者

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

抵扣说明:

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

余额充值