3.QOpenGLWidget-通過著色器來渲染漸變三角形
阿新 • • 發佈:2020-10-13
在上章2.通過QOpenGLWidget繪製三角形,我們學習繪製三角形還是單色的,本章將為三角形每個頂點著色.
1.著色器描述
著色器的開頭總是要宣告版本,接著是輸入和輸出變數、uniform和main函式。每個著色器的入口點都是main函式,在這個函式中我們處理所有的輸入變數,並將結果輸出到輸出變數中。如果你不知道什麼是uniform也不用擔心,我們後面會進行講解。一個典型的著色器有下面的結構:
2.1 向量Vector
GLSL中的向量是一個可以包含有1、2、3或者4個分量的容器,分量的型別可以是前面預設基礎型別的任意一個。它們可以是下面的形式(n代表分量的數量):
大多數時候我們使用vecn,因為float足夠滿足大多數要求了。
一個向量的分量可以通過vec.x這種方式獲取,這裡x是指這個向量的第一個分量。你可以分別使用.x、.y、.z和.w來獲取它們的第1、2、3、4個分量。GLSL也允許你對顏色使用rgba,或是對紋理座標使用stpq訪問相同的分量。
向量這一資料型別也允許一些有趣而靈活的分量選擇方式,叫做重組(Swizzling)。重組允許這樣的語法:
#version version_number
in type in_variable_name; in type in_variable_name; out type out_variable_name; uniform type uniform_name; //type:變數型別,是一個可以包含有1、2、3或者4個分量的容器,可以定義為float(vecn)、bool(bvecn)等型別,在第2節講述
int main() { // 處理輸入並進行一些圖形操作 ... // 輸出處理過的結果到輸出變數 out_variable_name = weird_stuff_we_processed; }
當我們特別談論到頂點著色器的時候,每個輸入變數也叫頂點屬性(老版本的Vertex Attribute)。我們能宣告的頂點屬性是有上限的,它一般由硬體來決定。
OpenGL確保至少有16個包含4分量的頂點屬性可用,但是有些硬體或許允許更多的頂點屬性,你可以查詢GL_MAX_VERTEX_ATTRIBS來獲取具體的上限:int nrAttributes;
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &nrAttributes);
std::cout << "Maximum nr of vertex attributes supported: " << nrAttributes << std::endl; //列印上限
2.資料型別
和其他程式語言一樣,GLSL有資料型別可以來指定變數的種類。GLSL中包含C等其它語言大部分的預設基礎資料型別:int、float、double、uint和bool。GLSL也有兩種容器型別,它們會在這個教程中使用很多,分別是向量(Vector)和矩陣(Matrix),其中矩陣我們會在之後的教程裡再討論。型別 | 含義 |
vecn | 包含n個float分量的預設向量 |
bvecn | 包含n個bool分量的向量 |
ivecn | 包含n個int分量的向量 |
uvecn | 包含n個unsigned int分量的向量 |
dvecn | 包含n個double分量的向量 |
vec2 someVec; vec4 differentVec = someVec.xyxx; vec3 anotherVec = differentVec.zyw; vec4 otherVec = someVec.xxxx + anotherVec.yxzy;你可以使用上面4個字母任意組合來建立一個和原來向量一樣長的(同類型)新向量,只要原來向量有那些分量即可;然而,你不允許在一個vec2向量中去獲取.z元素。我們也可以把一個向量作為一個引數傳給不同的向量建構函式,以減少需求引數的數量:
vec2 vect = vec2(0.5, 0.7); //初始化vect,設定第一分量為0.5、第二分量為0.7 vec4 result = vec4(vect, 0.0, 0.0); //初始化result,設定XYZW為0.5,0.7,0.0 ,0.0 vec4 otherResult = vec4(result.xyz, 1.0);
3.輸入與輸出
之前我們渲染的三角形只能固定設定一種顏色,比如我們想通過應用程式中根據不同情況來發送我們想渲染的顏色,該怎麼辦?使用uniform變數 3.1 Uniform Uniform是一種從CPU中的應用向GPU中的(vertex和fragment)著色器傳送資料的方式,但uniform和頂點屬性有些不同。 首先,uniform是全域性的(Global)。全域性意味著uniform變數必須在每個著色器程式物件中都是獨一無二的,而且它可以被著色器程式的任意著色器在任意階段訪問。它不能被shader程式修改.(shader只能用,不能改,只能等外部程式重新重置或更新),Uniform變數通過application呼叫函式glUniform()函式賦值的. 而glUniform()函式分為很多種,因為OpenGL由C語言編寫,但是C語言不支援函式過載,所以會有很多名字相同字尾不同的函式,glUniform大概格式為 :glUniform{1,2,3,4}{i,f,ub,ui,uiv,dv,v}比如glUniform1i()、glUniform4ui等,其中i表示32位整形,f表示32位浮點型,ub表示8位無符號byte,ui表示32位無符號整形,v表示指標型別。 比如:
glUniform4f(GLint location, GLfloat x, GLfloat y, GLfloat z, GLfloat w); //表示設定location位置的uniform變數值的xyzw分量3.2 通過uniform設定三角形顏色 接下來,我們在上章的三角形程式片元著色器中新增uniform變數,然後通過外部app來隨著時間動態設定三角形顏色. 頂點著色器
#version 330 core layout (location = 0) in vec3 aPos; // 位置變數的屬性位置值為0 void main() { gl_Position = vec4(aPos, 1.0); // 注意我們如何把一個vec3作為vec4的構造器的引數 }片元著色器
#version 330 core out vec4 FragColor; uniform vec4 ourColor; // 在OpenGL程式程式碼中設定這個變數 void main() { FragColor = ourColor; }我們在片元著色器中聲明瞭一個uniformvec4的ourColor,並把片元著色器的輸出顏色設定為uniform值的內容。 外部app程式 在外部app程式中設定顏色如下:
float timeValue = glfwGetTime();//獲取執行的秒數 float greenValue = (sin(timeValue) / 2.0f) + 0.5f; //讓顏色在0.0到1.0之間改變 int vertexColorLocation = glGetUniformLocation(shaderProgram, "ourColor");//查詢uniform ourColor的位置值 glUseProgram(shaderProgram); glUniform4f(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f); //設定顏色然後一個隨著時間動態變化的三角形就誕生了 4.實現一個漸變三角形 如下圖所示: 要實現這個三角形,需要3個顏色:左下(綠色),右下(紅色),頂部(藍色),這次我們同樣打算把顏色資料加進頂點資料中,所以頂點資料變為:
float vertices[] = { // 位置 // 顏色 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 // 頂部 };由於現在有更多的資料要傳送到頂點著色器,我們有必要去調整一下頂點著色器,使它能夠接收顏色值作為一個頂點屬性輸入,所以在頂點著色器程式碼中定義了一個aColor 頂點著色器
#version 330 core layout (location = 0) in vec3 aPos; //位置變數的屬性位置值為 0 layout (location = 1) in vec3 aColor; //顏色變數(傳送給fragment shader)的屬性位置值為 1 out vec3 ourColor; // 向片段著色器輸出一個顏色 void main() { gl_Position = vec4(aPos, 1.0); ourColor = aColor; // 將ourColor設定為我們從頂點資料那裡得到的輸入顏色 }片元著色器
#version 330 core out vec4 FragColor; in vec3 ourColor; //vertex shader 傳入的資料 void main() { FragColor = vec4(ourColor, 1.0); }
具體程式碼如下所示:
#include "myglwidget.h" #include <QtDebug> //GLSL3.0版本後,廢棄了attribute關鍵字(以及varying關鍵字),屬性變數統一用in/out作為前置關鍵字 #define GL_VERSION "#version 330 core\n" #define GLCHA(x) #@x //加單引號 #define GLSTR(x) #x //加雙引號 #define GET_GLSTR(x) GL_VERSION#x const char *vsrc = GET_GLSTR( layout (location = 0) in vec3 aPos; layout (location = 1) in vec3 aColor; out vec3 ourColor; void main() { gl_Position = vec4(aPos, 1.0); ourColor = aColor; } ); const char *fsrc =GET_GLSTR( out vec4 FragColor; in vec3 ourColor; void main() { FragColor = vec4(ourColor, 1.0); } ); myGlWidget::myGlWidget(QWidget *parent):QOpenGLWidget(parent) { } void myGlWidget::paintGL() { // 繪製 // glViewport(0, 0, width(), height()); glClear(GL_COLOR_BUFFER_BIT); // 渲染Shader vao.bind(); //繫結啟用vao glDrawArrays(GL_TRIANGLES, 0, 3); //繪製3個定點,樣式為三角形 vao.release(); //解綁 }
void myGlWidget::initializeGL() { // 為當前環境初始化OpenGL函式 initializeOpenGLFunctions(); glClearColor(1.0f, 1.0f, 1.0f, 1.0f); //設定背景色為白色 //1.建立著色器程式 program = new QOpenGLShaderProgram; program->addShaderFromSourceCode(QOpenGLShader::Vertex,vsrc); program->addShaderFromSourceCode(QOpenGLShader::Fragment,fsrc); program->link(); program->bind();//啟用Program物件 //2.初始化VBO,將頂點資料儲存到buffer中,等待VAO啟用後才能釋放 float vertices[] = { // 位置 // 顏色 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 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 // 頂部 顏色對應藍色 }; vbo.create(); vbo.bind(); //繫結到當前的OpenGL上下文, vbo.allocate(vertices, 18*sizeof(GLfloat)); vbo.setUsagePattern(QOpenGLBuffer::StaticDraw); //設定為一次修改,多次使用 //3.初始化VAO,設定頂點資料狀態(頂點,法線,紋理座標等) vao.create(); vao.bind(); // void setAttributeBuffer(int location, GLenum type, int offset, int tupleSize, int stride = 0); program->setAttributeBuffer(0, GL_FLOAT, 0, 3, 6 * sizeof(float)); //設定aPos頂點屬性 program->setAttributeBuffer(1, GL_FLOAT, 3 * sizeof(float), 3, 6 * sizeof(float)); //設定aColor頂點顏色 //offset:第一個資料的偏移量 //tupleSize:一個數據有多少個元素,比如位置為xyz,顏色為rgb,所以是3 //stride:步長,下個數據距離當前資料的之間距離,比如右下位置和左下位置之間間隔了:3個xyz值+3個rgb值,所以填入 6 * sizeof(float) program->enableAttributeArray(0); //使能aPos頂點屬性 program->enableAttributeArray(1); //使能aColor頂點顏色 //4.解綁所有物件 vao.release(); vbo.release(); } void myGlWidget::resizeEvent(QResizeEvent *e) { }