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;
}
我們在片元着色器中聲明了一個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) { }

 下章學習:4.QOpenGLWidget-對三角形進行紋理貼圖、紋理疊加

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM