OpenGL Uniform Buffer Object 的坑
什么是 Uniform Buffer Object?
见WIKI: https://www.khronos.org/opengl/wiki/Uniform_Buffer_Object
及 learnopengl 中的示例:https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL#:~:text=the%20geometry%20shader.-,Uniform%20buffer%20objects,-We%27ve%20been%20using
在项目中使用 UBO 时,遇到了一些坑,这里记录一下。
坑1:UBO 的对齐问题
在使用 UBO 时,需要注意 UBO 的对齐问题。为了代码的可移植性,一般会直接使用 std140
来定义 UBO 的内存布局,如:
1 | layout (std140) uniform ExampleBlock |
std140
的布局规则 理解了是一回事,但是在C++中写一个 UBO 对应的struct 的时候,还是会出现对齐问题。比如C++ 中用下面这个 struct 来对应上面的 ExampleBlock
:
1 | struct ExampleBlock { |
这个结构体在C++编译器的眼里是按C++ 自己的内存布局规则来的,这时候我们需要手动按std140
对齐:
1 | struct alignas(16) ExampleBlock { |
注意上面例子中的 struct 中的 bool
类型,需要对齐到 4
字节。
但是,如果你这么写了,仍然可能会遇到一个问题,C++ 代码中明明设置的是 false
,但是程序执行后,在 Shader Program 中读取到的却是 true
。(゚Д゚≡゚д゚)!?
1 | ExampleBlock block; |
1 | #version 410 core |
C++ 中boolean
的值为 false
,但显示的是红色!
这是因为 bool
类型在 C++ 中是 1
字节,而在 GLSL 中是 4
字节,故而在 C++ 中,为 bool 增加了 3 个字节的 padding, 而 padding 的值是不确定的(通常取决于编译器行为)。因为 padding 的存在,导致C++中明明看起来是false
,在 Shader 中按 bool
类型 读到的却是 true
(等于0
或0.0
的是 false
,其他任何值都是 true
)。
如下所示,bool boolean
字段的内存中 padding 位被填充了非0
的数据:
1 | 00000000 00000001 00100000 00100000 00000000 |
解决办法,在C++中 用 int32_t
类型代替 bool
类型,在 GLSL 中还是用 bool
类型来读取:
1 | // bool in std140 layout |
1 | ExampleBlock block; |
同样的问题,还会体现在 mat3
类型上,mat3
在 GLSL 中是 3 * vec3
(mat3x3),在 C++ 中需要手动对齐为 3 * vec4
(column major, 3 columns of 4 components matrix, mat3x4)。
1 | // vec3 in std140 layout |
坑2:同名 UBO
如果在不同 Shader 中定义了两个同名的 UBO,比如:
1 | // 1.frag |
假如这两个Shader 同时被use
,在 C++ 中传递 UBO 数据的时候,只有最后一个 UBO 会被传递到 GPU,而前面的会被忽略(甚至有可能导致内存问题)。
这是因为 UBO 在 OpenGL 中是全局的,如果定义了两个同名的 UBO,OpenGL 会认为这是同一个 UBO,只有最后一个 UBO 的数据会被传递到 GPU。
解决办法是,给不同结构的 UBO 用不同的名字(就是这么朴素而有效 <(▰˘◡˘▰)> )
Refs
- OpenGL Uniform Buffer Object Wiki
- Using OpenGL Uniform Buffer Object
- OpenGL Data Type (GLSL)
- C++ Struct memory alignment
std140
Uniform Buffer Layout
Author: Yrom
Link: https://yrom.net/blog/2024/02/24/opengl-ubo-pitfalls/
License: 知识共享署名-非商业性使用 4.0 国际许可协议