UBO(Uniform Buffer Object)是用来存储着色语言中Uniform类型变量的缓冲区对象,使用UBO可以让uniform变量在不同的着色语言程序中实现共用,也可以在着色语言程序中实现uniform类型变量的设置与更新。
提到UBO就必须要提到着色语言GLSL中的Uniform Blocks,它将众多的Uniform类型的变量集中在一起进行统一的管理,对于需要大量Uniform类型变量的程序可以显著地提高性能。
相比传统设置单个uniform类型变量的方式,ubo有着以下一些特点:
UBO必须配合Uniform Block一起使用,Uniform Block的定义和C/C++中的struct很类似。Uniform Block定义的语法如下:
storage_qualifier block_name
{
} instance_name;
//定义MatrixBlock Uniform Block
uniform MatrixBlock
{
mat4 projection;
mat4 modelview;
} matrices;
当Uniform Block在着色语言程序中链接之后,会在着色语言中生成很多个绑定索引值,通过这些索引可以找到Uniform Block。获取Uniform Block可以使用如下的API:
GLuint glGetUniformBlockIndex( GLuint program, const char *uniformBlockName );
获取Uniform Block index的目的是为了使得它和uniformBlock ID值进行绑定,这个Uniform Block的ID值存放在OpenGL Context中,通过它可以作为连接Uniform Block和UBO的桥梁:(通过把Uniform Block和UBO绑定到相同的Binding points(ID)上实现UBO和Uniform Block 的互动)
将Uniform Block 的索引值绑定到binding points需要使用下面的方式:
void glUniformBlockBinding(GLuint program, GLuint uBlockIndex, GLuint uBlockBinding);
通过以上两个函数可以完成上面示意图中的左边部分,右边部分的实现通过下面的API完成:
1.glGenBuffers生成缓冲区对象,需要注意的是要创建GL_UNIFORM_BUFFER
2.glBindBuffer绑定GL_UNIFROM_BUFFER
3.glBufferData传入缓冲区对象存储的内容
4.glBindBufferBase来设置缓冲区与binding point的绑定
void glBindBufferBase(GLenum target, GLuint bindingPoint, GLuint bufferName);
target的取值:GL_UNIFORM_BUFFER
bindingPoint:必须和glUniformBlockBinding中的uBlockBinding值一样,这样就让Block Uniform和Uniform Buffer连接起来
bufferName:通过glGenBuffers生成的ID值
整个设置绑定的过程如下所示:
// 需要注意的是binding point的值必须小于GL_MAX_UNIFORM_BUFFER_BINDINGS
GLuint bindingPoint = 1;
GLuint buffer;
GLuint blockIndex;
//复制到缓冲区存储空间的值
float myFloats[8] = {1.0, 0.0, 0.0, 1.0, 0.4, 0.0, 0.0, 1.0};
//获取Uniform Block的索引值
blockIndex = glGetUniformBlockIndex(p, "ColorBlock");
//将Uniform Block的索引值和binding point关联
glUniformBlockBinding(p, blockIndex, bindingPoint);
//生成UBO
glGenBuffers(1, &buffer);
glBindBuffer(GL_UNIFORM_BUFFER, buffer);
//设置UBO存储的数据(用来给Uniform Block中变量赋值)
glBufferData(GL_UNIFORM_BUFFER, sizeof(myFloats), myFloats, GL_DYNAMIC_DRAW);
//将UBO与同样的binding point关联
glBindBufferBase(GL_UNIFORM_BUFFER, bindingPoint, buffer);
假设我们定义了如下的Uniform Block(具体内容见后面分析)
layout (std140) uniform ColorBlock {
vec4 diffuse;
vec4 ambient;
};
void glGetActiveUniformBlockiv(GLuint program, GLuint uBlockIndex, GLenum pname, GLint *params);查询Uniform Block的相关信息
参数:program:当前链接好的着色语言程序;
uBlockIndex:Block Uniform的索引值;
pname:GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS(查询Block中有多少个Uniform变量);GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES
(获取Block中uniform的索引值)
params:获取得到的结果
当我们获得了Block Uniform中某个uniform的索引值之后,可以通过glGetActiveUniformsiv来获取uniform数据:
void glGetActiveUniformsiv(GLuint program, GLsizei ucount, const GLuint *uIndices, GLenum pname, GLint *params);
参数:
ucount:索引值的个数
uindices:索引值数组
pname:我们需要获取的内容:GL_UNIFORM_TYPE
, GL_UNIFORM_OFFSET
, GL_UNIFORM_SIZE
, GL_UNIFORM_ARRAY_STRIDE
, GL_UNIFORM_MATRIX_STRIDE
.
params:获取得到的结果
当获取到Uniform在Uniform Block中的偏移量之后,我们可以利用glBufferSubData来更新UBO中的数据,这样就实现了设置和修改Uniform Block中相应变量的值
void glBufferSubData( GLenum target, GLintptr offset, GLsizeiptr size, const GLvoid * data);
ColorBlock
Size 32
Block binding point: 0
Buffer bound to binding point: 0
{
ambient
GL_FLOAT_VEC4
offset: 16
size: 16
diffuse
GL_FLOAT_VEC4
offset: 0
size: 16
}
float color[4] = {0.3, 0.0, 0.0, 1.0};
GLuint offset = 16;
glBindBuffer(GL_UNIFORM_BUFFER, buffer);
glBufferSubData(GL_UNIFORM_BUFFER, offset , sizeof(color), color);
layout (std140) uniform ColorBlock2 {
vec3 diffuse;
vec3 ambient;
};
ColorBlock2
Size 28
Block binding point: 0
Buffer bound to binding point: 0
{
ambient
GL_FLOAT_VEC3
offset: 16
size: 12
diffuse
GL_FLOAT_VEC3
offset: 0
size: 12
}
变量类型 |
变量大小/偏移量 |
标量数据类型(bool,int,uint,float) |
基于基本机器类型的标量值大小 (例如,sizeof(GLfloat)) |
二元向量(bvec2,ivec2,uvec2,vec2) |
标量类型大小的两倍 |
三元向量(bvec3,ivec3,uvec3,vec3) |
标量类型大小的四倍 |
三元向量(bvec4,ivec4,uvec4,vec4) |
标量类型大小的四倍 |
标量的数组或向量 |
数组中每个元素大小是基本类型的大小,偏移量是其索引值(从0开始)与元素大小的乘积。整个数组必须是vec4类型的大小的整数倍(不足将在尾部填充)。 |
一个或多个C列R行列主序矩阵组成的数组 |
以C个向量(每个有R个元素)组成的数组形式存储。会像其他数组一样填充。 如果变量是M个列主序矩阵的数组,那么它的存储形式是:M*C个向量(每个有R个元素)组成的数组。 |
一个或多个R行C列的行主序矩阵组成的数组 |
以R个向量(每个有C个元素)组成的数组。默认像其他数组一样填充。 如果变量是M个行主序矩阵组成的数组,则存储形式是M*R个向量(每个有C个元素)组成的数组。 |
单个结构体或多个结构体组成的数组 |
单个结构体成员的偏移量和大小可以由前面的规则计算出。结构大小总是vec4大小的整数倍(不足在后面补齐)。 由结构组成的数组,偏移量的计算需要考虑单个结构的对齐和补齐。结构的成员偏移量由前面的规则计算出。 |
也就是说我们有两种方式来更新这些Uniform Block中的Uniform变量的值:
1.使用OpenGL中的查询函数来查询Uniform Block中Uniform变量在UBO中的偏移量,然后使用glBufferSubData更新之;
2.使用std140之后直接寻找到uniform在UBO中的偏移量(由于它是一个标准,因此具体大小可以自己计算),然后使用glBufferSubData更新之。
下面使用一个具体的例子来总结UBO的使用方法:(使用FreeGlut)
#pragma comment(lib, "glew32.lib")
#pragma comment(lib, "freeglut.lib")
#include
#include
#include
#include "glshadertools.h"
GLuint programID;
GLuint uboID;
GLuint bindingPoint = 1;
GLuint ubIndex;
GLfloat color1[] = {1.0f, 0.0f, 0.0f, 0.0f};
GLfloat color2[] = {0.0f, 1.0f, 0.0f, 1.0f};
//////////////////////////////////////////////////////////////////////////
GLfloat xRot;
GLfloat yRot;
GLfloat zoom = -10.0f;
bool mouseLeftDown;
float mouseX, mouseY;
void init()
{
programID = gltLoadShaderProgram("hello.vert", "hello.frag");
ubIndex = glGetUniformBlockIndex(programID, "ColorBlock");
glUniformBlockBinding(programID, ubIndex, bindingPoint);
GLint bufferSize = 0;
glGetActiveUniformBlockiv(programID, ubIndex, GL_UNIFORM_BLOCK_DATA_SIZE, &bufferSize);
glGenBuffers(1, &uboID);
glBindBuffer(GL_UNIFORM_BUFFER, uboID);
glBufferData(GL_UNIFORM_BUFFER, bufferSize, NULL, GL_DYNAMIC_READ);
glBindBufferBase(GL_UNIFORM_BUFFER, bindingPoint, uboID);
//寻找每一个uniform变量的偏移值
const GLchar *names[] = {"color1","color2"};
GLuint indices[2];
glGetUniformIndices(programID, 2, names, indices);
GLint offset[2];
glGetActiveUniformsiv(programID, 4, indices, GL_UNIFORM_OFFSET, offset);
//拷贝数据到UBO中(给Uniform Block中的color1和color2变量赋值)
glBufferSubData(GL_UNIFORM_BUFFER, offset[0], 4*sizeof(float), color1);
glBufferSubData(GL_UNIFORM_BUFFER, offset[1], 4*sizeof(float), color2);
glUseProgram(programID);
}
void reshape(int w, int h)
{
glViewport(0, 0, (GLsizei)w, (GLsizei)h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(60.0f, (float)(w)/h, 0.1f, 1000.0f);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void display()
{
glClearColor(0, 0, 0, 0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glColor3f(1.0f, 0.0f, 1.0f);
glLoadIdentity();
glTranslatef(0, 0, zoom);
glRotatef(xRot, 1, 0, 0); // pitch
glRotatef(yRot, 0, 1, 0); // heading
glutSolidTeapot(2.0);
glutSwapBuffers();
}
void mouse(int button, int state, int x, int y)
{
mouseX = (float)x;
mouseY = (float)y;
switch (button)
{
case GLUT_LEFT_BUTTON:
{
if (state == GLUT_DOWN) {
mouseLeftDown = true;
} else if (state == GLUT_UP) {
mouseLeftDown = false;
}
}
break;
case GLUT_RIGHT_BUTTON:
{
if (state == GLUT_DOWN) {
}
}
break;
default:
break;
}
}
void mouseMove(int x, int y)
{
if(mouseLeftDown)
{
yRot += (x - mouseX);
xRot += (y - mouseY);
mouseX = (float)x;
mouseY = (float)y;
}
glutPostRedisplay();
}
void mouseWheel(int wheel, int direction, int x, int y)
{
switch (direction)
{
case 1: //means wheel up
{
zoom -= 1.0f;
}
break;
case -1: //means wheel down
{
zoom += 1.0f;
}
break;
default:
break;
}
glutPostRedisplay();
}
void keyboard(unsigned char key, int x, int y)
{
switch(key)
{
case 27: // ESCAPE
exit(0);
break;
default:
break;
}
}
void idle()
{
glutPostRedisplay();
}
int main(int argc, char** argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);
glutInitWindowSize(640, 480);
glutCreateWindow(argv[0]);
if (glewInit()) {
std::cerr << "Unable to initialize GLEW ... exiting" << std::endl;
exit(EXIT_FAILURE);
}
init();
glutDisplayFunc(display);
glutReshapeFunc(reshape);
glutMouseFunc(mouse);
glutMotionFunc(mouseMove);
glutMouseWheelFunc(mouseWheel);
glutKeyboardFunc(keyboard);
glutIdleFunc(idle);
glutMainLoop();
}
顶点Shader:
#version 330
void main()
{
gl_Position = ftransform();
}
#version 330
layout (std140) uniform ColorBlock
{
vec4 color1;
vec4 color2;
};
void main()
{
gl_FragColor = color1 + color2;
}