0%

03 _着色器数据块接口

1. uniform 块

着色器与应用程序之间,或者着色器各阶段之间共享的变量可以组织为变量块的形式,并且有的时候必须采用这种形式。

1
2
3
4
5
6
7
8
9
uniform b {     // 限定符可以为 uniform、in、out 或者 buffer
vec4 v1; // 块中的变量列表
bool v2; // ...
}; // 访问匿名块成员时使用v1、v2

uniform b { // 限定符可以为 uniform、in、out 或者 buffer
vec4 v1; // 块中的变量列表
bool v2; // ...
} name; // 访问有名块成员时使用 name.v1、name.v2

通常会在多个着色器程序中用到同一个 uniform 变量。
由于 uniform 变量的位置是着色器链接的时候产生的(也就是调用 glLinkProgram() 的时候),因此它在应用程序中获得的索引可能会有变化,即使我们给 uniform 变量设置的值可能是完全相同的。

uniform变量是同时存在于用户应用程序和着色器当中的,因此需要同时修改着色器的内容并调用OpenGL函数来设置uniform缓存对象。

1.1 指定着色器中的 uniform 块

1
2
3
4
5
uniform Matrices {
mat4 ModelView;
mat4 Projection;
mat4 Color;
};

Note

  • 着色器中的数据类型有两种:不透明的和透明的;其中不透明类型包括采样器、图像和原子计数器;
  • uniform 块中只可以包含透明类型的变量;
  • uniform 块必须在全局作用域内声明。

1.2 uniform 块的布局控制

1.2.1 uniform 的布局限制符

布局限制符 描 述
binding = N 设置缓存的绑定位置,需要用到 OpenGL API
shared 设置 uniform 块是多个程序间共享的(这是默认的布局方式,与 shared 存储限制符不存在
混淆)
packed 设置 uniform 块占用最小的内存空间,但是这样会禁止程序间共享这个块
stdl40 使用标准布局方式来设置 uniform 块或者着色器存储的 buffer 块, 参见附录H
std430 使用标准布局方式来设置 uniform 块,参见附录I
offset = N 强制设置成员变量位于缓存的N字节偏移处
align = N 强制设置成员变址的偏移位置是N的倍数
row_major 使用行主序的方式来存储 uniform 块中的矩阵
column_major 使用列主序的方式来存储 uniform 块中的矩阵(这也是默认的顺序)

1.2.2 布局声明

Example 01 :需要共享一个uniform块,并且使用行主序的方式来存储数据

1
2
3
layout (shared, row_major) uniform {
......
};
  • 多个限制符可以通过圆括号中的逗号来分隔。

Example 02 :需要对所有后继的uniform块设置同一种布局

1
layout (packed, column_major) uniform;
  • 当前行之后的所有 uniform 块都会使用这种布局方式,除非再次改变全局的布局,或者对某个块的声明单独设置专属的布局方式。

1.2.3 缓存布局

如果你在着色器和应用程序之间共享了一块缓存,那么这两者都需要确认成员变量所处的内存偏移地址。

此时可以通过 offset 限制符来控制成员的精确位置,或者用 align 限制符来设置一个模糊的对齐方式。
你可以只对某些成员应用这些限制符,从而确保应用程序和着色器之间的布局是同步的。

1
2
3
4
5
6
7
#version 440
layout (std140) uniform b {
float size; //默认从 0 字节位置开始
layout (of fset=32) vec4 color; // 从 32 字节开始
layout (align=1024) vec4 a[12]; //从下一个 1024 倍数的字节位置开始
vec4 b[12]; //从 a[12] 之后的偏移地址开始
} buf;

Note

  • 对齐过程是自然的,只不过 stdl40 需要对类似 vec4 这样的类型增加一个额外的 16 字节对齐的限制;
  • vec4 的基本对齐方式是 4 * 其基本类型的对齐方式(即:float),因此 vec4 的基本对齐方式为 16

1.3 从应用程序中访问 uniform 块

1.3.1 uniform 块的声明

  • 在顶点着色器和片元着色器中添加固定的 uniform 块作为缓冲区

顶点着色器和片元着色器共享同一个名称为 “Uniforms” 的 uniform 块

顶点着色器 vShader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#version 330 core

uniform Uniforms {
vec3 traiislation;
float scale;
vec4 rotation;
bool enabled;
};

in vec2 vPos;
in vec3 vColor;
out vec4 fColor;

void main()
{
vec3 pos = vec3 (vPos, 0.0);

// rotation 中存储的四元数信息(旋转角,旋转轴)
float angle = radians(rotation[0]);
vec3 axis = normalize(rotation.yzw);

// 换算出旋转矩阵 rot
mat3 I = mat3(1.0);
mat3 S = mat3( 0, - axis.z, axis.y,
axis.z, 0, - axis.x,
- axis.y, axis.x, 0 );
mat3 uuT = outerProduct(axis, axis);
mat3 rot = uuT + cos(angle) * (I - uuT) + sin(angle) * S;

pos *= scale;
pos *= rot;
pos += translation;

fColor = vec4 (scale, scale, scale, 1);
gl_Position = vec4 (pos, 1);
}

片元着色器 fShader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#version 330 core

uniform Uniforms {
vec3 translation;
float scale;
vec4 rotation;
bool enabled;
};

in vec4 fColor;
out vec4 color;

void main()
{
color = fColor;
}

1.3.2 获取存储大小

用于将 GLSL 类型转换为存储大小的辅助函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
size_t
TypeSize(GLenum type)
{
size__t size;

#define CASE(Enum, Count, Type) \
case Enum: size = Count * sizeof(Type); break

switch (type) {
CASE (GL__FLOAT, 1, GLfloat);
CASE (GL_FLOAT__VEC2, 2, GLfloat);
CASE (GL_FLOAT__VEC3, 3, GLfloat);
CASE (GL_FLOAT_VEC4, 4, GLfloat);
CASE (GL__INT, 1, GLint);
CASE (GL_INT_VEC2, 2, GLint);
CASE (GL_INT__VEC3, 3, GLint);
CASE (GL INT VEC4, 4, GLint);
CASE (GL_UNSIGNED_INT, 1, GLuint);
CASE (GL_UNSIGNED_INT_ VEC2, 2, GLuint);
CASE (GL_UNSIGNED_INT_ VEC3, 3, GLuint);
CASE (GL_UNSIGNED_INT_ VEC4, 4, GLuint);
CASE (GL_BOOL, 1, GLboolean);
CASE (GL_BOOL_VEC2, 2, GLboolean);
CASE (GL_BOOL_VEC3, 3, GLboolean);
CASE (GL_BOOL_VEC4, 4, GLboolean);
CASE (GL_FLOAT_MAT2, 4, GLfloat);
CASE (GL_FLOAT_MAT2x3, 6, GLfloat);
CASE (GL_FLOAT_MAT2x4, 8, GLfloat);
CASE (GL_FLOAT_MAT3, 9, GLfloat);
CASE (GL_FLOAT_MAT3X2, 6, GLfloat);
CASE (GL_FLOAT_MAT3 x4, 12, GLfloat);
CASE (GL_FLOAT_MAT4, 16, GLfloat);
CASE (GL_FLOAT_MAT4 x2, 8, GLfloat);
CASE (GL_FLOAT_MAT4x3, 12, GLfloat);
#undef CASE
default:
fprintf (stderr, "Unknown type: 0x%x\n",type);
exit(EXIT_FAILURE);
break;
}

return size;
}

1.3.3 uniform 块的初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
void
init ()
{
GLuint program;
glClearColor(1, 0, 0, 1);

Shaderinfo shaders[] = {
{ GL_VERTEX_SHADER, vShader },
{ GL_FRAGMENT_SHADER, fShader },
{ GL_NONE, NULL }
};

program = LoadShaders(shaders);
glUseProgram(program);

// 初始化uniform块"Uniforms”中的变量
GLuint ubolndex;
GLint xiboSize;
GLuint ubo;
GLvoid *buffer;

// 查找 "Uniforms" 的 uniform 缓存索引,并判断整个块的大小
ubolndex = glGetUniformBlockindex (program, "Uniforms");
glGetActiveUniformBlockiv(program, ubolndex, GL_UNIFORM_BLOCK_DATA_SIZE, &uboSize);

buffer = malloc(uboSize);

if (buffer == NULL) {
fprintf(stderr, "Unable to allocate buffer\n");
exit(EXIT_FAILURE);
}
else {
enum { Translation, Scale, Rotation, Enabled, NumUniforms };

// 准备存储在缓存对象中的值
GLfloat scale = 0.5;
GLfloat translation[] = { 0.1, 0.1, 0.0 };
GLfloat rotation[] = { 90, 0.0, 0.0, 1.0 };
GLboolean enabled = GL_TRUE;

// 我们可以建立一个变量名称数组.对应块中已知的 uniform 变量
const char* names[NumUniforms] = {
"translation",
"scale",
"rotation",
"enabled"
};

// 查询对应的属性,以判断向数据然存中写入数值的位置
GLuint indices[NumUniforms];
GLint size[NumUniforms];
GLint offset[NumUniforms];
GLint type[NumUniforms];

// 返回所有 NumUniforms 个 uniform 变量的索引位置
// 变量名称通过字符串数组 names 指定
// 返回值保存在 indices 中,例如:names[0] == translationIndex
glGetUniformindices(program, NumUniforms, names, indices);

// 获得指定索引位置的偏移量,大小和类型
glGetActiveUniformsiv(program, NumUniforms, indices, GL_UNIFORM_OFFSET, offset);
glGetActiveUniformsiv(program, NumUniforms, indices, GL_UNIFORM_SIZE, size);
glGetActiveUniformsiv(program, NumUniforms, indices, GL_UNIFORM_TYPE, type);

// 将 uniform 变量值拷贝到缓存中
memcpy(buffer + offset [Scale], &scale,
size[Scale] * TypeSize(type[Scale]));
memcpy(buffer + offset[Translation], &translation,
size[Translation] * TypeSize(type[Translation]));
memcpy(buffer + offset[Rotation], &rotation,
size[Rotation] * TypeSize(type[Rotation]));
memcpy(buffer + offset[Enabled], &enabled,
size[Enabled] * TypeSize(type[Enabled]));


// 建立uniform绫存对象,初始化存储内容,并且与着色器程序建立关联
glGenBuffers(1, &ubo);
glBindBuffer(GL_UNIFORM_BUFFER, ubo);
glBufferData(GL_UNIFORM_BUFFER, uboSize, buffer, GL_STATIC_RAW);

glBindBufferBase(GL_UNIFORM_BUFFER, ubolndex, ubo);
}
......
}

2. buffer 块

buffer 块与 uniform 块的区别

  • 着色器可以写入buffer块,修改其中的内容并呈现给其他的着色器调用或者应用程序本身;
  • 可以在渲染之前再决定它的大小,而不是编译和链接的时候。
1
2
3
4
buffer Bufferobject {   // 创建一个可读写的 buffer 块
int mode; // 序言(preamble)成员
vec4 points[]; // 最后一个成员可以是未定义大小的数组
};

如果在着色器中没有给出上面的数组的大小,那么可以在应用程序中编译和连接之后,渲染之前设置它的大小。
着色器中可以通过 length() 方法获取渲染时的数组大小。

Note

  • buffer 块只可以使用 std430 布局

3. in / out 块

3.1 着色器的输入块与输出块

着色器变量从一个阶段输出,然后再输入到下一个阶段中,这一过程可以使用块接口来表示。

顶点着色器的输出

1
2
3
4
out Lighting {
vec3 normal;
vec2 bumpCoord;
);

片元着色器的输入

1
2
3
4
in Lighting {
vec3 normal;
vec2 bumpCoord;
);

3.2 设置成员的位置

layout(location=N) 被用于每个独立的输入和输出变量。
它也可以作用于输入和输出块的成员,显式地设置它们的位置:

1
2
3
4
5
6
#version 440

in Lighting {
layout(location=l) vec3 normal;
layout(location=2) vec2 bumpCoord;
};

无论这些 location 位置信息是否在块中,都是可以等价于一个vec4的。

3.3 分量

如果用户希望把多个小的对象设置到同一个位置上,那么也可以使用分量(component)关键字。

1
2
3
4
5
#version 440
in Lighting {
layout(location=l, component=0) vec2 offset;
layout(location=l, component=2) vec2 bumpCoord;
};