日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

QT使用openGL绘制一个三角形

發(fā)布時間:2024/3/12 c/c++ 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 QT使用openGL绘制一个三角形 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

對于opengl的學(xué)習(xí)來說,繪制一個三角形是學(xué)習(xí)一種計算機語言時的一個hello world級的入門程序,個人覺得相比主流語言的helloworld,openGL的入門確實是有一些勸退,雖然說有不錯的教程,但簡明與全面不可兼得,很容易面對教程中一大堆概念和術(shù)語而摸不到頭腦,本文試圖用“相對”簡單和直觀的方式讓人成功的繪制出第一個三角形。對于使用QT的同學(xué),可以直接從文末的鏈接下載完整代碼,自己修改其中的參數(shù)觀察變化,這樣理解起來更快。希望能讓使用QT并且想學(xué)習(xí)openGL的人踏出第一步,而不是被畫一個三角形拒之門外。

文章目錄

    • 前言
    • 環(huán)境
    • 論繪制一個三角形都需要什么
        • 比較重要的組成部分
          • 各部分關(guān)系圖
          • 一種可行的繪制流程
    • 編寫代碼
      • 關(guān)于QT使用openGL
      • 按步驟來
        • 創(chuàng)建openGL程序
        • 編寫頂點著色器程序和片段著色器并添加到openGL程序中去
        • 創(chuàng)建并綁定VAO
        • 創(chuàng)建并綁定VBO
        • 將數(shù)據(jù)存入VBO
        • 告訴openGL該如何分配和使用數(shù)據(jù)
        • 繪制
      • 完整的MyWidget.cpp代碼
      • 繪制最終效果
    • 完整項目代碼

前言

本人也是初學(xué)openGL,而且代碼部分會出現(xiàn)QT封裝類和openGL原生函數(shù)混用的情況(QT的一些封裝對比原生確實方便一些,但是缺點是文檔不夠親民,示例代碼少,有時會不知該如何使用),對于并不使用QT的同學(xué)來說,可以僅參考各部分組成關(guān)系繪制流程,我覺得應(yīng)該還是有助于初期對openGL的理解的。

隔了一段時間回頭來修改了一下這篇文章,想讓他更加詳細親民。不過仔細想了想,感覺學(xué)習(xí)openGL最好的入門方法就是找一個在你的環(huán)境下可以編譯運行的繪制三角形或正方形的openGL代碼,結(jié)合著各種教程,修改其中參數(shù),觀察變化來理解其中的意義。
雖然只是繪制一個三角形,但是需要相對較多的準備知識,而且直到最后你能將這些知識組合起來并正確組織代碼之前,你無法得到任何的反饋,因為這個過程幾乎已經(jīng)沒法再分解了,你沒辦法先畫出一個點,再畫出一條線,進而畫出一個三角形,因為openGL的繪制過程并不是這種邏輯,如果你只是對著文字教程一點一點編寫代碼,可能很久都沒法得到正確的結(jié)果,也不知道是哪里出錯了,如此既會浪費時間,又會有挫敗感。所以如果你對這個入門感到頭痛,那么就先去找一份適合自己的可用代碼吧。

本文不會介紹的很全面,只是希望能夠通過此文讓同學(xué)們跨過opengl的門檻,更加輕松的去理解和學(xué)習(xí)其他人的教程,這里也給出兩個教程鏈接:

一個比較全面系統(tǒng)的教程: LearnOpenGL CN

和我一樣使用QT的同學(xué)在學(xué)習(xí)上方教程時如果想知道QT做了哪些封裝以及如何使用相應(yīng)的類時,可以參考這里:基于QT的openGL學(xué)習(xí),這一系列的主要問題是基本就是示例代碼,而沒有解釋。不過入了門之后直接看代碼可能反而比文字描述直觀,也是不錯的參考。

環(huán)境

Windows7
QT 5.10.1 (MSVC2017_x64)

論繪制一個三角形都需要什么

對于openGL來說,繪制一個三角形需要我們通過計算機語言向其提供 頂點顏色 的信息。

比較重要的組成部分

關(guān)于頂點和顏色信息的存儲:openGL使用簡稱為 VAO(Vertex Array Object,頂點數(shù)組對象) 的對象來存儲這些信息。
向VAO傳遞信息的過程中,我們會使用簡稱為 VBO(Vertex Buffer Object,頂點緩沖對象) 的對象。

對VAO中存儲的信息進行處理:openGL使用一個 程序(program) 對象來決定使用VAO中信息的方式并進行最終的繪制。程序包含所謂的 “著色器”(shader) ,你可以把著色器理解成是由openGL語言(基本獨立于你所使用的語言)編寫而成的程序。

一個能夠繪制圖形的openGL程序至少包含頂點著色器(Vertex Shader)片段著色器(Fragment Shader),其中頂點著色器會決定繪制頂點的位置,而片段著色器用來決定這些位置的顏色

上述的組成部分如果按照一定的流程全部正確設(shè)置完畢,我們就可以繪制出我們的第一個三角形了。

各部分關(guān)系圖

一種可行的繪制流程

當你熟悉了openGL的基礎(chǔ)知識之后,你會知道繪制流程的順序并不是固定的,只需滿足一些必要條件即可,但是現(xiàn)在知道這一點就可以了,然后暫且認為流程就是如下固定的,否則容易頭暈。

編寫代碼

了解了上述知識后,現(xiàn)在要做的就是學(xué)習(xí)如何通過代碼實現(xiàn)上述的步驟來繪制了,到現(xiàn)在為止可以說第一步只邁出了小半,因為openGL并沒有特別符合直覺和方便使用的函數(shù)接口,像如下

#include <openGL> void main() {setVertex(xxxx);setColor(xxxx);paint(); }

這樣的使用方法并不存在,必須結(jié)合前述知識去學(xué)習(xí)openGL存儲和處理數(shù)據(jù)的方法。

關(guān)于QT使用openGL

編寫一個自定義類,繼承QOpenGLWidgetQOpenGLFunctions即可,我們的繪制便會在這個Widget內(nèi)進行。
頭文件MyWidget.h示例如下:

#include <QOpenGLWidget> #include <QOpenGLFunctions> #include <QOpenGLVertexArrayObject> #include <QOpenGLShaderProgram> #include <QOpenGLBuffer>class MyWidget : public QOpenGLWidget,protected QOpenGLFunctions {Q_OBJECTpublic:MyWidget(QWidget* parent=0);~MyWidget();protected:void initializeGL() override;void paintGL() override;void resizeGL(int width, int height) override;private:QOpenGLShaderProgram* program;QOpenGLVertexArrayObject m_vao;QOpenGLBuffer m_vbo;int m_attr;int m_color;};

繼承后的類會包含initializeGL(),paintGL(),resizeGL()這三個需要重寫的函數(shù)。

顧名思義,initializeGL用來初始化各項openGL相關(guān)的部件,設(shè)置openGL程序,存儲數(shù)據(jù)等操作的代碼通常在此函數(shù)內(nèi)進行編寫,而不是在類的構(gòu)造函數(shù)中。

paintGL用來編寫繪制相關(guān)操作的代碼。

resizeGL用來在Widget尺寸發(fā)生改變時修改設(shè)置以得到預(yù)期的效果。

注意在此三個函數(shù)之外的自定義函數(shù)中調(diào)用openGL的函數(shù)功能時,通常需要先調(diào)用makeCurrent() 函數(shù)來獲得上下文(context)。

按步驟來

創(chuàng)建openGL程序

QT創(chuàng)建和綁定openGL程序比較簡單直觀

QOpenGLShaderProgram* program = new QOpenGLShaderProgram; program->bind();

但是這僅僅是創(chuàng)建了一個空的程序,如前述,想要繪制圖形,openGL程序中至少包含頂點著色器程序和片段著色器程序,于是我們接著往下來。

編寫頂點著色器程序和片段著色器并添加到openGL程序中去

著色器程序的名稱和后綴沒有固定要求,方便區(qū)分功能用途即可,你甚至可以直接在QT代碼中用字符串的形式編寫并傳入著色器程序,不過我覺得額外編寫文件好修改一些,這里便介紹從其他文件添加著色器程序的方法。

頂點著色器程序 triangle.vert:

#version 330 core layout (location = 0) in vec3 aPos; layout (location = 1) in vec3 aColor;out vec3 ourColor;void main() {ourColor = aColor;gl_Position =vec4(aPos, 1.0); }

第一行#version 330 core為openGL版本聲明,你可能會看到不含這一行的openGL程序代碼,變量的定義方法可能會與此不同

layout 和 location是什么現(xiàn)在可以不管,事實上沒有這兩個也沒關(guān)系。文章后面會有簡單解釋

in 代表此變量會由我們的程序傳入

vec3 代表3維向量的數(shù)據(jù)類型

aPos和aColor和ourColor是我們自己定義的變量名

out 代表此變量會由此著色器傳出給片段著色器 (其實著色器并不只有兩種,他們會按一定的順序?qū)?shù)據(jù)傳遞下去,由于我們只使用了兩種著色器程序,此處直接理解為傳給片段著色器即可) 供其使用。

gl_Position 是頂點著色器的內(nèi)建變量,是一個4維向量,前3維代表頂點的空間位置,第4維目前我們一律設(shè)置為1.0即可。后續(xù)我們將通過自己的程序,借助頂點著色器中定義的aPos變量將頂點的空間坐標傳遞進來。

openGL的繪圖空間為一個長寬高均為2的立方體:

只有在這個空間范圍內(nèi)的頂點才能夠被繪制出來。

片段著色器程序triangle.frag:

#version 330 core in vec3 ourColor; void main() {gl_FragColor = vec4(ourColor,1.0); }

in代表此變量由其他著色器程序傳入,在本文中會接收由頂點著色器傳入的ourColor變量,注意想要正確的傳出傳入變量,需要保證不同著色器中的變量名稱和類型一致,而且傳遞方向不要搞反。
gl_FrageColor為內(nèi)建變量,用來表示顏色,也是一個4維變量(4個分量分別代表RGBA:紅,綠,藍,α值),α值現(xiàn)在統(tǒng)一設(shè)置為1.0即可。

接下來就可以將以上兩個程序添加到program中了

//向program中添加頂點著色器if(!program->addShaderFromSourceFile(QOpenGLShader::Vertex,":/triangle.vert")){qDebug()<< (program->log());return;}//向program中添加片段著色器if(!program->addShaderFromSourceFile(QOpenGLShader::Fragment,":/triangle.frag")){qDebug()<< (program->log());return;}if(!program->link()){qDebug()<< (program->log());return;}

創(chuàng)建并綁定VAO

QT中VAO的創(chuàng)建和綁定也比較簡單

QOpenGLVertexArrayObject m_vao;m_vao.create();m_vao.bind();

創(chuàng)建并綁定VBO

基本同上

QOpenGLBuffer m_vbo;m_vbo.create();m_vbo.bind();

將數(shù)據(jù)存入VBO

注意我們并不會直接對VAO進行操作,VBO可以看做是某一項數(shù)據(jù),而VAO則是這些數(shù)據(jù)的集合,可以理解為在program從VBO中取數(shù)據(jù)時會將這些數(shù)據(jù)自動存入VAO,所以我們也需要在綁定VBO之前綁定一個VAO。
我們所需要的數(shù)據(jù)可以在代碼中使用一個靜態(tài)數(shù)組創(chuàng)建

static GLfloat vertices[] = {//我們所準備的需要提供給openGL的頂點數(shù)據(jù)// 位置 // 顏色0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, // 右下-0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, // 左下0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f // 頂部 };

這里應(yīng)該也算是個難理解的地方,這里數(shù)組中每一行代表一個頂點數(shù)據(jù),而其中每一行的前三個數(shù)代表空間坐標,后三個數(shù)代表顏色分量。
然而這種區(qū)分是我們自行規(guī)定的,對于VAO和VBO來說,目前他們就只是二進制數(shù)據(jù)而已,openGL本身并沒有規(guī)定各項數(shù)據(jù)必須以何種形式組織起來,我們將通過一些方式來告訴openGL如何來分配和使用這些數(shù)據(jù)。(這里可以在成功畫出三角形后再回頭來理解)
將這些數(shù)據(jù)存入VBO只需一行代碼

m_vbo.allocate(vertices, sizeof(vertices));

這個語句也在一定程度上表示vbo只關(guān)心數(shù)據(jù)的大小,你準備了一個數(shù)組,VBO把這個數(shù)組中的內(nèi)容一股腦復(fù)制進來,數(shù)據(jù)存儲就算完成了。這里跟memcpy函數(shù)有一定的相似性,他只管拷貝數(shù)據(jù),至于數(shù)據(jù)是什么意義,則需要程序員來控制。

告訴openGL該如何分配和使用數(shù)據(jù)

現(xiàn)在搬來我們之前寫好的頂點著色器程序

#version 330 core layout (location = 0) in vec3 aPos; layout (location = 1) in vec3 aColor;out vec3 ourColor;void main() {ourColor = aColor;gl_Position =vec4(aPos, 1.0); }


我們需要告訴頂點著色器程序,我們希望aPos這個變量中持有頂點的空間坐標信息,這是一個三維向量,所以我們讓他持有靜態(tài)數(shù)組中每一行的前三個數(shù)據(jù),這可以通過以下代碼來完成

int m_attr=program->attributeLocation("aPos");program->setAttributeBuffer(m_attr,GL_FLOAT, 0, 3,6*sizeof(GLfloat));program->enableAttributeArray(m_attr);

上方這種方式更加貼近原生openGL的方法,在QT中,你也可以使用下方這種更易于理解的方式(至少你暫時不用思考上面方法中那個整形變量是做什么的)

program->setAttributeBuffer("aPos",GL_FLOAT, 0, 3,6*sizeof(GLfloat));program->enableAttributeArray("aPos");

在這里解釋一下這個整形變量m_attr。openGL會把變量名和一個整型數(shù)字一一對應(yīng)起來,原生openGL的函數(shù)需要一個傳出參數(shù),用來告訴你變量名"aPos"應(yīng)該對應(yīng)哪個整型值。假設(shè)你現(xiàn)在需要一個變量"aPos",openGL通過這個函數(shù)告訴你,在我的內(nèi)部用數(shù)字"1"代表這個變量,那么在這之后,當你想要使用"aPos"時,你需要告訴openGL,我現(xiàn)在要操作"1"所代表的變量了。

還記得前面寫的layout (location=0)嗎?這里其實就是相當于手動分配了變量和數(shù)字的對應(yīng)關(guān)系。
比如layout (location = 0) in vec3 aPos;
就代表我們想讓數(shù)字"0"來代表"aPos"這個變量,這個屬于程序員與openGL的約定,當使用數(shù)字"0"時,雙方都明白所指的是什么。當你想使用aPos時,你告訴openGL我要使用"aPos"和我要使用"0"意思是一樣的。區(qū)別在于對于前者,openGL先要查找aPos所對應(yīng)的數(shù)字,所以事先手動分配數(shù)字并直接使用數(shù)字可以節(jié)省一步從而一定程度上提高性能。

setAttributeBuffer這個函數(shù)中的參數(shù)還是需要好好解釋一下的,而且主要是后三個參數(shù)


GL_FLOAT是用來告訴著色器VAO中的數(shù)據(jù)類型。
舉個相似的例子來說明著色器是如何看待這些數(shù)據(jù)的。
假設(shè)我們內(nèi)存中有16進制數(shù)據(jù)(0x)123456789ABC,這數(shù)據(jù)本身目前并沒有意義
那么我們用get(short,0,2,3sizeof(short))可以取出[12,34],[78,9A](16進制)兩組數(shù)據(jù)
get(short,0,2,2sizeof(short))可以取出[12,34],[56,78],[9A,BC]
get(short,2,2,2sizeof(short))可以取出[56,78],[9A,BC]
get(int,0,1,1sizeof(int))則可以取出[1234],[5678],[9ABC]三組數(shù)據(jù)

可以看到不同的參數(shù)會賦予數(shù)據(jù)不同的意義,而對于openGL的setAttributeBuffer函數(shù)來說,你要做的就是通過這個函數(shù)及其參數(shù)來確保openGL可以按照你的要求讀出正確的數(shù)據(jù)。
通過上述語句,openGL就知道了名為"aPos"的變量表示三組頂點數(shù)據(jù):(0.5f,-0.5f,0.0f),(-0.5f,-0.5f,0.0f)以及(0.0f,0.5f,0.0f)。

如果你理解了我們?nèi)绾瓮ㄟ^上述代碼告訴openGL在當前VBO的數(shù)據(jù)中取每行的前三個數(shù)據(jù)給aPos,那么每行后三個數(shù)據(jù)如何傳給aColor也就應(yīng)該清楚了

int m_color=program->attributeLocation("aColor");program->setAttributeBuffer(m_color,GL_FLOAT,3*sizeof(GLfloat),3,6*sizeof(GLfloat));program->enableAttributeArray(m_color);

注:其實你也可以通過兩個數(shù)組,兩個VBO分別來傳遞信息,兩種方式難理解的點不同,我覺得都是入門需要掌握的,但是這里先只給出這一種方式吧。

這里還是應(yīng)該多看幾遍,力求理解著色器程序是如何取到數(shù)據(jù)的,如果覺得講得不清楚可以留言,我會嘗試說得再細致一些。

至此,我們的openGL程序設(shè)置已經(jīng)完成,頂點位置和顏色數(shù)據(jù)也都存到了VAO中,接下來,終于可以在崩潰前進行激動人心的繪制了……

繪制

上述編碼基本都是在initializeGL()函數(shù)中編寫的,繪制通常在paintGL()函數(shù)中完成,繪制時需要設(shè)置完整的program(決定如何確定頂點和顏色)和VAO(其中存儲了繪制過程中所需要的數(shù)據(jù))
因此在繪制函數(shù)之前(一般為glDrawXXXX)確保我們綁定了正確的openGL程序和VAO。
于是在paintGL()函數(shù)中,我們可以編寫如下代碼:

program->bind();//綁定繪制所要使用的openGL程序m_vao.bind();//綁定包含openGL程序所需信息的VAOglDrawArrays(GL_TRIANGLES, 0, 3);//繪制

通過這個程序解釋glDrawArrays各項參數(shù)的意義既麻煩又不好理解,但是還是適當說明下。

GL_TRIANGLES 表示我們要繪制的是三角形(然而并不是說繪制矩形就有GL_RECTANGLE可以用…)可以認為這表示一種排列頂點的方式。

0 表示從第0個頂點開始繪制

3 表示我們一共要繪制3個頂點

至此,三角形的繪制可以說已經(jīng)結(jié)束了。后面會給出完整項目代碼的鏈接,當你成功繪制出三角形,并更進一步的可以參照其他教程畫出正方形,正方體等等圖形時,修改幾次其中的參數(shù)即可讓你有個直觀的認識。

完整的MyWidget.cpp代碼

#include "mywidget.h" #include <QDebug>static GLfloat vertices[] = {//我們所準備的需要提供給openGL的頂點數(shù)據(jù)// 位置 // 顏色0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, // 右下-0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, // 左下0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f // 頂部 };MyWidget::MyWidget(QWidget* parent):QOpenGLWidget(parent) {}MyWidget::~MyWidget() //建議在析構(gòu)函數(shù)中手動銷毀openGL相關(guān)的對象, //文檔中特意提到QT的回收機制難以保證回收所有openGL使用的資源 //不銷毀的話在關(guān)閉程序時可能會出現(xiàn)異常 {makeCurrent();m_vao.destroy();m_vbo.destroy();doneCurrent(); }void MyWidget::initializeGL() {initializeOpenGLFunctions();// 創(chuàng)建并綁定著色器程序program = new QOpenGLShaderProgram;program->bind();//向program中添加頂點著色器if(!program->addShaderFromSourceFile(QOpenGLShader::Vertex,":/triangle.vert")){qDebug()<< (program->log());return;}//向program中添加片段著色器if(!program->addShaderFromSourceFile(QOpenGLShader::Fragment,":/triangle.frag")){qDebug()<< (program->log());return;}if(!program->link()){qDebug()<< (program->log());return;}//創(chuàng)建并綁定VAOm_vao.create();m_vao.bind();//創(chuàng)建并綁定VBOm_vbo.create();m_vbo.bind();m_vbo.allocate(vertices, sizeof(vertices));//向VBO傳遞我們準備好的數(shù)據(jù)(本文件起始部分的靜態(tài)數(shù)組)//向頂點著色器傳遞其中定義為"aPos"的變量所需的數(shù)據(jù)m_attr=program->attributeLocation("aPos");program->setAttributeBuffer(m_attr,GL_FLOAT, 0, 3,6*sizeof(GLfloat));program->enableAttributeArray(m_attr);//向頂點著色器傳遞其中定義為"aColor"的變量所需的數(shù)據(jù)m_color=program->attributeLocation("aColor");program->setAttributeBuffer(m_color,GL_FLOAT,3*sizeof(GLfloat),3,6*sizeof(GLfloat));program->enableAttributeArray(m_color);program->release();//解綁程序}void MyWidget::paintGL() {//glClearColor(0.2f, 0.3f, 0.3f, 1.0f);//glClear(GL_COLOR_BUFFER_BIT);program->bind();//綁定繪制所要使用的openGL程序m_vao.bind();//綁定包含openGL程序所需信息的VAOglDrawArrays(GL_TRIANGLES, 0, 3);//繪制m_vao.release();//解綁VAOprogram->release();//解綁程序//update();//調(diào)用update()函數(shù)會執(zhí)行paintGL,現(xiàn)在繪制一個靜態(tài)的三角形可以不使用//也可以用定時器連接update()函數(shù)來控制幀率,直接在paintGL函數(shù)中調(diào)用update()大概是60幀 }void MyWidget::resizeGL(int width, int height) {}

繪制最終效果

如果你一切順利,你就可以得到自己通過openGL繪制的第一個三角形啦,效果如下:

完整項目代碼

完整的QT項目已上傳至github

希望同學(xué)們都能夠順利地邁出學(xué)習(xí)openGL的第一步!如果覺得本文中有寫的不清楚或者是錯誤的地方,歡迎留言指出。

總結(jié)

以上是生活随笔為你收集整理的QT使用openGL绘制一个三角形的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。