Qt創建一個OpenGL窗口

  • 2019 年 12 月 19 日
  • 筆記

點擊上方藍字可直接關注!方便下次閱讀。如果對你有幫助,可以點個在看,讓它可以幫助到更多同志~

一直以來想結合Qt學習OpenGL,但是自己查了一些資料後還是比較困惑,最近在網上找到了兩個資料鏈接,一個是NeHe的教程,相對來講舊一些;另一個是https://learnopengl.com/。我的目的是在Qt開發過程中,如果需要用到OpenGL相關的三維模型開發,可以快速上手,所以我選擇的是用Qt比較新的版本自己做一遍。至於內部圖形學 原理,則是邊寫程式邊學習,所以本教程比較適合初學者。在查看網上一些教程之後,大部分還是以前的Qt版本,所以打算用比較新的版本來改寫。

環境:Win10、 Qt 5.12 、MinGW

效果預覽:

1. 新建繼承QWidget的類MyGLWidget

之後進行如下改寫:

#include <QOpenGLWidget>  class MyGLWidget : public QOpenGLWidget{    Q_OBJECT  public:    MyGLWidget(QWidget *parent = nullptr,               bool windowFlag = false);      ~MyGLWidget();  protected:    void initializeGL();  void paintGL();  void resizeGL( int width, int height );    void keyPressEvent( QKeyEvent *e );  protected:    bool fullscreen;     //窗口是否處於全螢幕狀態};

因為QOpenGLWidget類已經內置了對OpenGL的處理,就是通過對initializeGL()、paintGL()和resizeGL()這個三個函數實現的,具體情況可以參考QOpenGLWidget類的文檔。附上鏈接:

https://doc.qt.io/qt-5/qopenglwidget.html#details

initializeGL()是用來初始化這個OpenGL窗口部件的,可以在裡面設定一些有關選項。paintGL()就是用來繪製OpenGL的窗口了,只要有更新發生,這個函數就會被調用。resizeGL()就是用來處理窗口大小變化這一事件的,width和height就是新的大小狀態下的寬和高了,另外resizeGL()在處理完後會自動刷新螢幕。

2. pro文件改寫

Qt MinGW編譯工具下需要鏈接庫 opengl32 和 glu32

pro中添加如下:

win32-g++ {    LIBS += -lopengl32 -lglu32}

我的Windows系統已經帶了opengl32庫和glu32庫,如果沒有的話需要去下載。

如果不鏈接opengl相關庫,編譯時會報出下面的錯誤

3. 各個函數實現

①頭文件的包含

#include <GL/glu.h>

#include <QKeyEvent>

②函數實現

MyGLWidget::MyGLWidget(QWidget *parent,                       bool windowFlag)    : QOpenGLWidget(parent){    fullscreen = windowFlag;    setGeometry( 0, 0, 640, 480 ); //設置窗口的位置,即左上角為(0,0)點,大小為640*480    //設置窗口的標題為「 goose's OpenGL Framework」    setWindowTitle( "A goose's OpenGL Framework" );      if ( fullscreen )      showFullScreen();}

void MyGLWidget::initializeGL()

{

//啟用smooth shading(陰影平滑)。陰影平滑通過多邊形精細的混合色彩,並對外部光進行平滑。我將在另一個教程中更詳細的解釋陰影平滑。

glShadeModel( GL_SMOOTH );

//這一行設置清除螢幕時所用的顏色。如果對色彩的工作原理不清楚的話,這裡簡單說明下。色彩值的範圍從0.0到1.0。0.0代表最黑的情況,1.0就是最亮的情況。glClearColor後的第一個參數是紅色,第二個是綠色,第三個是藍色。最大值也是1.0,代表特定顏色分量的最亮情況。最後一個參數是Alpha值。當它用來清除螢幕的時候,我們不用關心第四個數字。現在讓它為0.0。我會用另一個教程來解釋這個參數。

//通過混合三種原色(紅、綠、藍),您可以得到不同的色彩。希望您在學校里學過這些。因此,當您使用glClearColor(0.0, 0.0, 1.0, 0.0 ),您將用亮藍色來清除螢幕。如果您用glClearColor(0.5, 0.0, 0.0, 0.0 )的話,您將使用中紅色來清除螢幕。不是最亮(1.0),也不是最暗 (0.0)。要得到白色背景,您應該將所有的顏色設成最亮(1.0)。要黑色背景的話,您該將所有的顏色設為最暗(0.0)。

glClearColor( 0.0, 0.0, 0.0, 0.0 );

glClearDepth( 1.0 ); //設置深度快取

glEnable( GL_DEPTH_TEST ); //啟用深度測試

glDepthFunc( GL_LEQUAL ); //所作深度測試的類型

//上面這三行必須做的是關於depth buffer(深度快取)的。將深度快取設想為螢幕後面的層。深度快取不斷的對物體進入螢幕內部有多深進行跟蹤。我們本節的程式其實沒有真正使用深度快取,但幾乎所有在螢幕上顯示3D場景OpenGL程式都使用深度快取。它的排序決定那個物體先畫。這樣您就不會將一個圓形後面的正方形畫到圓形上來。深度快取是OpenGL十分重要的部分。

//真正精細的透視修正。這一行告訴OpenGL我們希望進行最好的透視修正。這會十分輕微的影響性能。但使得透視圖看起來好一點。

glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST );

}

//這個函數中,我們對OpenGL進行所有的設置。我們設置清除螢幕所用的顏色,打開深度快取,啟用smooth shading(陰影平滑),等等。這個常式直到OpenGL窗口創建之後才會被調用。

void MyGLWidget::paintGL(){    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); //清楚螢幕和深度快取    glLoadIdentity();                                 //重置當前的模型觀察矩陣}

//這個函數中包括了所有的繪圖程式碼。任何您所想在螢幕上顯示的東東都將在此段程式碼中出現。以後的每個教程中我都會在常式的此處增加新的程式碼。如果您對OpenGL已經有所了解的話,您可以在 glLoadIdentity()調用之後,函數返回之前,試著添加一些OpenGL程式碼來創建基本的形。如果您是OpenGL新手,等著我的下個教程。目前我們所做的全部就是將螢幕清除成我們前面所決定的顏色,清除深度快取並且重置場景。我們仍沒有繪製任何東西

void MyGLWidget::resizeGL(int width, int height){    if ( height == 0 )   //防止height為0    {      height = 1;    }    glViewport( 0, 0, GLint(width), GLint(height) ); //重置當前的視口(Viewport)    glMatrixMode( GL_PROJECTION );                   //選擇投影矩陣    glLoadIdentity();                                //重置投影矩陣    gluPerspective( 45.0, GLdouble(width)/GLdouble(height), 0.1, 100.0 );  //建立透視投影矩陣    glMatrixMode( GL_MODELVIEW );                   //選擇模型觀察矩陣    glLoadIdentity();                               //重置模型觀察矩陣}

上面幾行為透視圖設置螢幕。意味著越遠的東西看起來越小。這麼做創建了一個現實外觀的場景。此處透視按照基於窗口寬度和高度的45度視角來計算。0.1,100.0是我們在場景中所能繪製深度的起點和終點。

glMatrixMode(GL_PROJECTION)指明接下來的兩行程式碼將影響projection matrix(投影矩陣)。投影矩陣負責為我們的場景增加透視。glLoadIdentity()近似於重置。它將所選的矩陣狀態恢復成其原始狀態。調用glLoadIdentity()之後我們為場景設置透視圖。

glMatrixMode(GL_MODELVIEW)指明任何新的變換將會影響 modelview matrix(模型觀察矩陣)。模型觀察矩陣中存放了我們的物體訊息。最後我們重置模型觀察矩陣。如果您還不能理解這些術語的含義,請別著急。在以後的教程里,我會向大家解釋。只要知道如果您想獲得一個精彩的透視場景的話,必須這麼做。

這個函數的作用是重新設置OpenGL場景的大小,而不管窗口的大小是否已經改變(假定您沒有使用全螢幕模式)。甚至您無法改變窗口的大小時(例如您在全螢幕模式下),它至少仍將運行一次——在程式開始時設置我們的透視圖。OpenGL場景的尺寸將被設置成它顯示時所在窗口的大小。

void MyGLWidget::keyPressEvent(QKeyEvent *e){    switch ( e->key() ){//如果按下了F2鍵,那麼螢幕是否全螢幕的狀態就切換一次。然後再根據需要,顯示所要的全螢幕窗口//者普通窗口。    case Qt::Key_F2:         fullscreen = !fullscreen;      if ( fullscreen )      {        showFullScreen();      }      else      {        showNormal();        setGeometry( 0, 0, 640, 480 );      }      update();      break;  //如果按下了Escape(Esc)鍵,程式退出    case Qt::Key_Escape:      close();    }}

4. main函數設置

int main(int argc, char *argv[]){    QApplication a(argc, argv);      bool fs = false;      //是否全螢幕為false  //這裡彈出一個消息對話框,讓用戶選擇是否使用全螢幕模式    switch( QMessageBox::information( nullptr,          "Start FullScreen?",          "Would You Like To Run In Fullscreen Mode?",          QMessageBox::Yes,          QMessageBox::No | QMessageBox::Default ) )      {      case QMessageBox::Yes:        fs = true;        break;      case QMessageBox::No:        fs = false;        break;      }  //創建一個MyGLWidget對象    MyGLWidget w(nullptr,fs);    w.show();      return a.exec();            //開啟Qt的事件循環}

5. 程式運行效果

①對話框介面,選擇No

②運行效果

螢幕左上角

6. 小結

主要是對別人寫好的程式做了Qt版本的提升;

這裡面很多術語我也不懂,無法建立起一個完整的框架,我是打算邊寫程式邊建立框架;

對於程式中 文字解釋的排版歡迎大家多提出寶貴的意見,感謝!