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和顶点属性(顶点数据链接到顶点属性)
有些不同;
- uniform是全局的,必须在每个着色器程序对象中都是独一无二的,而且可以被任意着色器在任意阶段访问
- 无论把uniform值设置成什么,uniform会一直保存他们的数据,直到他们被重置或者更新。
通过在着色器中为类型和变量名添加uniform
关键字声明一个全局变量;
#version 330 core
out vec4 FragColor;
uniform vec4 uniformColor;
void main(){
FragColor= uniformColor;
}
上述unifrom值为空,如何为其添加数据?
-
找到着色器中uniform属性的索引/位置值
-
更新赋值
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);
上述流程:
-
glfwGetTime()获取运行的秒数;
-
使用sin函数让颜色在0.0到1.0之间改变;
-
将结果存储在greenValue;
-
利用glGetUniformLocation查询 uniformColor的位置值;
-
通过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);