OpenGL(三)之Qt窗口(QOpenGLWidget)

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實例之間的共享,而無需進行任何步驟。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章