OpenGL ES 2.0 笔记 #4:Shaders

纯属个人主观感受,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 中使用。但是,uniformvarying?黑人表情。

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

第二个例程演示 uniformuniform 变量在 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...

相关推荐
凌云行者3 天前
OpenGL入门004——使用EBO绘制矩形
c++·cmake·opengl
闲暇部落3 天前
Android OpenGL ES详解——模板Stencil
android·kotlin·opengl·模板测试·stencil·模板缓冲·物体轮廓
凌云行者6 天前
OpenGL入门003——使用Factory设计模式简化渲染流程
c++·cmake·opengl
凌云行者6 天前
OpenGL入门002——顶点着色器和片段着色器
c++·cmake·opengl
闲暇部落7 天前
Android OpenGL ES详解——裁剪Scissor
android·kotlin·opengl·窗口·裁剪·scissor·视口
彭祥.12 天前
点云标注工具开发记录(四)之点云根据类别展示与加速渲染
pyqt·opengl
闲暇部落15 天前
android openGL ES详解——缓冲区VBO/VAO/EBO/FBO
kotlin·opengl·缓冲区·fbo·vbo·vao·ebo
闲暇部落16 天前
android openGL ES详解——混合
android·opengl
离离茶20 天前
【opengles】笔记1:屏幕坐标与归一化坐标(NDC)的转换
opengl·opengles
蒋灵瑜的笔记本20 天前
【OpenGL】创建窗口/绘制图形
开发语言·c++·图形渲染·opengl