OpenGL对于Uniform block有两种布局方式:
1、标准布局
2、共享布局
标准布局是由开发者在着色器中定义Uniform block的位置值,需要C++程序使用glMapBuffer来向该Uniform block缓存中写入数据,这个布局成员之间会“留空”,会导致失去一些性能和存储空间。使用标准布局好处就是易实现,c++程序不需要做较多的工作就可以刷数据到着色器缓存中,但是会损失一些性能。
共享布局是就是由OpenGL自己安排uniform block的位置值,c++程序需要去明确uniform block的各个成员位置值才能从着色器中读取数据,并且需要向OpenGL指定数据的存储位置,让OpenGL去读取数据并且布置数据到uniform block中,共享布局还可以让各个着色器程序共享同个uniform block。使用共享布局的好处是高性能,但是使用起来较为复杂。
标准布局的一个例子就是使用std140布局,先出例子:
layout(std140) uniform TransformBlock
{
float scale; //缩放因子
vec3 translation; //平移向量
float rotation[3]; // 旋转轴
mat4 projection_matrix; // 投影矩阵(在缩放、平移旋转变换之后应用)
} transform;
语法就是布局限定符layout(标准布局版本),前面说标准布局会留空这是为啥呢,这主要是由于标准布局规定的对齐规则导致的:
1、在uniform类型中vec2是八字节对齐的但总的uniform类型还是按16字节对齐
2、除了vec4或者N*4的矩阵外其他数据类型都是按照16字节对齐
上述例子对齐规则后偏移量就是:
scale 0
translation 16
rotation 16+16
projection_matrix 16+16+16*3
标准布局也可以使用布局限定符offset自定义偏移量,但是这个自定义的偏移量还是要遵守上述的对齐规则,继续直接上例子:
layout(std140) uniform ManuallyLaidOutBlock
{
layout (offset = 32) vec4 foo; // At offset 32 bytes
layout (offset = 8) vec2 bar; // At offset 8 bytes
layout (offset = 48) vec3 baz; // At offset 48 bytes
} myBlock;
这里主要是第二个成员bar,vec2八字节对齐符合规则一,则没啥问题,但是它在内存中所处的起始是在foo变量前了,bar在内存的8字节处,foo在内存的32字节处,也就是说offset能让成员变量在内存所在的位置顺序和uniform块类型声明的顺序不同。
接着还有另一个对齐限定符要讲,就是align,这个限定符主要是用来调整整个uniform块各个成员的对齐方式,上例子:
layout (std140, align = 16) uniform ManuallyLaidOutBlock
{
layout (offset = 32) vec4 foo; // At offset 32 bytes
layout (offset = 8) vec2 bar; // At offset 16 bytes
layout (offset = 48) vec3 baz; // At offset 48 bytes
} myBlock;
这里已经确定了整块的成员对齐方式都是16字节,offset对各个成员的偏移再进一步调整,可是此时bar指定的是八字节偏移,并不是16字节的倍数,所以bar的起始会在16字节处偏移。同样的bar在内存中是在foo变量前的。
接着再聊聊共享布局,共享布局uniform block声明是比较简单的,因为是OpenGL的默认布局,只需要直接声明:
uniform block
{
float scale;
...
}sharedBlock;
共享布局是让OpenGL自动为uniform block进行布局,OpenGL自动设计的布局会相比标准布局会稍微高效,但是需要的操作却会比较多,主要集中在外部程序向uniform block变量赋值的过程。
首先,需要获取uniform block中各个成员的索引,再根据索引去获取各个成员在内存布局中的信息,比如偏移量、数组步长、矩阵步长等。
获取uniform block中各个成员的索引的OpenGLAPI是
void glGetUniformIndices(GLuint program,
GLsizei uniformCount,
const GLchar ** uniformNames,
GLuint * uniformIndices);
第一个参数是GLSL的程序对象,第二个是uniform成员变量的个数,第三个是要获取的uniform成员变量字符串数组,第四个是用于获取uniform成员变量的索引数组。
接着使用获取到 索引数组再调用glGetActiveUniformsiv获取uniform变量信息:
void glGetActiveUniformsiv(GLuint program,
GLsizei uniformCount,
const GLuint * uniformIndices,
GLenum pname,
GLint * params);
首先是GLSL的程序对象,接着是uniform成员变量的个数,第三个是uniform成员变量的索引数组,第四个是要获取的信息类型,如偏移量是GL_UNIFORM_OFFSET,数组步长是GL_UNIFORM_ARRAY_STRIDE,矩阵步长是GL_UNIFORM_MATRIX_STRIDE,最后一个是用于接收所需值的数组。
关于OpenGL的布局就先到这,下一篇继续一起学习共享布局吧