Uniform 块
std140 内存布局规则
OpenGL 提供了不同的 layout 策略来定义 Uniform 块的内存布局。最常用的是 std140,其规则如下:
- 标量类型(float、int、bool):占 4 字节,对齐为 4 字节;
- vec2:占 8 字节,对齐为 8 字节;
- vec3 / vec4:占 16 字节,对齐为 16 字节(vec3 实际占用 16 字节);
- mat4:被视为 4 个 vec4,占用 64 字节,每列按 vec4 对齐;
- 结构体成员:对齐为其中最大成员的对齐值,整体对齐到最大成员的倍数;
- 数组元素:对齐为 16 字节(每个元素当作 vec4);
- 结构体数组:每个元素作为独立结构体,整体对齐也为 16 字节。
Uniform 块声明示例
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 |
|---|---|---|---|---|
| a | float | 4 | 4 | 0 |
| b | vec2 | 8 | 8 | 8 |
| c | vec3 | 12 | 16 | 16 |
| d | float | 4 | 4 | 32 |
| e | mat4 | 64 | 16 | 48 |
总大小 = 48 + 64 = 112 字节
查询 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 名称。
C++中声明内存对齐的结构体
#include <glm/glm.hpp>
#include <cstddef> // for std::byte
#include <cstdint> // for fixed width types
struct alignas(16) ExampleBlockData {
float a; // offset = 0
std::byte pad0[12]; // 填充到 offset 16
glm::vec2 b; // offset = 16
std::byte pad1[8]; // 填充到 offset 32
glm::vec3 c; // offset = 32
std::byte pad2[4]; // 填充到 offset 48
float d; // offset = 48
std::byte pad3[12]; // 填充到 offset 64
glm::mat4 e; // offset = 64,占用 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, bufferSize, NULL, GL_DYNAMIC_DRAW);
glBindBuffer(GL_UNIFORM_BUFFER, 0);bufferSize:通过glGetActiveUniformBlockiv或 手动计算得到;
绑定 UBO
绑定 UBO 到 Uniform Block Binding Point:
GLuint bindingPoint = 0;
glUniformBlockBinding(programID, blockIndex, bindingPoint);
glBindBufferBase(GL_UNIFORM_BUFFER, bindingPoint, ubo);注意:
glUniformBlockBinding将 Uniform 块索引与绑定点关联;glBindBufferBase将实际 UBO 绑定到绑定点。
写入数据
方法一:直接写入整块数据
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);