纯属个人主观感受,OpenGL spec 使用甚至发明的一些术语十分令人费解,就比如 shader,字面意义与专业功能有所疏离。Shade 作为名词,意思是不同颜色之间的过渡或渐变,典型使用是一部电影名"50 Shades of Grey"。显然在 OpenGL 中 shade 是作动词用的,理解为产生作为名词的 shades 的动作/过程,那么,对于 OpenGL 渲染流水线中 Fragment Shader 阶段而言它是合适的,然而对 Vertex Shader,则很难让人在脑子里就二者形成关联--等等!,fragment 不也正是另一个虚头八脑的术语么?
在 GLSL 中定义了这样几个所谓的 Storage Qualifier:const
, attribute
, uniform
, varying
。第一个沿用 C++,没毛病。attribute
经过前几天的折腾,已接受:变量绑定到 vertex attribute array,作为 vertex shader 的输入,只能在 vertex shader 中使用。但是,uniform
?varying
?黑人表情。
varying
说,varying
变量是从 vertex shader 的输出,输入 fragment shader。这课的第一个示例程序,用 varying
在 vertex shader 中定义一个颜色变量,给它写死一个值;转到 fragment shader 中,把传过来的颜色直接赋给 fragment,成为屏幕上最终呈现出来的颜色。Vertex shader:
C++
...
varying mediump vec4 a_color;
void main()
{
...
a_color = vec4(.5, 0, 0, 1);
}
以及 fragment shader:
C++
...
precision mediump float;
varying vec4 a_color;
void main()
{
gl_FragColor = a_color;
}
关于 varying
,只有一点需要说明:在 vertex shader 和 fragment shader 中必须声明严格一致,包括类型和精度。
GLSL 规定整数和浮点型变量有高中低 3 档精度:highp
, mediump
, lowp
。不同精度的运算成本和结果的...精度不同。要点:
- Vertex shader 的缺省 精度为
highp
;fragment shader 没有缺省精度,每个变量必须显式声明 - 可以以
precision <precision> <type>;
的形式声明 shader 的默认 精度,例如上面 fragment shader 声明float
类型默认为mediump
,因此在下面声明varying
变量时省略了精度
上面 vertex shader 中 varying
变量显式声明为 mediump
,而 fragment shader 中通过默认精度的方式使得 varying
也是 mediump
,二者一致,满足 GLSL 的要求。
完整代码在 gitlab.com/sihokk/lear...
uniform
第二个例程演示 uniform
。uniform
变量在 shader 中只读,vertex shader 和 fragment shader 都可以访问。例程在 fragment shader 中定义了一个 uniform
颜色变量,该颜色赋值给 fragment 成为屏幕绘制颜色。通过在程序中修改 uniform
的值,产生颜色变化的运行效果。
Fragment shader:
C++
...
precision mediump float;
uniform vec4 a_color;
void main()
{
gl_FragColor = a_color;
}
程序代码:
C
static void render()
{
glClear(GL_COLOR_BUFFER_BIT);
GLuint prog = ...
GLint uniform_color = glGetUniformLocation(prog, "a_color");
GLfloat green = (GLfloat)sin(SDL_GetTicks() / 1000.0) / 2 + .5f;
glUniform4f(uniform_color, 0, green, 0, 1);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0);
}
这里将系统时间戳转换到 [0, 1] 区间的一个值,作为颜色的 G 通道分量。
首先通过 glGetUniformLocation()
取得 shader 中 uniform
的一个标识,然后调用 glUniform*()
函数对其赋值。这里调用 glUniform4f()
接收 4 个 float
型参数,将依次赋值给 uniform
颜色变量的 RGBA 分量。
有一点需要强调:调用 glUniform*()
前必须存在"当前 program",即:已经调用过 glUseProgram()
将某个有效的 program 投入使用。ES spec 声称 uniform
是 program local 的数据,我是这样理解的:调用 glUniform*()
设置 uniform
的值,直接写入了 program,不像 attribute
那样是保存为 OpenGL 状态,而当 shader 执行时,从 OpenGL 状态中读取数据。
程序运行时,正方形颜色反复绿色渐变(2^8 Shades of Green?)。完整代码在 gitlab.com/sihokk/lear...
Rasterizer 插值
比如绘制一个三角形,程序输入给 vertex shader 的只是 3 个顶点的属性(如:坐标、颜色...),Rasterizer 却生成了三角形所能覆盖的屏幕像素范围内的很多个"点",称为 fragment,包括三角形的 3 条边上以及内部的所有点。这些点的属性(再次:包括坐标、颜色...),rasterizer 是用简单的线性插值算法计算出来的。
例如,指定三角形 3 个顶点的颜色分别是红绿蓝,rasterizer 最终产生的完整图形将是像下面这样的渐变色:
顶点属性数据定义是:
C
const GLfloat vertices[] = {
// Position (x, y) , Color (r, g, b)
-.5f, -.5f, 1.f, 0, 0, //
.5f, -.5f, 0, 1.f, 0, //
0, .5f, 0, 0, 1.f, //
};
共 3 个顶点,每个顶点包含 5 项数据,包括坐标(x/y)和颜色(r/g/b)。Vertex shader 中定义了 2 个 attribute
,分别是顶点坐标和颜色:
C++
...
attribute vec4 a_pos;
attribute mediump vec4 a_color;
varying mediump vec4 vertex_color;
void main()
{
gl_Position = a_pos;
vertex_color = a_color;
}
看到颜色 attribute
通过 varying
传递给 fragment shader。
程序中将这 2 个 attribute
绑定到 VBO:
C
GLint attr_pos = glGetAttribLocation(prog, "a_pos");
GLint attr_color = glGetAttribLocation(prog, "a_color");
const GLsizei stride = 5 * sizeof(GLfloat);
glEnableVertexAttribArray(attr_pos);
glVertexAttribPointer(attr_pos, 2, GL_FLOAT, GL_FALSE, stride, 0);
glEnableVertexAttribArray(attr_color);
glVertexAttribPointer(attr_color, 3, GL_FLOAT, GL_FALSE, stride, (void *)(2 * sizeof(GLfloat)));
坐标和颜色保存在同一个 attribute array 中,也即同一个 VBO。每个顶点包含 5 项数据,因此 stride
就是 5 项数据的字节数(第 4 行)。
坐标数据位于开头,因此偏移量为 0(第 7 行);而颜色数据则需要偏移坐标 x/y 2 项数据的字节数(第 10 行)。
完整程序代码:gitlab.com/sihokk/lear...