0%

shader

shader简介

shader程序是OpenGL渲染管线中的某个步骤,他运行在GPU上。shader程序之间是隔离的,他们之间进行通信的唯一方式就是接受上游shader的输出作为自己的输入并输出新的数据作为下游的输入。

GLSL

shader程序是有GLSL这种编程语言编写的,shader总是以一个版本声明开始,接下来就是一系列的输入输出变量和uniform,然后就是main函数,每个shader程序都是以main函数作为程序入口,在main函数中处理输入变量并为输出变量赋值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#version version_number
in type in_variable_name;
in type in_variable_name;

out type out_variable_name;

uniform type uniform_name;

void main()
{
// process input(s) and do some weird graphics stuff
...
// output processed stuff to output variable
out_variable_name = weird_stuff_we_processed;
}

每个shader的输入变量也叫做vertex attribute,由于硬件的限制,我们在shader中声明的vertex attribute的数量是有上限的,OpenGL保证至少可以声明16个4维变量,最大数量可以通过查询GL_MAX_VERTEX_ATTRIBS来获取。

1
2
3
int nrAttributes;
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &nrAttributes);
std::cout << "Maximum nr of vertex attributes supported: " << nrAttributes << std::endl;

虽然大多数情况下最多值就等于最小值16。

Types

GLSL 有和C语言一样的基础类型: int, float, double, uint and bool。另外GLS也有两种container类型: vectorsmatrices

Vectors

GLSL中的vector类型是可以容纳1、2、3 或者 4 个基本类型元素的container。它们有以下几种声明形式form (n 代表元素的个数):

  • vecn: the default vector of n floats.
  • bvecn: a vector of n booleans.
  • ivecn: a vector of n integers.
  • uvecn: a vector of n unsigned integers.
  • dvecn: a vector of n double components.

我们可以使用 .x, .y, .z and .w 来分别获取到第一到第四个元素。大多数情况下,我们使用float类型的vecn就足够了。

vertor类型有一些很方便的赋值和构造方式:

1
2
3
4
5
6
7
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);

Matrix

//TODO: 看完后序章节后补全矩阵这部分。

Ins And Outs

每个shader程序使用inout关键字来标识输入输出,如果下一个shader的输入变量和上一个shader的输出变量名一致,OpenGL就会将变量的值传递过去(这个逻辑是在链接shader program的时候完成的)。

但是vertex shader和fragment shader有一些特殊的地方。因为vertex是渲染管线的第一个阶段,它的输入由用户来定义。为了说明vertex shader的输入是如何组织的,我们在vertex shader中定义输入的时候还需要使用location来说明:

1
layout (location=0) in vec3 pos;

然后用户在CPU中定义输入的来源数据。

fragment shader要求必须有一个vec4类型的颜色输出变量,因为fragment shader的作用就是生成最后像素的颜色值。如果你没有定义这个颜色输出,OpenGL最后输出的可能就是纯黑色或者纯白色。

So if we want to send data from one shader to the other we’d have to declare an output in the sending shader and a similar input in the receiving shader. When the types and the names are equal on both sides OpenGL will link those variables together and then it is possible to send data between shaders (this is done when linking a program object).

Uniforms

uniforms是另外一种从CPU传递数据到shader程序的方式,uniform和vertex attribute不同,它是全局性质的,也就是说,uniform变量在shader program之间是隔离的,但是在shader program中的每个shader之间是共享的。另外一旦uniform变量被赋值,它的值就会一直保持这个,直到下一次被重置或者重新赋值。定义uniform变量,我们只需在定义变量时使用uniform关键字即可:

1
uniform vec4 outColor;

如果你你定义了uniform变量但是没有在任何地方使用,GLSL编译器或最终会将这个变量移除。

uniform变量的赋值可以在glsl中赋值,也可以在CPU中赋值,在CPU中赋值时,需要先获取到uniform变量的location,然后才能赋值:

1
2
3
int vertexColorLocation = glGetUniformLocation(shaderProgram, "ourColor");
glUseProgram(shaderProgram);
glUniform4f(vertexColorLocation, 0.0f, 1.0, 0.0f, 1.0f);
注意,在CPU端获取uniform变量时不需要先调用glUseProgram,但是为uniform变量赋值的之前则必须先调用glUseProgram。

fragment interpolation

OpenGL在渲染一个primitive时,在rasterization阶段,一个primitive最终会对应到屏幕中的若干个像素,也即是若干个fragmen,rasterization阶段会决定这些fragment的位置,基于这个位置,fragment shader中的所有输入变量的值将会是对应primitive中各顶点输出值的线性插值结果。