1. 程式人生 > 實用技巧 >3.QOpenGLWidget-通過著色器來渲染漸變三角形

3.QOpenGLWidget-通過著色器來渲染漸變三角形

在上章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}
比如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) { }