OpenGL缓冲区对象之UBO

来源:互联网 发布:手机淘宝分类链接 编辑:程序博客网 时间:2024/05/17 17:40
  • 概述

UBO(Uniform Buffer Object)是用来存储着色语言中Uniform类型变量的缓冲区对象,使用UBO可以让uniform变量在不同的着色语言程序中实现共用,也可以在着色语言程序中实现uniform类型变量的设置与更新。

提到UBO就必须要提到着色语言GLSL中的Uniform Blocks,它将众多的Uniform类型的变量集中在一起进行统一的管理,对于需要大量Uniform类型变量的程序可以显著地提高性能。

  • 优势

相比传统设置单个uniform类型变量的方式,ubo有着以下一些特点:

  1. 可以通过切换不同的UBO绑定迅速更新程序中的uniform类型变量的值(在单一着色语言程序中)
  2. 可以存储更多的uniform类型变量
  3. 可以在不同的着色语言程序中通过更新UBO中的数据实现所有uniform类型变量的更新
  • 使用

UBO必须配合Uniform Block一起使用,Uniform Block的定义和C/C++中的struct很类似。Uniform Block定义的语法如下:

storage_qualifier block_name{  <define members here>} instance_name;
//定义MatrixBlock Uniform Blockuniform MatrixBlock{  mat4 projection;  mat4 modelview;} matrices;
  • 关联UBO和Uniform Block

当Uniform Block在着色语言程序中链接之后,会在着色语言中生成很多个绑定索引值,通过这些索引可以找到Uniform Block。获取Uniform Block可以使用如下的API:

GLuint glGetUniformBlockIndex​( GLuint program​, const char *uniformBlockName​ );

参数中uniformBlockName指的是着色语言程序中的Uniform Block(上文中的MatrixBlock)

获取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);

其中uBlockIndex是前面通过glGetUniformBlockIndex得到的索引值;uBlockBinding是传入的Binding point值

通过以上两个函数可以完成上面示意图中的左边部分,右边部分的实现通过下面的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_BINDINGSGLuint 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); //生成UBOglGenBuffers(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

假设我们定义了如下的Uniform Block(具体内容见后面分析)

layout (std140) uniform ColorBlock {    vec4 diffuse;    vec4 ambient;};

为了给diffuse和ambient变量设置值,最关键的地方是我们需要获得这些变量在UBO中相对于UBO存储位置的偏移量,可以通过如下的命令设置:

void glGetActiveUniformBlockiv(GLuint program, GLuint uBlockIndex, GLenum pname, GLint *params);查询Uniform Block的相关信息参数:<p>program:当前链接好的着色语言程序;</p><p>uBlockIndex:Block Uniform的索引值;</p><p>pname:GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS(查询Block中有多少个Uniform变量);<code>GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES</code>(获取Block中uniform的索引值)</p><p>params:获取得到的结果</p>

当我们获得了Block Uniform中某个uniform的索引值之后,可以通过glGetActiveUniformsiv来获取uniform数据:

void glGetActiveUniformsiv(GLuint program, GLsizei ucount, const GLuint *uIndices, GLenum pname, GLint *params);参数:ucount:索引值的个数uindices:索引值数组pname:我们需要获取的内容:<code>GL_UNIFORM_TYPE</code>, <code>GL_UNIFORM_OFFSET</code>, <code>GL_UNIFORM_SIZE</code>, <code>GL_UNIFORM_ARRAY_STRIDE</code>, <code>GL_UNIFORM_MATRIX_STRIDE</code>.params:获取得到的结果
当获取到Uniform在Uniform Block中的偏移量之后,我们可以利用glBufferSubData来更新UBO中的数据,这样就实现了设置和修改Uniform Block中相应变量的值

void glBufferSubData( GLenum target, GLintptr offset, GLsizeiptr size, const GLvoid * data);

例如对于上面我们的ColorBlock,通过查询我们可以看到它占用的空间:

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    }

于是,当我们需要更新ambient的数据时,我们可以这样去做:

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);

当我们修改一下ColorBlock中的内容:

layout (std140) uniform ColorBlock2 {    vec3 diffuse;    vec3 ambient;};

再次查询ColorBlock占用的空间:

ColorBlock2    Size 28    Block binding point: 0    Buffer bound to binding point: 0     {       ambient            GL_FLOAT_VEC3            <span style="color:#FF0000;">offset: 16</span>            size: 12       diffuse            GL_FLOAT_VEC3            offset: 0            <span style="color:#FF0000;">size: 12</span>    }

发现diffuse占用的空间是12个字节,但是ambient却是从16个字节处开始的。这里面就涉及到一个字节对齐的概念,前面我们声明Uniform Block的时候都使用了一个标识符std140,指的是GLSL 1.4版本的时候指定的一个字节对齐的规范,具体的内容如下表:

变量类型

变量大小/偏移量

标量数据类型(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 <gl/glew.h>#include <gl/freeglut.h>#include <iostream>#include "glshadertools.h"GLuintprogramID;GLuintuboID;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;voidinit(){programID = gltLoadShaderProgram("hello.vert", "hello.frag");ubIndex = glGetUniformBlockIndex(programID, "ColorBlock");glUniformBlockBinding(programID, ubIndex, bindingPoint);GLintbufferSize = 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();}voiddisplay(){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);   // pitchglRotatef(yRot, 0, 1, 0);   // headingglutSolidTeapot(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: // ESCAPEexit(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 330void main(){gl_Position = ftransform();}

片元shader:

#version 330layout (std140) uniform ColorBlock{vec4 color1;vec4 color2;};void main(){gl_FragColor = color1 + color2;}

1 0