UBO(Uniform Buffer Object)是用來存儲着色語言中Uniform類型變量的緩衝區對象,使用UBO可以讓uniform變量在不同的着色語言程序中實現共用,也可以在着色語言程序中實現uniform類型變量的設置與更新。
提到UBO就必須要提到着色語言GLSL中的Uniform Blocks,它將衆多的Uniform類型的變量集中在一起進行統一的管理,對於需要大量Uniform類型變量的程序可以顯著地提高性能。
相比傳統設置單個uniform類型變量的方式,ubo有着以下一些特點:
- 可以通過切換不同的UBO綁定迅速更新程序中的uniform類型變量的值(在單一着色語言程序中)
- 可以存儲更多的uniform類型變量
- 可以在不同的着色語言程序中通過更新UBO中的數據實現所有uniform類型變量的更新
UBO必須配合Uniform Block一起使用,Uniform Block的定義和C/C++中的struct很類似。Uniform Block定義的語法如下:
當Uniform Block在着色語言程序中鏈接之後,會在着色語言中生成很多個綁定索引值,通過這些索引可以找到Uniform Block。獲取Uniform Block可以使用如下的API:
參數中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需要使用下面的方式:
其中uBlockIndex是前面通過glGetUniformBlockIndex得到的索引值;uBlockBinding是傳入的Binding point值
通過以上兩個函數可以完成上面示意圖中的左邊部分,右邊部分的實現通過下面的API完成:
1.glGenBuffers生成緩衝區對象,需要注意的是要創建GL_UNIFORM_BUFFER
2.glBindBuffer綁定GL_UNIFROM_BUFFER
3.glBufferData傳入緩衝區對象存儲的內容
4.glBindBufferBase來設置緩衝區與binding point的綁定
target的取值:GL_UNIFORM_BUFFERbindingPoint:必須和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(具體內容見後面分析)
爲了給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:獲取得到的結果
例如對於上面我們的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中的內容:
再次查詢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”
- 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:
片元shader:
- #version 330
- layout (std140) uniform ColorBlock
- {
- vec4 color1;
- vec4 color2;
- };
- void main()
- {
- gl_FragColor = color1 + color2;
- }