• Qt OpenGL 蒙板


    这次教程中,我们教介绍OpenGL的蒙板技术。到目前为止,我们已经学会如何使用alpha混合,把一个透明物体渲染到屏幕上了,但有时使用它看起来并不是那么的复合我们的心意。使用蒙板技术,将会使图像按照我们设定的蒙板位置精确地绘制。

    直到现在,我们在把图像加载到屏幕上时都没有檫除背景色,因为这样简单高效,但是效果并不总是很好。大部分情况下,把纹理混合到屏幕,纹理不是太少就是太多。当我们使用精灵图时,我们不希望背景从精灵的缝隙中透出光来;但在显示文字时,我们又希望文字的间隙可以显示背景色。

    基于上述原因,我们需要使用“掩模”。使用“掩膜”需要两个步骤,首先我们在场景上放置黑白相间的纹理,白色代表透明部分,黑色代表不透明部分。接着我们使用一种特殊的混合方式,只有在黑色部分上的纹理才会显示在场景中。

    程序运行时效果如下:

    下面进入教程:

    我们这次将在第06课代码的基础上修改代码,总体上并不会太难,希望大家能理解蒙板技术,这技术真的很好用。首先打开myglwidget.h文件,将类声明更改如下:

    1. 1 #ifndef MYGLWIDGET_H
    2. 2 #define MYGLWIDGET_H
    3. 3
    4. 4 #include <QWidget>
    5. 5 #include <QGLWidget>
    6. 6
    7. 7 class MyGLWidget : public QGLWidget
    8. 8 {
    9. 9 Q_OBJECT
    10. 10 public:
    11. 11 explicit MyGLWidget(QWidget *parent = 0);
    12. 12 ~MyGLWidget();
    13. 13
    14. 14 protected:
    15. 15 //3个纯虚函数的重定义
    16. 16 void initializeGL();
    17. 17 void resizeGL(int w, int h);
    18. 18 void paintGL();
    19. 19
    20. 20 void keyPressEvent(QKeyEvent *event); //处理键盘按下事件
    21. 21
    22. 22 private:
    23. 23 bool fullscreen; //是否全屏显示
    24. 24 bool m_Masking; //是否使用"12px;">掩模"
    25. 25 bool m_Scene; //控制绘制哪一层
    26. 26
    27. 27 GLfloat m_Rot; //控制纹理滚动
    28. 28 QString m_FileName[5]; //图片的路径及文件名
    29. 29 GLuint m_Texture[5]; //储存五个纹理
    30. 30 };
    31. 31
    32. 32 #endif // MYGLWIDGET_H

    本文福利,费领取Qt开发学习资料包、技术视频,内容包括(C++语言基础,Qt编程入门,QT信号与槽机制,QT界面开发-图像绘制,QT网络,QT数据库编程,QT项目实战,QT嵌入式开发,Quick模块,面试题等等)↓↓↓↓↓↓见下面↓↓文章底部点击费领取↓↓

    我们增加了两个布尔变量m_Masking和m_Scene来控制是否开启“ 掩模”以及绘制哪一个场景。然后我们增加一个控制图形滚动旋转的变量m_Rot,当然要去掉之前控制旋转的变量。最后把m_FileName和m_Texture变成长度为5的数组,因为我们需要载入5个纹理。

    接下来,我们打开myglwidget.cpp,在构造函数中对新增变量进行初始化,比较简单,大家参照注释理解,不多作解释,具体代码如下:

    1. 1 MyGLWidget::MyGLWidget(QWidget *parent) :
    2. 2 QGLWidget(parent)
    3. 3 {
    4. 4 fullscreen = false;
    5. 5 m_Masking = true;
    6. 6 m_Scene = false;
    7. 7
    8. 8 m_FileName[0] = "D:/QtOpenGL/QtImage/Logo.bmp"; //纹理0
    9. 9 m_FileName[1] = "D:/QtOpenGL/QtImage/Mask1.bmp"; //<span style="font-size:12px;">掩模</span>纹理1,作为"12px;">掩模"使用
    10. 10 m_FileName[2] = "D:/QtOpenGL/QtImage/Image1.bmp"; //纹理1
    11. 11 m_FileName[3] = "D:/QtOpenGL/QtImage/Mask2.bmp"; //<span style="font-size:12px;">掩模</span>纹理2,作为"12px;">掩模"使用
    12. 12 m_FileName[4] = "D:/QtOpenGL/QtImage/Image2.bmp"; //纹理2
    13. 13
    14. 14 QTimer *timer = new QTimer(this); //创建一个定时器
    15. 15 //将定时器的计时信号与updateGL()绑定
    16. 16 connect(timer, SIGNAL(timeout()), this, SLOT(updateGL()));
    17. 17 timer->start(10); //10ms为一个计时周期
    18. 18 }

    然后,我们略微修改下initializeGL()函数,就是载入5个位图并转换成纹理,不多解释了,具体代码如下:

    1. 1 void MyGLWidget::initializeGL() //此处开始对OpenGL进行所以设置
    2. 2 {
    3. 3 //载入位图并转换成纹理
    4. 4 for (int i=0; i<5; i++){
    5. 5 m_Texture[i] = bindTexture(QPixmap(m_FileName[i]));
    6. 6 }
    7. 7 glEnable(GL_TEXTURE_2D); //启用纹理映射
    8. 8
    9. 9 glClearColor(0.0f, 0.0f, 0.0f, 0.0f); //黑色背景
    10. 10 glShadeModel(GL_SMOOTH); //启用阴影平滑
    11. 11 glClearDepth(1.0); //设置深度缓存 <pre name="code" class="cpp"><pre name="code" class="cpp">
    12. 12 }

    继续,我们要进入最有趣的paintGL()函数,当然这也是重点,具体代码如下:

    1. 1 void MyGLWidget::paintGL() //从这里开始进行所以的绘制
    2. 2 {
    3. 3 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //清除屏幕和深度缓存
    4. 4 glLoadIdentity(); //重置模型观察矩阵
    5. 5 glTranslatef(0.0f, 0.0f, -2.0f); //移入屏幕2.0单位
    6. 6
    7. 7 glBindTexture(GL_TEXTURE_2D, m_Texture[0]); //选择Logo纹理
    8. 8 glBegin(GL_QUADS); //绘制纹理四边形
    9. 9 glTexCoord2f(0.0f, -m_Rot+0.0f);
    10. 10 glVertex3f(-1.1f, -1.1f, 0.0f);
    11. 11 glTexCoord2f(3.0f, -m_Rot+0.0f);
    12. 12 glVertex3f(1.1f, -1.1f, 0.0f);
    13. 13 glTexCoord2f(3.0f, -m_Rot+3.0f);
    14. 14 glVertex3f(1.1f, 1.1f, 0.0f);
    15. 15 glTexCoord2f(0.0f, -m_Rot+3.0f);
    16. 16 glVertex3f(-1.1f, 1.1f, 0.0f);
    17. 17 glEnd();
    18. 18
    19. 19 glEnable(GL_BLEND); //启用混合
    20. 20 glDisable(GL_DEPTH_TEST); //禁用深度测试
    21. 21
    22. 22 if (m_Masking) //是否启用"12px;">掩模"
    23. 23 {
    24. 24 glBlendFunc(GL_DST_COLOR, GL_ZERO); //使用黑白"12px;">掩模"
    25. 25 }
    26. 26
    27. 27 if (m_Scene)
    28. 28 {
    29. 29 glTranslatef(0.0f, 0.0f, -1.0f); //移入屏幕1.0单位
    30. 30 glRotatef(m_Rot*360, 0.0f, 0.0f, 1.0f); //绕z轴旋转
    31. 31
    32. 32 if (m_Masking) //"12px;">掩模"是否打开
    33. 33 {
    34. 34 glBindTexture(GL_TEXTURE_2D, m_Texture[3]); //选择第二个"12px;">掩模"纹理
    35. 35 glBegin(GL_QUADS); //开始绘制四边形
    36. 36 glTexCoord2f(0.0f, 0.0f);
    37. 37 glVertex3f(-1.1f, -1.1f, 0.0f);
    38. 38 glTexCoord2f(1.0f, 0.0f);
    39. 39 glVertex3f(1.1f, -1.1f, 0.0f);
    40. 40 glTexCoord2f(1.0f, 1.0f);
    41. 41 glVertex3f(1.1f, 1.1f, 0.0f);
    42. 42 glTexCoord2f(0.0f, 1.0f);
    43. 43 glVertex3f(-1.1f, 1.1f, 0.0f);
    44. 44 glEnd();
    45. 45 }
    46. 46
    47. 47 glBlendFunc(GL_ONE, GL_ONE); //把纹理2复制到屏幕上
    48. 48 glBindTexture(GL_TEXTURE_2D, m_Texture[4]); //选择第二个纹理
    49. 49 glBegin(GL_QUADS); //绘制四边形
    50. 50 glTexCoord2f(0.0f, 0.0f);
    51. 51 glVertex3f(-1.1f, -1.1f, 0.0f);
    52. 52 glTexCoord2f(1.0f, 0.0f);
    53. 53 glVertex3f(1.1f, -1.1f, 0.0f);
    54. 54 glTexCoord2f(1.0f, 1.0f);
    55. 55 glVertex3f(1.1f, 1.1f, 0.0f);
    56. 56 glTexCoord2f(0.0f, 1.0f);
    57. 57 glVertex3f(-1.1f, 1.1f, 0.0f);
    58. 58 glEnd();
    59. 59 }
    60. 60 else
    61. 61 {
    62. 62 if (m_Masking) //"12px;">掩模"是否打开
    63. 63 {
    64. 64 glBindTexture(GL_TEXTURE_2D, m_Texture[1]); //选择第一个"12px;">掩模"纹理
    65. 65 glBegin(GL_QUADS); //绘制四边形
    66. 66 glTexCoord2f(m_Rot+0.0f, 0.0f);
    67. 67 glVertex3f(-1.1f, -1.1f, 0.0f);
    68. 68 glTexCoord2f(m_Rot+4.0f, 0.0f);
    69. 69 glVertex3f(1.1f, -1.1f, 0.0f);
    70. 70 glTexCoord2f(m_Rot+4.0f, 4.0f);
    71. 71 glVertex3f(1.1f, 1.1f, 0.0f);
    72. 72 glTexCoord2f(m_Rot+0.0f, 4.0f);
    73. 73 glVertex3f(-1.1f, 1.1f, 0.0f);
    74. 74 glEnd();
    75. 75 }
    76. 76
    77. 77 glBlendFunc(GL_ONE, GL_ONE); //把纹理1复制到屏幕
    78. 78 glBindTexture(GL_TEXTURE_2D, m_Texture[2]); //选择第一个纹理
    79. 79 glBegin(GL_QUADS); //绘制四边形
    80. 80 glTexCoord2f(m_Rot+0.0f, 0.0f);
    81. 81 glVertex3f(-1.1f, -1.1f, 0.0f);
    82. 82 glTexCoord2f(m_Rot+4.0f, 0.0f);
    83. 83 glVertex3f(1.1f, -1.1f, 0.0f);
    84. 84 glTexCoord2f(m_Rot+4.0f, 4.0f);
    85. 85 glVertex3f(1.1f, 1.1f, 0.0f);
    86. 86 glTexCoord2f(m_Rot+0.0f, 4.0f);
    87. 87 glVertex3f(-1.1f, 1.1f, 0.0f);
    88. 88 glEnd();
    89. 89 }
    90. 90
    91. 91 glEnable(GL_DEPTH_TEST); //启用深度测试
    92. 92 glDisable(GL_BLEND); //禁用混合
    93. 93
    94. 94 m_Rot += 0.002f; //增加调整纹理滚动旋转变量
    95. 95 if (m_Rot > 1.0f)
    96. 96 {
    97. 97 m_Rot -= 1.0f;
    98. 98 }
    99. 99 }

    函数一开始,清除背景色,重置矩阵,把物体移入屏幕2.0单位。接着我们选择logo纹理,绘制纹理四边形,注意到我们调用glTexCoord选择纹理坐标时,有的数是大于1.0的,这时候OpenGL默认截取小数部分进行处理,这样就可以得到无缝的循环纹理(具体效果大家看上面的图或自己运行程序时再看)。然后我们启用混合并禁用深度测试。

    接着我们需要根据m_Masking的值设置是否使用“掩模”,如果是,我们需要设置相应的混合因子。一个“掩模”只是一幅绘制到屏幕的纹理图片,但只有黑色和白色,白色的部分代表透明,黑色的部分代表不透明。我们设置的混合因子GL_DST_COLOR、GL_ZERO使得任何纹理(OpenGL并不知道这是不是“掩模”)黑色的部分会变为黑色,白色的部分会保持原来的颜色,就是变成透明,透过了原来的颜色。

    然后我们检查是绘制哪一个场景(图层),true绘制第二层,false绘制第一层。true时先开始绘制第二层,为了不使得它看起来太大,我们把它移入屏幕1.0单位,并把它按m_Rot的值绕z轴旋转。接着我们检查m_Marking的值,如果为true,我们就把“掩模”绘制到屏幕上,当我们完成这个操作时,将会看到一个镂空的纹理出现在屏幕上。然后我们变换混合因子GL_ONE、GL_ONE,这次我们告诉OpenGL把任何黑色部分对应的像素复制到屏幕,这样看起来纹理就像被镂空一样贴在屏幕上。要注意的是,我在变换了混合因子后才选择的纹理。如果我们没有使用 “掩模”,我们的图像将与屏幕颜色融合。

    下面我绘制第一层与第二层的绘制基本相同,不多解释了。最后我们启用深度测试,禁用混合,然后增加m_Rot变量,如果大于1.0,把它的值减去1.0。

    最后我们修改一下键盘控制函数,就是加上了空格和M键作为切换键,很简单不多解释了,具体代码如下:

    1. 1 void MyGLWidget::keyPressEvent(QKeyEvent *event)
    2. 2 {
    3. 3 switch (event->key())
    4. 4 {
    5. 5 case Qt::Key_F1: //F1为全屏和普通屏的切换键
    6. 6 fullscreen = !fullscreen;
    7. 7 if (fullscreen)
    8. 8 {
    9. 9 showFullScreen();
    10. 10 }
    11. 11 else
    12. 12 {
    13. 13 showNormal();
    14. 14 }
    15. 15 updateGL();
    16. 16 break;
    17. 17 case Qt::Key_Escape: //ESC为退出键
    18. 18 close();
    19. 19 break;
    20. 20 case Qt::Key_Space: //空格为场景(图层)的切换键
    21. 21 m_Scene = !m_Scene;
    22. 22 break;
    23. 23 case Qt::Key_M: //M为是否"掩膜"的切换键
    24. 24 m_Masking = !m_Masking;
    25. 25 break;
    26. 26 }
    27. 27 }

    现在就可以运行程序查看效果了!


    一点内容的补充:上面我们提到当调用glTexCoord选择纹理坐标时,如果大于1.0,OpenGL默认截取小数部分进行处理。其实这只是OpenGL默认的处理模式:GL_REPEAT。对于纹理坐标大于1.0,OpenGL有以下几种处理模式:

    GL_CLAMP - 截取

    GL_REPEAT - 重复(OpenGL默认的模式)

    GL_MIRRORED_REPEAT - 镜像重复

    GL_CLAMP_TO_EDGE - 忽略边框截取

    GL_CLAMP_TO_BORDER - 带边框的截取

    我们可以利用glTexParameter函数来进行模式的转换,如:x方向的转换为glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP),变换模式只需更改第三个参数。而第二参数代表方向,GL_TEXTURE_WRAP_S代表x方向,GL_TEXTURE_WRAP_T代表y方向,GL_TEXTURE_WRAP_R代表z方向。

    本文福利,费领取Qt开发学习资料包、技术视频,内容包括(C++语言基础,Qt编程入门,QT信号与槽机制,QT界面开发-图像绘制,QT网络,QT数据库编程,QT项目实战,QT嵌入式开发,Quick模块,面试题等等)↓↓↓↓↓↓见下面↓↓文章底部点击费领取↓↓

  • 相关阅读:
    行政管理专业如何选择合适的毕业论文题目?
    最前端|一文详解Vue3.x 中 hooks 函数封装和使用
    Hadoop性能调优建议
    【视觉基础篇】10 # 图形系统如何表示颜色?
    linux 学习:查找命令 find | grep
    Salesforce LWC学习(四十六) 自定义Datatable实现cell onclick功能
    微信小程序中复制文本
    嵌入式学习-FreeRTOS-Day3
    如何一键重装win7系统?重装win7系统详细教程
    正向代理——流量代理
  • 原文地址:https://blog.csdn.net/m0_73443478/article/details/128204167