1. uniform 块
着色器与应用程序之间,或者着色器各阶段之间共享的变量可以组织为变量块的形式,并且有的时候必须采用这种形式。
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 块
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块,并且使用行主序的方式来存储数据
layout (shared, row_major) uniform {
......
};
- 多个限制符可以通过圆括号中的逗号来分隔。
Example 02 :需要对所有后继的uniform块设置同一种布局
layout (packed, column_major) uniform;
- 当前行之后的所有 uniform 块都会使用这种布局方式,除非再次改变全局的布局,或者对某个块的声明单独设置专属的布局方式。
1.2.3 缓存布局
如果你在着色器和应用程序之间共享了一块缓存,那么这两者都需要确认成员变量所处的内存偏移地址。
此时可以通过 offset
限制符来控制成员的精确位置,或者用 align
限制符来设置一个模糊的对齐方式。
你可以只对某些成员应用这些限制符,从而确保应用程序和着色器之间的布局是同步的。
#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
#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
#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 类型转换为存储大小的辅助函数
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 块的初始化
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块,修改其中的内容并呈现给其他的着色器调用或者应用程序本身;
- 可以在渲染之前再决定它的大小,而不是编译和链接的时候。
buffer Bufferobject { // 创建一个可读写的 buffer 块
int mode; // 序言(preamble)成员
vec4 points[]; // 最后一个成员可以是未定义大小的数组
};
如果在着色器中没有给出上面的数组的大小,那么可以在应用程序中编译和连接之后,渲染之前设置它的大小。
着色器中可以通过 length()
方法获取渲染时的数组大小。
Note :
- buffer 块只可以使用 std430 布局
3. in / out 块
3.1 着色器的输入块与输出块
着色器变量从一个阶段输出,然后再输入到下一个阶段中,这一过程可以使用块接口来表示。
顶点着色器的输出 :
out Lighting {
vec3 normal;
vec2 bumpCoord;
);
片元着色器的输入 :
in Lighting {
vec3 normal;
vec2 bumpCoord;
);
3.2 设置成员的位置
layout(location=N)
被用于每个独立的输入和输出变量。
它也可以作用于输入和输出块的成员,显式地设置它们的位置:
#version 440
in Lighting {
layout(location=l) vec3 normal;
layout(location=2) vec2 bumpCoord;
};
无论这些 location 位置信息是否在块中,都是可以等价于一个vec4的。
3.3 分量
如果用户希望把多个小的对象设置到同一个位置上,那么也可以使用分量(component)关键字。
#version 440
in Lighting {
layout(location=l, component=0) vec2 offset;
layout(location=l, component=2) vec2 bumpCoord;
};