欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

OpenGL(三)之Qt窗口(QOpenGLWidget)

程序员文章站 2022-04-03 23:20:13
...

OpenGL(三)之Qt窗口(QOpenGLWidget详解)

在上一篇中窗口类渲染OpenGL部件是基于QWindow,但在实际应用开发中比较常用的窗口是基于QWidget(当然还有Qt Quick这里并不展开讲)。至于QWindowQWidget的联系,可以简略看一下这边博文从QWindow到QWidget(Qt5)

QOpenGLWidget 类详解

QOpenGLWidget是一个渲染OpenGL图形的窗口部件。 QOpenGLWidget提供的功能是把OpenGL 图形显示功能整合到Qt 应用程序当中。它非常容易使用:把我们的子类继承QOpenGLWidget,然后就可以像使用其它QWidget那样使用,除此之外,你也可以使用 QPainter和标准的OpenGL渲染命令。
QOpenGLWidget提供3个便利的虚函数,我们可以再我们的子类中重新实现内容,用来执行OpenGL的典型任务:

  • paintGL() : 渲染OpenGL的场景。当窗口需要被更新时,调用这个函数。
  • resizeGL(): 设置OpenGL的视口,投影矩阵等;当窗口改变大小时被调用,以及在第一次显示时会被调用(这是因为新建一个窗口时
    会自动发送重置窗口大小事件[resize event])
  • initializeGL() : 设置OpenGL的资源和状态。这个函数需要在paintGL()resizeGL()之前调用。

如果想在其它地方触发paintGL() 进行重绘,不直接调用paintGL() 函数。可以使用update()函数。QWidget::update();

当调用 initializeGL()paintGL()paintGL() ,窗口中OpenGL渲染的上下文使用的是当前的。如果需要在其它地方调用标注的OpenGL API的函数(例如:在我们自己窗口的构造函数或者在我们自己绘制函数中),我们需要先调用makeCurrent();

所有的渲染都发生在一个OpenGL framebuffer对象中,即幕后渲染。makeCurrent()确保它在上下文中绑定。在paintGL()的呈现代码中,创建和绑定额外的framebuffer对象时,请记住这一点,永远不要重新绑定ID为0的framebuffer。相反,调用defaultFramebufferObject()来获取应该绑定的ID。

在平台支持时,QOpenGLWidget允许使用不同OpenGL的版本以及配置文件。只要通过setFormat()函数来设置需要的格式。注意:在同一窗口中有多个QOpenGLWidget实例需要使用相同的格式,或者至少不能使它们的上下文不能共享。为了克服这个问题,最好使用QSurfaceFormat::setDefaultFormat() 来替代setFormat()函数。

注意】:在一些平台(例如macOS)上,当请求OpenGL核心配置(即上下文)时,在构造QApplication实例之前必须的调用QSurfaceFormat::setDefaultFormat(),。这是为了确保在使用正确的版本和配置文件创建所有内部上下文时,上下文之间的资源共享保持正常工作。

OpenGL Function Calls, Headers and QOpenGLFunctions

在进行OpenGL函数调用时,强烈建议避免直接调用函数。相反,更喜欢使用QOpenGLFunctions(在制作可移植应用程序时)或版本化变体(例如,QOpenGLFunctions_3_2_Core等,当针对现代的,仅限桌面的OpenGL时)。这样,应用程序将在所有Qt构建配置中正常工作,包括执行动态OpenGL实现加载的应用程序,这意味着应用程序不直接链接到GL实现,因此直接函数调用是不可行的。

在paintGL()中,当前场景(context)始终可以通过调用QOpenGLContext :: currentContext()来访问。从这个context中,可以通过调用QOpenGLContext :: functions()来检索已经初始化的,准备好使用的QOpenGLFunctions实例。为每个GL调用添加前缀的替代方法是从QOpenGLFunctions继承并在initializeGL()中调用QOpenGLFunctions :: initializeOpenGLFunctions()。

至于OpenGL头文件,请注意,在大多数情况下,不需要直接包含任何头文件,如GL.h.与OpenGL相关的Qt头文件将包含qopengl.h,后者将包含适用于系统的标头。这可能是OpenGL ES 3.x或2.0标头,可用的最高版本,或系统提供的gl.h.此外,作为OpenGL和OpenGL ES的Qt的一部分,提供了扩展头的副本(在某些系统上称为glext.h)。这些将在可行的情况下自动包含在平台上。这意味着来自ARB,EXT,OES扩展的常量和函数指针typedef自动可用。

实例一

直接继承 QOpenGLWidget,这里面就要例举上述三个函数具体需要实现什么功能。
QOpenGLContext::currentContext()->functions();从当前上下文中获取关联的OpenGL函数,只有获取这个函数的指针,我们调用OpenGL函数进行渲染才是有效的。

  class MyGLWidget : public QOpenGLWidget
  {
  public:
      MyGLWidget(QWidget *parent) : QOpenGLWidget(parent) { }

  protected:
      void initializeGL()
      {
          //设置渲染的上下文,加载着色器和其他资源,等等...
          QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions();
          
          f->glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
          ...
      }

      void resizeGL(int w, int h)
      {
          //更新投影矩阵和,设置视口等其它相关设置
          m_projection.setToIdentity();
          m_projection.perspective(45.0f, w / float(h), 0.01f, 100.0f);
          ...
      }

      void paintGL()
      {
          // 绘制场景
          QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions();
          f->glClear(GL_COLOR_BUFFER_BIT);
          ...
      }

  };

实例二

直接继承 QOpenGLWidget ,同时继承QOpenGLFunctions。这么做的目的只是为了简化代码,可以不用每次调用QOpenGLContext::currentContext()->functions();获取当前上下文的QOpenGLFunctions对象,需要任何函数直接调用就可以。不过在初始化函数需要先调用initializeOpenGLFunctions();可以使OpenGL的函数只关联到当前上下文,这样我们才能在当前上下文进行渲染等操作。

  class MyGLWidget : public QOpenGLWidget, protected QOpenGLFunctions
  {
  public:
      MyGLWidget(QWidget *parent) : QOpenGLWidget(parent) { }

  protected:
      void initializeGL()
      {
          initializeOpenGLFunctions();
          glClearColor(...);
          ...
      }

      void resizeGL(int w, int h)
      {
          //更新投影矩阵和,设置视口等其它相关设置
          m_projection.setToIdentity();
          m_projection.perspective(45.0f, w / float(h), 0.01f, 100.0f);
          ...
      }

      void paintGL()
      {
          // 绘制场景
          glClear(GL_COLOR_BUFFER_BIT);
          ...
      }

  };

实例三:上下文配置格式

  1. 配置格式:要获取与给定OpenGL版本或配置文件兼容的上下文,或请求深度和模具缓冲区,调用setFormat():
  QOpenGLWidget *widget = new QOpenGLWidget(parent);
  QSurfaceFormat format;
  format.setDepthBufferSize(24);
  format.setStencilBufferSize(8);
  format.setVersion(3, 2);
  format.setProfile(QSurfaceFormat::CoreProfile);
  //必须在显示之前调用
  widget->setFormat(format); 
  1. 对于OpenGL 3.0+上下文,当可移植性不重要时,我们可以使用下面这种方式来访问具体版本的的函数功能。
      ...
      void paintGL()
      {
          QOpenGLFunctions_3_2_Core *f = QOpenGLContext::currentContext()- >versionFunctions<QOpenGLFunctions_3_2_Core>();
          ...
          f->glDrawArraysInstanced(...);
          ...
      }
      ...
  1. 如上所述,全局地设置请求的格式,以便在应用程序的生命周期中应用于所有窗口和上下文,这样更简单、更健壮。
  int main(int argc, char **argv)
  {
      QApplication app(argc, argv);

      QSurfaceFormat format;
      format.setDepthBufferSize(24);
      format.setStencilBufferSize(8);
      format.setVersion(3, 2);
      format.setProfile(QSurfaceFormat::CoreProfile);
      QSurfaceFormat::setDefaultFormat(format);

      MyWidget widget;
      widget.show();

      return app.exec();
  }

与QGLWidget 的联系

传统的QtOpenGL模块(以QGL为前缀的类)提供了一个名为QGLWidget的widget。QOpenGLWidget旨在成为它的替代品。因此,特别是在新的应用程序中,一般建议使用QOpenGLWidget。

虽然API非常相似,但两者之间存在重要差异:QOpenGLWidget始终使用帧缓冲对象在屏幕外渲染。另一方面,QGLWidget使用原生窗口和曲面。后者在复杂的用户界面中使用它时会引起问题,因为根据平台,这种本机子窗口小部件可能具有各种限制,例如关于堆叠命令。QOpenGLWidget通过不创建单独的本机窗口来避免这种情况。

由于帧缓冲对象的支持,QOpenGLWidget的行为与QOpenGLWindow非常相似,更新行为设置为PartialUpdateBlit或PartialUpdateBlend。这意味着在paintGL()调用之间保留内容,以便可以进行增量渲染。使用QGLWidget(当然QOpenGLWindow具有默认的更新行为)通常不是这种情况,因为交换缓冲区会使后台缓冲区中的内容不确定。

注意:大多数应用程序不需要增量渲染,因为它们将在每次调用绘制时呈现视图中的所有内容。在这种情况下,在paintGL()中尽早调用glClear()非常重要。这有助于使用基于tile的架构的移动GPU认识到tile缓冲区不需要用framebuffer以前的内容重新加载。忽略clear调用可能会导致此类系统的性能显著下降

注意:避免在QOpenGLWidget上调用winId()。此功能触发创建本机窗口,从而降低性能并可能出现毛刺。

与QGLWidget的差异

除了framebuffer对象支持的主要概念差异之外,QOpenGLWidget和旧的QGLWidget之间存在许多较小的内部差异:

调用paintGL()时的OpenGL状态。 QOpenGLWidget通过glViewport()设置视口。 它不执行任何清算。
当开始绘画时通过QPainter清除。 与常规widget不同,QGLWidget默认为autoFillBackground的值为true。 然后,每次使用QPainter :: begin()时,它都会清除调色板的背景颜色。 QOpenGLWidget不遵循:autoFillBackground默认为false,就像任何其他widget一样。 唯一的例外是当用作QGraphicsView等其他小部件的视口时。 在这种情况下,autoFillBackground将自动设置为true,以确保与基于QGLWidget的视口兼容。

多重采样

要启用多采样,请在传递给setFormat()的QSurfaceFormat上设置请求样本的数量。在不支持它的系统上,请求可能会被忽略。

Context Sharing

当多个QOpenGLWidgets作为子组件添加到同一个*小部件时,它们的上下文将相互共享。这不适用于属于不同窗口的QOpenGLWidget实例。

这意味着同一个窗口中的所有QOpenGLWidgets都可以访问彼此共享的资源,比如纹理,而不需要额外的“全局共享”上下文,就像QGLWidget一样。

要在属于不同窗口的QOpenGLWidget实例之间建立共享,请在实例化QApplication之前设置Qt::AA_ShareOpenGLContexts应用程序属性。这将触发所有QOpenGLWidget实例之间的共享,而无需进行任何步骤。