在上章2.通過QOpenGLWidget繪制三角形,我們學習繪制三角形還是單色的,本章將為三角形每個頂點着色.
1.着色器描述
着色器的開頭總是要聲明版本,接着是輸入和輸出變量、uniform和main函數。每個着色器的入口點都是main函數,在這個函數中我們處理所有的輸入變量,並將結果輸出到輸出變量中。如果你不知道什么是uniform也不用擔心,我們后面會進行講解。
一個典型的着色器有下面的結構:
#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),其中矩陣我們會在之后的教程里再討論。
2.1 向量Vector
GLSL中的向量是一個可以包含有1、2、3或者4個分量的容器,分量的類型可以是前面默認基礎類型的任意一個。它們可以是下面的形式(n代表分量的數量):
類型
|
含義
|
vecn
|
包含n個float分量的默認向量
|
bvecn
|
包含n個bool分量的向量
|
ivecn
|
包含n個int分量的向量
|
uvecn
|
包含n個unsigned int分量的向量
|
dvecn
|
包含n個double分量的向量
|
大多數時候我們使用vecn,因為float足夠滿足大多數要求了。
一個向量的分量可以通過vec.x這種方式獲取,這里x是指這個向量的第一個分量。你可以分別使用.x、.y、.z和.w來獲取它們的第1、2、3、4個分量。GLSL也允許你對顏色使用rgba,或是對紋理坐標使用stpq訪問相同的分量。
向量這一數據類型也允許一些有趣而靈活的分量選擇方式,叫做重組(Swizzling)。重組允許這樣的語法:
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}
比如:
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; }
我們在片元着色器中聲明了一個uniform vec4的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) { }