学习记录------Opengl着色器
主页 - LearnOpenGL CN (learnopengl-cn.github.io) 的OpenGL学习记录
主页 - LearnOpenGL CN (learnopengl-cn.github.io) 的OpenGL学习记录
主页 - LearnOpenGL CN (learnopengl-cn.github.io) 的OpenGL学习记录
首先了解着色器之前先要知道Opengl图形渲染管线的流程,这里就先用图表示
图片来源[你好,三角形 - LearnOpenGL CN (learnopengl-cn.github.io)](https://learnopengl-cn.github.io/01 Getting started/04 Hello Triangle/)
着色器
首先解决着色器是什么的问题
着色器(Shader)是运行在GPU上的小程序,一种在渲染管线上某个特定部分运行的小程序。
它是通过一种GLSL类C语言编写的小程序。
着色器的特点:
- 用于将输入转化为输出的程序。
- 着色器之间是独立的程序,之间是互不通信的,唯一通信的方式是通过输入和输出的变量
GLSL
GLSL全称叫OpenGL Shader Language ,一种类C语言程序,内置了针对图形计算的一些特性,比如向量和矩阵操作的特性。
程序的大致结构如下:
// 开头声明版本
#version version_number
// 输入变量
in type in_variable_name;
in type in_variable_name;
// 输出变量
out type out_variable_name;
// 这种变量后面详细说明, 可以理解为一种从CPU中应用向GPU发送的着色器数据的方式
uniform type uniform_name;
// 程序的入口main函数
int main()
{
// 处理输入并进行一些图形操作
...
// 输出处理过的结果到输出变量
out_variable_name = weird_stuff_we_processed;
}
关于顶点着色器的一些细节:
-
输入变量也叫顶点属性(Vertex Attribute),这东西是有上限的,一般由硬件决定。
-
OpenGL要16个包含4个分量的顶点属性是可用的。
-
可以通过GL_MAX_VERTEX_ATTRIBS来查询。
// 使用前要将OpenGL的函数的东西初始化完成, 否者可能会导致空指针异常
int nrAttributes;
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &nrAttributes);
std::cout << "Maximum nr of vertex attributes supported: " << nrAttributes << std::endl;
通常至少会返回16,大部分情况下足够使用。
数据类型
作为类C语言包含有基础的数据类型int
、float
、double
、uint
和bool
。
同时它还内置两种容器 向量 Vector
和矩阵 Matrix
。
下面介绍两种容器。
向量
GLSL中的向量又有几种表现形式如下
类型 | |
---|---|
vecn | 包含n 个float分量的默认向量 |
bvecn | 包含n 个bool分量的默认向量 |
ivecn | 包含n 个int分量的默认向量 |
uvecn | 包含n 个unsigned int分量的默认向量 |
dvecn | 包含n 个double分量的默认向量 |
常用vecn
,因为float的精度应该都满足大部分需求了
作为容器肯定有获取容器内部元素的方法,可以通过 vec.x
获取第一个分量,以此类推 .x .y .z .w 获取对应的 1,2,3,4的分量。
作为着色器语言当然还提供了访问颜色的rgba
,或访问纹理坐标的stpq
通过这些容器分别访问它们的分量。
向量的灵活的操作
提供了灵活的分量选择方式,叫做重组(Swizzling)。重组允许这样的语法:
vec2 someVec;
vec4 differentVec = someVec.xyxx;
vec3 anotherVec = differentVec.zyw;
vec4 otherVec = someVec.xxxx + anotherVec.yxzy;
上面列举的方法都是可以的,通过任意组合赋值给信的向量,只要原来的向量有这些分量即可,比如你不能从 vec2
获取 .z
分量 因为它本身只有.x
和 .y
。
同时还可以把向量作为参数传递给其他的向量的构造函数。
vec2 vect = vec2(0.5, 0.7);
vec4 result = vec4(vect, 0.0, 0.0);
vec4 otherResult = vec4(result.xyz, 1.0);
向量的灵活性可以让我们使用在各种的输入输出上。
矩阵
…教程占时留在后面,没讲后面学完了再来补充
输入输出
着色器之前重要的通信手段,每一个着色器都有相应的输入和输出,通过输入和输出的方式才能让着色器之间进行数据交流和传递,
in
和out
这两个关键字来实现。只要out
声明的输出变量和下一个阶段执行的着色器中in
声明的输入变量名称相同,它就会进行传递。但是在顶点着色器和片段着色器中会有些不同。顶点着色器应该接收的是一种特殊形式的输入,否则就会效率低下。顶点着色器的输入特殊在,它从顶点数据中直接接收输入。
为了定义顶点数据该如何管理,我们使用
location
这一元数据指定输入变量,这样我们才可以在CPU上配置顶点属性。我们已经在前面的教程看过这个了,
layout (location = 0)
。顶点着色器需要为它的输入提供一个额外的layout
标识,这样我们才能把它链接到顶点数据。如果没有在片段着色器设置输出的颜色,OpenGL会默认为物体渲染为黑色或者白色
总结来说就是,想要着色器通信就必须要定义一个输出变量和给下一个着色器定义一个输入变量,且这两个变量的名称相同,只有这样OpenGL才会把这两个变量链接在一起。
话不多说上代码
顶点着色器
#version 330 core
layout (location = 0) in vec3 aPos; // 位置变量的属性位置值为0
out vec4 vertexColor; // 为片段着色器指定一个颜色输出
void main()
{
gl_Position = vec4(aPos, 1.0); // 注意我们如何把一个vec3作为vec4的构造器的参数
vertexColor = vec4(0.5, 0.0, 0.0, 1.0); // 把输出变量设置为暗红色
}
片段着色器
#version 330 core
out vec4 FragColor;
in vec4 vertexColor; // 从顶点着色器传来的输入变量(名称相同、类型相同)
void main()
{
FragColor = vertexColor;
}
片段着色器就可以获取到顶点着色器中的vertexColor
变量了。
Uniform
Uniform是一种从CPU中的应用向GPU中的着色器发送数据的方式,但uniform和顶点属性有些不同。
关于Uniform对象的特点总结:
- Uniform是全局对象,全局的所以它也是唯一的,同时全局还意味着,它可以被着色器程序的任意着色器在任意阶段访问。
- 无论你把uniform值设置成什么,uniform会一直保存它们的数据,直到它们被重置或更新。
总结一下着色器 - learnopengl-cn.github.io关于Uniform的例子代码
#version 330 core
out vec4 FragColor;
uniform vec4 ourColor; // 在OpenGL程序代码中设定这个变量
void main()
{
FragColor = ourColor;
}
上述代码是跑在GPU中的着色器,在这着色器中定义了ourColor
的全局变量,我们可以通过glGetUniformLocation
来获取该变量,如果没找到则返回-1,再通过glUniform4f
改变该变量,同时需要注意的是 获取变量时没有要求你调用glUseProgram使用着色器,但是在你改变该变量时一定要在glUseProgram之后使用。
// 这段代码是一段让图像变色的代码
float timeValue = glfwGetTime();
float greenValue = (sin(timeValue) / 2.0f) + 0.5f;
// 查找到对象是返回 该对象的位置
int vertexColorLocation = glGetUniformLocation(shaderProgram, "ourColor");
glUseProgram(shaderProgram);
// 将对象的位置传递给glUniform4f
glUniform4f(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f);
上述代码就是在C++中改变着色器中Uniform变量的方式,可以理解上述代码是跑在CPU中的,而着色器是跑在GPU中的,通过glGetUniformLocation查找shaderProgram着色器程序中的Uniform变量,在通过glUniform4f改变该变量的值。
OpenGL的接口如下:
// location 表明要修改的变量的位置
// 当我们使用glGetUniformLocation返回的就是该变量的位置
// v1,v2,v3,v4 就是容器对应的值
glUniform4f(GLint location,GLfloat v1,GLfloat v2,GLfloat v3,GLfloat v4)
总结
总结下来就是着色器可以理解为跑在GPU的程序,给GPU运行的程序,通过OpenGL提供的接口将程序丢给GPU来运行,极大的减少CPU的负担,提高渲染的效率等。
上述主要提到了两个比较重要的着色器,顶点着色器和片段着色器,这两个着色器尤为重要。