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


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

    一、场景

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

    二、波动

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

    三、代码

    头文件:

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. /*
    7. *旗的效果(波动纹理).
    8. */
    9. class NeHe_11_Widget : public QGLWidget
    10. {
    11. Q_OBJECT
    12. public:
    13. NeHe_11_Widget(QWidget* parent = 0);
    14. ~NeHe_11_Widget();
    15. protected:
    16. void initializeGL();
    17. void paintGL();
    18. void resizeGL( int width, int height );
    19. void loadGLTextures(); //在这个函数中我们会载入指定的图片并生成相应当纹理。
    20. void keyPressEvent( QKeyEvent *e );
    21. void timerEvent( QTimerEvent *e );
    22. protected:
    23. GLfloat xRot, yRot, zRot;
    24. GLfloat hold; //变量hold将存放一个用来对旗形波浪进行光滑的浮点数。
    25. GLuint texture[1];
    26. float points[45][45][3]; //points数组来存放网格各顶点独立的(x,y,z)坐标.这里网格由45×45点形成,换句话说也就是由44格×44格的小方格子依次组成了。
    27. int wiggle_count; //wiggle_count用来指定纹理波浪的运动速度,每3帧一次看起来很不错。
    28. };

    cpp文件的实现:

    1. #include
    2. #include
    3. #include
    4. #include
    5. NeHe_11_Widget::NeHe_11_Widget(QWidget *parent):QGLWidget(parent)
    6. {
    7. xRot = yRot = zRot = 0.0;
    8. hold = 0.0;
    9. wiggle_count = 0;
    10. setGeometry( 100, 100, 640, 480 );
    11. startTimer( 50 );
    12. }
    13. NeHe_11_Widget::~NeHe_11_Widget()
    14. {
    15. }
    16. void NeHe_11_Widget::initializeGL()
    17. {
    18. loadGLTextures();
    19. //载入纹理.
    20. glEnable( GL_TEXTURE_2D );
    21. //启用纹理。如果没有启用的话,你的对象看起来永远都是纯白色
    22. glShadeModel( GL_SMOOTH );
    23. glClearColor( 0.0, 0.0, 0.0, 0.5 );
    24. glClearDepth( 1.0 );
    25. glEnable( GL_DEPTH_TEST );
    26. glDepthFunc( GL_LEQUAL );
    27. //所作深度测试的类型。
    28. glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST );
    29. //真正精细的透视修正。这一行告诉OpenGL我们希望进行最好的透视修正。这会十分轻微的影响性能。但使得透视图看起来好一点。
    30. glPolygonMode( GL_BACK, GL_FILL );
    31. glPolygonMode( GL_FRONT, GL_LINE );
    32. //上面的代码指定使用完全填充模式来填充多边形区域的背面(或者叫做后表面吧)。相反,多边形的正面(前表面)则使用轮廓线填充了。\
    33. 这些方式完全取决于您的个人喜好。并且与多边形的方位或者顶点的方向有关。详情请参考Red Book。这里我顺便推销一本推动我学习OpenGL\
    34. 的好书-Addison-Wesley出版的《Programmer's Guide to OpenGL》。个人以为这是学习OpenGL的无价之宝。
    35. for ( int x = 0; x < 45; x++ )
    36. {
    37. for ( int y = 0; y < 45; y++ )
    38. {
    39. points[x][y][0] = float( ( x/5.0 ) - 4.5 );
    40. points[x][y][1] = float( ( y/5.0 ) - 4.5 );
    41. points[x][y][2] = float( sin( ( ( ( x/5.0 ) * 40.0 )/360.0 ) * 3.141592654 * 2.0 ) );
    42. }
    43. }
    44. }

    这里感谢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,就转换为弧度了。

    1. void NeHe_11_Widget::resizeGL( int width, int height )
    2. {
    3. if ( height == 0 )
    4. {
    5. height = 1;
    6. }
    7. //防止height为0。
    8. glViewport( 0, 0, (GLint)width, (GLint)height );
    9. //重置当前的视口(Viewport)。
    10. glMatrixMode( GL_PROJECTION );
    11. //选择投影矩阵。
    12. glLoadIdentity();
    13. //重置投影矩阵。
    14. gluPerspective( 45.0, (GLfloat)width/(GLfloat)height, 0.1, 100.0 );
    15. //建立透视投影矩阵。
    16. glMatrixMode( GL_MODELVIEW );
    17. //选择模型观察矩阵。
    18. glLoadIdentity();
    19. //重置模型观察矩阵。
    20. }
    21. void NeHe_11_Widget::paintGL()
    22. {
    23. int x, y;
    24. float float_x, float_y, float_xb, float_yb;
    25. //x、y是循环变量,float_x、float_y、float_xb、float_yb是用来将旗形的波浪分割成很小的四边形。
    26. glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
    27. //清楚屏幕和深度缓存。
    28. glLoadIdentity();
    29. //重置模型观察矩阵
    30. glTranslatef( 0.0, 0.0, -12.0 );
    31. glRotatef( xRot, 1.0, 0.0, 0.0 );
    32. glRotatef( yRot, 0.0, 1.0, 0.0 );
    33. glRotatef( zRot, 0.0, 0.0, 1.0 );
    34. glBindTexture( GL_TEXTURE_2D, texture[0] );
    35. glBegin( GL_QUADS );
    36. for ( x = 0; x < 44; x++ )
    37. { //沿X平面0-44循环(45点)
    38. for ( y = 0; y < 44; y++ )
    39. {
    40. //沿Y平面0-44循环(45点)
    41. float_x = float(x)/44.0;
    42. float_y = float(y)/44.0;
    43. float_xb = float(x+1)/44.0;
    44. float_yb = float(y+1)/44.0;
    45. //上面我们使用4个变量来存放纹理坐标。每个多边形(网格之间的四边形)分别映射了纹理的1/44 x 1/44部分。\
    46. 循环首先确定左下顶点的值,然后我们据此得到其他三点的值。
    47. glTexCoord2f( float_x, float_y );
    48. glVertex3f( points[x][y][0], points[x][y][1], points[x][y][2] );
    49. glTexCoord2f( float_x, float_yb );
    50. glVertex3f( points[x][y+1][0], points[x][y+1][1], points[x][y+1][2] );
    51. glTexCoord2f( float_xb, float_yb );
    52. glVertex3f( points[x+1][y+1][0], points[x+1][y+1][1], points[x+1][y+1][2] );
    53. glTexCoord2f( float_xb, float_y );
    54. glVertex3f( points[x+1][y][0], points[x+1][y][1], points[x+1][y][2] );
    55. //上面四个坐标分别为左下、左上、右上、右下。
    56. //上面几行使用glTexCoord2f()和 glVertex3f()载入数据。提醒一点:四边形是逆时针绘制的。这就是说,\
    57. 您开始所见到的表面是背面。后表面完全填充了,前表面由线条组成。
    58. //如果您按顺时针顺序绘制的话,您初始时见到的可能是前表面。也就是说您将看到网格型的纹理效果而不是完全填充的。
    59. }
    60. }
    61. glEnd();
    62. if ( wiggle_count == 2 )
    63. {
    64. for ( y = 0; y < 45; y++ )
    65. {
    66. hold = points[0][y][2];
    67. for ( x = 0; x < 44; x++ )
    68. {
    69. points[x][y][2] = points[x+1][y][2];
    70. }
    71. points[44][y][2] = hold;
    72. }
    73. wiggle_count = 0;
    74. }
    75. wiggle_count++;
    76. //上面所作的事情是先存储每一行的第一个值,然后将波浪左移一下,是图象产生波浪。存储的数值挪到末端以产生一个永无尽头\
    77. 的波浪纹理效果。然后重置计数器 wiggle_count以保持动画的进行。
    78. //上面的代码由NeHe(2000年2月)修改过,以消除波浪间出现的细小锯齿。
    79. xRot += 0.3;
    80. yRot += 0.2;
    81. zRot += 0.4;
    82. //标准的NeHe旋转增量.
    83. }
    84. //loadGLTextures()函数就是用来载入纹理的。
    85. void NeHe_11_Widget::loadGLTextures()
    86. {
    87. QImage tex,buf;
    88. if (!buf.load( ":/data/Tim.bmp" ))
    89. {
    90. qWarning( "Could not read image file, using single-color instead." );
    91. QImage dummy( 128, 128, QImage::Format_RGB32 );
    92. dummy.fill( Qt::green );
    93. buf = dummy;
    94. //如果载入不成功,自动生成一个128*128的32位色的绿色图片。
    95. }
    96. tex = QGLWidget::convertToGLFormat(buf);
    97. glGenTextures( 1, &texture[0] );
    98. glBindTexture( GL_TEXTURE_2D, texture[0] );
    99. glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
    100. glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
    101. glTexImage2D( GL_TEXTURE_2D, 0, 3, tex.width(), tex.height(), 0,
    102. GL_RGBA, GL_UNSIGNED_BYTE, tex.bits() );
    103. }
    104. //这里就是定时操作函数timerEvent(),执行的操作就是updateGL(),就是刷新窗口了,其实它也会调用paintGL(),所以就实现了每5毫秒刷新一次的动画效果。
    105. void NeHe_11_Widget::timerEvent(QTimerEvent*)
    106. {
    107. updateGL();
    108. }
    109. void NeHe_11_Widget::keyPressEvent( QKeyEvent *e )
    110. {
    111. switch ( e->key() )
    112. {
    113. case Qt::Key_Escape:
    114. close();
    115. }
    116. }

    四、运行效果

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

     

     

     

     五、资料

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

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

     

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

    下一篇:

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

  • 相关阅读:
    分销小程序开发|分销小程序要怎么吸引客户?
    3.Maven 环境搭建
    SSM+基于微信小程序的航空售票管理系统 毕业设计-附源码191111
    时序分解 | Matlab实现SSA-VMD麻雀算法优化变分模态分解时间序列信号分解
    独立显卡与集成显卡的区别介绍
    实战:Linux下静默安装DM达梦数据库
    蓝桥杯练习
    奖学金答辩注意事项
    Spring Boot : Webflux 和 MVC 性能对比
    基于数组寻址的SCL算法实现S7-1200_1500中的配方功能Recipe
  • 原文地址:https://blog.csdn.net/ifeng12358/article/details/126078917