OpenGL(3)之Qt窗口(QOpenGLWidget)

OpenGL(3)之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();
          ...
          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实例之间的共享,而无需进行任何步骤。

你可能感兴趣的:(Qt,OpenGL)