Open之旅(4):着色器簡介

title: OpenGL(4)之着色器初探
date: 2020-06-30 18:10
category: 圖形學
tags: opengl
鏈接:OpenGL(4)之着色器初探

前言

着色器(Shader)是運行在GPU上的小程序,服務於OpenGL渲染管線的各個着色階段【處理階段(頂點着色階段,細分着色階段,幾何着色階段,片元着色階段)和一個計算着色階段】,是一種將輸入轉換爲輸出的程序,他們之間不能相互通信,
唯一的溝通就是通過輸入in和輸出out

1.GLSL是什麼?

GLSL是一種類C語言寫成的,被GPU識別,因此是爲圖形計算量聲定製的,包含對向量和矩陣操作的特性;

着色器的開頭必須要聲明版本,然後輸入和輸出變量,uniform(全局共享變量)和main函數;

每個着色器的入口點都是main函數,在這個函數中處理所有的輸入變量,並將結果輸出到輸出變量中,通過輸入輸出,即可完成不同着色器語言之間的通信。

#version version_numner core 
in type in_variable_name;
in type in_variable_name;

out type out_variable_name;

uniform type unform_name;

int main(){

	//... 處理輸入並進行一些圖形操作
	//...

	// 輸出處理過的結果到輸出變量
	out_variable_name = fun;
}

每個輸入變量也叫頂點屬性(Vertex Attribute),能聲明的頂點屬性是有上限的,
一般由硬件來決定,OpenGL確保至少有16個包含4分量(x,y,z,w)的頂點屬性可用;可以查詢GL_MAX_VERTEX_ATTRIBS來獲取具體的上限值。
#define GL_MAX_VERTEX_ATTRIBS 0x8869=34921

int nAttributes;
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS,&nAttributes);
std::cout<<"Maximum nAttributes of vertex attributes supported:"<<nAttributes<<std::endl;

2.GLSL中的數據類型

  • 基礎類型:int、float、double、uint、bool

  • 容器類型:Vector(向量)、Matrix(矩陣)

3.向量介紹

GLSL向量是一個可以包含1、2、3或者4個分量的容器,分量的類型可以是前面默認基礎類型的任意一個,默認的是float類型分量

類型 含義
vecn 包含n個float分量的默認向量
bvecn 包含n個bool分量的向量
ivecn 包含n個int分量的向量
uvecn 包含n個unsigned int分量的向量
dvecn 包含n個double分量的向量

一個向量的分量可以通過vec.x方式獲取,x指的是向量的第一個分量,
也可以通過vec.xy取出第一個第二個分量,vec.yz取出第二個第三個分量。

向量的重組

vec2 vec11;

vec4 combineVec = vec11.xyxx;

vec3 llaVec = combineVec.xyw;

vec4 allVec = vec11.xxxx+llaVec.yzxx;

可以使用上面四種向量自由組合搭配來創建一個和原來向量一樣長的(同類型)的新向量,只有原來的向量有這些分量即可。

vec2 ver = vec2(0.4,0.1);

vec4 res = vec4(ver,0.1,1.0);

vec4 ares = vec4(res.xyz,1.0);

4.關於GLSL中的輸入和輸出概念

每個着色器都有輸入和輸出,這樣才能進行數據的交流和傳遞,
GLSL定義了in和out關鍵字來實現,只要一個輸出變量與下一個着色器階段的輸入匹配,就會傳遞下去。

爲了定義頂點數據該如何管理,使用location這一個元數據指定輸入變量,這樣纔可以在CPU上配置頂點屬性。
layout (location =0),頂點着色器的輸入需要額外的layout標識,以便我們可以將其與頂點數據鏈接。

注意
如果從一個着色器向另外一個着色器發送數據,必須在發送方着色器中聲明一個輸出變量A,在接收方着色器中聲明一個類似的輸入B,當A和B的類型和名字都一樣時候,OpenGL就會把兩個變量鏈接到一起,它們之間就能發送數據了(glLinkProgram()連接程序對象時完成的);

看以下案例:

頂點着色器

#version 330 core

layout (location=0) in vec3 aPos; // 位置變量的屬性位置值爲0

out vec4 vectexColor; //爲片段着色器指定一個顏色輸出

void main(){

	gl_Position = vec4(aPos,1.0);//vec3 ---> vec4 
	vectexColor = vec4(0.5,0.0,0.0,1.0);// 輸出變量設置爲暗紅色
}

片段着色器

#version 330 core

out vec4 FragColor;

in vec4 vectexColor;//從頂點着色器傳遞過來的 (in)輸入變量 (名稱相同,類型相同)

void main(){

	FragColor = vectexColor; // 最終的片段會被着色爲vectexColor的定義的顏色
}

5.共享變量Uniform

Uniform是一種從CPU中的應用GPU中的着色器發送數據的方式,但是uniform和頂點屬性(頂點數據鏈接到頂點屬性)
有些不同;

  1. uniform是全局的,必須在每個着色器程序對象中都是獨一無二的,而且可以被任意着色器在任意階段訪問
  2. 無論把uniform值設置成什麼,uniform會一直保存他們的數據,直到他們被重置或者更新。

通過在着色器中爲類型和變量名添加uniform關鍵字聲明一個全局變量;

#version 330 core

out vec4 FragColor;

uniform vec4 uniformColor; 

void main(){
	FragColor= uniformColor;
}

上述unifrom值爲空,如何爲其添加數據?

  1. 找到着色器中uniform屬性的索引/位置值

  2. 更新賦值

float timeValue = glfwGetTime();

float greenValue = (sin(timeValue)/2.0f)+0.5f;

int vertexColorLocation = glGetUniformLocation(shaderProgram,"uniformColor");// 上述第一步:找到其索引

glUseProgram(shaderProgram);

glUniform4f(vertexColorLocation,0.0f,greenValue,0.0f,1.0f);

上述流程:

  1. glfwGetTime()獲取運行的秒數;

  2. 使用sin函數讓顏色在0.0到1.0之間改變;

  3. 將結果存儲在greenValue;

  4. 利用glGetUniformLocation查詢 uniformColor的位置值;

  5. 通過glUniform4f函數設置uniform值;

6.其他屬性

上節瞭解瞭如何填充VBO,配置頂點屬性指針以及如何把它們存儲到一個VAO裏面,把顏色填充到頂點數據中,將顏色數據添加到3個float值至vertices數組中

float vertices[] = {
	// position      //color
	0.4f, 0.4f,0.0f,  1.0f,0.0f,0.0f
	-0.3f,-0.4f,0.0f, 0.0f,1.0f,0.0f
	0.0f, 0.4f,0.0f,  0.0f,0.0f,1.0f  
}

由此可見,多了一組數據要發送給頂點着色器,因此需要進行相應的調整,使其能夠接收顏色值作爲一個頂點屬性輸入,同理可以通過layout標籤 但是此時指定的location=1 而不是0了,區分是位置變量屬性以及顏色變量屬性

#version 330 core
layout(location =0) in vec3 aPos;
layout (location =1 )in vec3 aColor;

out vec3 shareColor ; // 向片段着色器傳遞顏色

void main(){
	gl_Position = vec4(aPos, 1.0);
	shareColor = aColor;
}

可以看出這個時候沒有使用uniform進行傳值。

#version 330 core

out vec4 FragColor;

in vec3 shareColor;

void main(){
	FragColor = vec4(shareColor,1.0);
}


因爲添加了另一個頂點屬性,並且更新了VBO的內存,那麼就必須重新配置頂點屬性指針,更新後的VBO內存中的數據如下圖所示:

如此以來,color的頂點屬性步長以及偏移量也需要做適當修改

glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,6*sizeof(float),(void*)0);

glEnableVertexAttribPointer(0);

//(void*)(3*sizeof(float)) :sizeof(flaot) =4 
//3*4 因此偏移量爲12 如上圖,第一個R是偏移了12個字節單位
glVertextAttribPointer(1,3,GL_FLOAT,GL_FALSE,6*sizeof(float),(void*)(3*sizeof(float)));
glEnableVertexAttribPointer(1);

參考

LearnOpenGL

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章