Uniform Block
Uniform Block 有3种常见的布局规则:
- packed:不同OpenGL实现存在布局差异,不同program也可能存在布局差异
- shared (默认布局):不同OpenGL实现存在布局差异,不同program使用相同定义无布局差异
- std140:均不存在布局差异
std140 内存布局规则
N = 1 个基本机器单位 = 4 字节
| 类型 | 对齐要求 | 对齐要求: 字节数 |
|---|---|---|
| 标量 (float, int…) | N | 4 |
| vec2 | 2N | 8 |
| vec3 | 4N | 16 |
| vec4 | 4N | 16 |
| 数组的每个元素 | 4N(按 vec4 对齐) | 16 |
| 矩阵 (列主序 CxR) | 等同于 C 个 vec-R 的数组 | 每列 16 |
| 结构体 | 4N(按 vec4 对齐) | 16 |
偏移计算公式
成员偏移 = ceil(上一个成员结束位置 / 本成员对齐值) × 本成员对齐值实际偏移量 = 上一个成员结束位置向上取整到对齐值的整数倍。Block 第一个成员偏移为 0。
内存布局计算例子
layout(std140) uniform ExampleBlock {
float a; // offset 0
vec2 b; // offset 8
vec3 c; // offset 16(实际占 16 字节)
float d; // offset 32
mat4 e; // offset 48(占 64 字节)
};| 成员 | 类型 | 对齐要求 | 偏移 (offset) | 占用字节 | 填充 (padding) |
|---|---|---|---|---|---|
a |
float |
4 | 0 | 4 | 4 字节 (offset 4-7) |
b |
vec2 |
8 | 8 | 8 | 0 |
c |
vec3 |
16 | 16 | 12 | 0 (但 offset 28-31 被 d 占用) |
d |
float |
4 | 28 | 4 | 0 |
e |
mat4 |
16 | 32 | 64 (4×16) | 0 |
总大小 = 96 字节
[ 字节偏移 ] : [ 0-3 ][ 4-7 ][ 8-11 ][ 12-15 ]
---------------------------------------------------------
Chunk 0 : [ float a ][ PADDING ][ vec2 b ] <- 16 Bytes
Chunk 1 : [ vec3 c ][ float d ] <- 16 Bytes
Chunk 2 : [ mat4 e (列 0) ] <- 16 Bytes
Chunk 3 : [ mat4 e (列 1) ] <- 16 Bytes
Chunk 4 : [ mat4 e (列 2) ] <- 16 Bytes
Chunk 5 : [ mat4 e (列 3) ] <- 16 Bytes
---------------------------------------------------------
总计占用 6 个 Chunk = 96 BytesC++中声明内存对齐的结构体
#include <cstddef> // std::byte
struct ExampleBlock {
float a; // 偏移量 0,大小 4
std::byte _padding1[4]; // 偏移量 4,大小 4 (显式填充,为了对齐接下来的 vec2)
glm::vec2 b; // 偏移量 8,大小 8
glm::vec3 c; // 偏移量 16,大小 12
float d; // 偏移量 28,大小 4 (填补 vec3 的空隙)
glm::mat4 e; // 偏移量 32,大小 64 (列主序矩阵)
};UBO(Uniform Buffer Object)
Uniform Buffer Object(UBO) 是一种 GPU 缓冲对象,用于存储 Uniform 块的数据。 ->
创建 UBO 并上传数据
GLuint ubo;
glGenBuffers(1, &ubo);
glBindBuffer(GL_UNIFORM_BUFFER, ubo);
glBufferData(GL_UNIFORM_BUFFER, sizeof(ExampleBlock), &obj, GL_DYNAMIC_DRAW);
glBindBuffer(GL_UNIFORM_BUFFER, 0);绑定 UBO
Block Index:着色器内部的"编号",类似 Uniform 的 Location Binding Point:OpenGL 全局的"插槽编号",类似纹理单元/纹理插槽
使用 Binding Point
GLSL
layout(std140, binding = 0) uniform ExampleBlock {
...
}C++
// 绑定到 binding = 0
glBindBufferBase(GL_UNIFORM_BUFFER_OBJECT, 0, ubo);这种方法非常适合帧常量(在帧内无变化,如LightDirection),设置一次,多个Shader Program 共享。
使用 Block Index
GLSL
layout(std140) uniform ExampleBlock {
...
}C++
// 与纹理单元相同,都需要将UBO绑定到一个指定的插槽上
GLuint bindingPoint = 0;
glBindBufferBase(GL_UNIFORM_BUFFER, bindingPoint, ubo);
glUniformBlockBinding(programID, blockIndex, bindingPoint);写入数据
方法一:直接写入整块数据
glBindBuffer(GL_UNIFORM_BUFFER, ubo);
glBufferSubData(GL_UNIFORM_BUFFER, 0, bufferSize, dataPointer);
glBindBuffer(GL_UNIFORM_BUFFER, 0);方法二:映射写入(适用于频繁更新)
glBindBuffer(GL_UNIFORM_BUFFER, ubo);
void* ptr = glMapBufferRange(GL_UNIFORM_BUFFER, 0, bufferSize, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
memcpy(ptr, dataPointer, bufferSize);
glUnmapBuffer(GL_UNIFORM_BUFFER);
glBindBuffer(GL_UNIFORM_BUFFER, 0);附:查询 Uniform 块信息
使用以下 API 查询着色器中 Uniform 块的结构和布局信息:
GLuint blockIndex = glGetUniformBlockIndex(programID, "ExampleBlock");
// 获取块大小
GLint blockSize;
glGetActiveUniformBlockiv(programID, blockIndex, GL_UNIFORM_BLOCK_DATA_SIZE, &blockSize);
// 获取块中所有 uniform 的索引
GLint uniformIndices[NUM_UNIFORMS];
glGetUniformIndices(programID, uniformNames, uniformIndices);
// 获取每个 uniform 在块中的偏移
GLint uniformOffsets[NUM_UNIFORMS];
glGetActiveUniformsiv(programID, NUM_UNIFORMS, uniformIndices, GL_UNIFORM_OFFSET, uniformOffsets);其中 uniformNames 是一个 const char* 数组,列出了 Uniform 名称。