Shader
本节学习OpenGL中Shader的使用并将其抽象为类,简要介绍OpenGL所使用的着色器语言GLSL。
一、什么是Shader?
参考维基百科中对Shader的定义:着色器 - 维基百科,自由的百科全书 (wikipedia.org)
计算机图形学领域中,着色器(英语:shader)是一种计算机程序,原本用于进行图像的浓淡处理(计算图像中的光照、亮度、颜色等),但近来,它也被用于完成很多不同领域的工作,比如处理CG特效、进行与浓淡处理无关的视频后期处理、甚至用于一些与计算机图形学无关的其它领域。[1]
使用着色器在图形硬件上计算渲染效果有很高的自由度。尽管不是硬性要求,但目前大多数着色器是针对GPU开发的。GPU的可编程绘图管线已经全面取代传统的固定管线,可以使用着色器语言对其编程。构成最终图像的像素、顶点、纹理,它们的位置、色相、饱和度、亮度、对比度也都可以利用着色器中定义的算法进行动态调整。调用着色器的外部程序,也可以利用它向着色器提供的外部变量、纹理来修改这些着色器中的参数。
二、OpenGL中的Shader
在OpenGL中,我们使用GLSL语言进行Shader的编写,GLSL是为图形计算量身定制的,包含一些针对向量和矩阵操作的有用特性。
Shader的开头必须声明版本,接着添加输入和输出变量、uniform和main函数。每个着色器的入口点都是main函数,该函数中处理所有的输入变量,并将结果输出到输出变量中。
一般情况下,一个Shader中会包含以下内容:
#version version_number
in type in_variable_name;//输入变量
in type in_variable_name;
out type out_variable_name;//输出变量
uniform type uniform_name;//全局变量
int main()
{
// 处理输入并进行一些图形操作
...
// 输出处理过的结果到输出变量
out_variable_name = weird_stuff_we_processed;
}
对于顶点着色器,其对应的输入变量也就是in后面的变量成为顶点属性(Vertex Attribute),可以声明的顶点属性是有上限的,一般由硬件决定,可以通过GL_MAX_VERTEX_ATTRIBS来获取:
int nrAttributes;
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &nrAttributes);
std::cout << "Maximum nr of vertex attributes supported: " << nrAttributes << std::endl;
通常情况下它至少会返回16个。
二、GLSL中的数据类型
GLSL的数据类型可以来指定变量的种类。GLSL中包含默认基础数据类型:int、float、double、uint和bool。此外GLSL也有两种容器类型,分别是向量(Vector)和矩阵(Matrix)。
2.1. 向量
| 类型 | 含义 |
|---|---|
vecn |
包含n个float分量的默认向量 |
bvecn |
包含n个bool分量的向量 |
ivecn |
包含n个int分量的向量 |
uvecn |
包含n个unsigned int分量的向量 |
dvecn |
包含n个double分量的向量 |
一个向量的分量可以通过vec.x获取,这里x是指这个向量的第一个分量。也可以分别使用.x、.y、.z和.w来获取它们的第1、2、3、4个分量。GLSL允许对颜色使用rgba,或是对纹理坐标使用stpq访问相同的分量。
2.2. 向量的重组
重组方式如下面所示:
vec2 someVec;
vec4 differentVec = someVec.xyxx;
vec3 anotherVec = differentVec.zyw;
vec4 otherVec = someVec.xxxx + anotherVec.yxzy;
要注意两边向量的长度必须相同。
同时也可以将一个向量作为参数传给另一个向量的构造函数来减少需求参数的数量:
vec2 vect = vec2(0.5, 0.7);
vec4 result = vec4(vect, 0.0, 0.0);
vec4 otherResult = vec4(result.xyz, 1.0);
三、着色器的输入和输出
GLSL通过in 和 out 两个关键字来声明用于输入和输出的变量,只要变量能够匹配,数据就能够沿着管线传输。
对于顶点着色器,它的输入是从顶点数据中获取的。为了定义顶点数据的管理方式,使用location 来指定输入变量对应的顶点属性,例如在之前的小节当中 layout(location = 0)指定了顶点的顶点坐标属性。
对于片元着色器,最终的输出一定是一个vec4的颜色变量,因为片元着色器的目标就是计算最终输出的颜色,如果没有输出颜色,OpenGL会默认把你的物体渲染为黑色或者白色(像上节中的在没有添加shader情况下渲染出的三角形为黑色的)。
如果想要在顶点着色器和片元着色器之间传输数据,要在顶点着色器中定义out变量,在片元着色器中定义in变量,变量的类型和名字必须完全相同,这样OpenGL运行的时候就会把两个变量链接到一起。
四、Uniform定义的全局变量
Uniform定义的变量可以在任何着色器中进行访问,并且在定义之后一直保持。
简单尝试一下,首先在 Fragment Shader 中我们声明一个uniform变量用来控制输出颜色
std::string FragmentSrc = R"(
#version 330 core
layout(location = 0) out vec4 Fragcolor;
in vec3 v_Position;
uniform vec4 u_Color;
void main()
{
Fragcolor = u_Color;
}
)";
在shader中定义了uniform变量还不够,因为还没有指定变量存储的数据,因此需要在程序中对其进行指定,在 Render Loop 中,首先创建了一个随时间变化的颜色值用于传给uniform变量:
//Set Uniform
float timeValue = glfwGetTime(); //获取运行时间
float colorValue = (sin(timeValue) / 2.f) + 0.5f;//颜色随时间变化,并归一化到 (0,1)之间
GLint location = glGetUniformLocation(ShaderProgram, "u_COlor");
glUseProgram(ShaderProgram);
glUniform4f(location, 0.0f, colorValue, 0.0f, 1.0f);
使用函数glUniform4f来指定vec4类型的uniform变量,指定之前,我们首先需要知道要指定的uniform变量的位置,所以使用函数 glGetUniformLocation来获取之前定义的uniform变量的位置,第一个参数指定使用的着色器程序,第二个参数指定要获取的uniform变量的名称,注意:该名称必须和你定义的名称完全相同,否则会找不到对应的位置,返回-1。
更新一个uniform之前你必须先调用glUseProgram,因为是在当前激活的着色器程序中设置uniform的。
对于
glUniform

最低0.47元/天 解锁文章
3万+

被折叠的 条评论
为什么被折叠?



