高级GLSL(3)
撰写于 2016-06-12 修改于 2019-07-03 分类 OpenGL
如果有这种情况,有多个着色器,每个着色器中都要设置若干个相同的uniform变量(比如view,projection),那就要每个uniform变量都要设置一遍,尽管他们的值都是相同的,那有没有像VAO 和 VBO这样的缓冲区,可以节省一下操作?当然有,那就是UBO(Uniform Buffer Object),uniform缓冲区对象,可以存储一系列uniform变量,对应各个着色器中uniform block中的uniform变量。
uniform block
uniform block就是uniform变量组成的类结构体的块。结构大体如下:1
2
3
4
5layout (std140) uniform Matrices
{
mat4 view;
mat4 projection;
};
layout (std140)意思是:当前定义的uniform块为它的内容使用特定的内存布局,这个声明实际上是设置uniform块布局(uniform block layout)。std140就是其中的一种内存对齐方式,类似于C++的结构体的内存布局。基本类型,int、float、bool内存大小为4个字节;向量为2*4或者4*4个字节(即vec3为4*4个字节);标量或者向量数组,每个元素与vec4(即4*4个字节)相同;矩阵,可以看成是存贮着大量向量的数组,每个元素的大小与vec4(即4*4个字节)相同;结构体,根据以上规则计算各个元素的大小,并且间距必须是vec4基线的倍数。而且每个变量的偏移量必须是,变量所占内存的整数倍。教程中例子很明了:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15layout (std140) uniform ExampleBlock
{
// base alignment ---------- // aligned offset
float value; // 4 // 0
vec3 vector; // 16 // 16 (必须是16的倍数,因此 4->16)
mat4 matrix; // 16 // 32 (第 0 行)
// 16 // 48 (第 1 行)
// 16 // 64 (第 2 行)
// 16 // 80 (第 3 行)
float values[3]; // 16 (数组中的标量与vec4相同)//96 (values[0])
// 16 // 112 (values[1])
// 16 // 128 (values[2])
bool boolean; // 4 // 144
int integer; // 4 // 148
};
std140只是一种uniform block的内存布局方式,还有其他两种方式packed, shared。简单的介绍一下。1
2
3packed:具有高效的布局方式,它取决于数据的布局,为了节省空间,没有使用unifrom变量可能会被去除掉。
shared:具有高效的布局方式,它取决于数据的布局,也是默认的布局,但是它不是唯一取决于块的结构,允许跨程序共享数据存储。
std140:具有标准的跨平台(c++,java...)跨供应商(NVIDIA, ATI...)布局。不用的uniform变量会被去除掉。
详细的说明看这里。
使用uniform block
那么问题来了,uniform block怎么获取UBO中的数据呢,先看一张图:
这就是uniform block和UBO通信的方式,他们中间需要一个绑定点来交流数据。首先要获取着色器中uniform block的索引,把这个索引绑定到一个绑定点上。然后,把UBO也要帮当到一个绑定点上,uniform block就能通过绑定点找到当前绑定在绑定点上的UBO,进而获取UBO上的数据。
创建UBO
个人理解:创建UBO或者VBO时,OpenGL会生成一个缓冲区指针,但这只是一个很普通的指针,并且没有分配内存空间。然后绑定到一个目标上(GL_UNIFORM_BUFFER或者GL_ARRAY_BUFFER),这时,对该目标做任何操作,都相当于操作绑定到该目标上的缓冲区。当然如果操作完毕后,一定要把解绑当前该目标上绑定的缓冲区,防止误操作。
UBO(Uniform Buffer Object)的创建跟VBO的创建是一样的,创建UBO,绑定到GL_UNIFORM_BUFFER目标上,然后分配内存空间。1
2
3
4
5GLuint UBO;
glGenBuffers(1, &UBO);
glBindBuffer(GL_UNIFORM_BUFFER, UBO);
glBufferData(GL_UNIFORM_BUFFER, 2 * sizeof(mat4), NULL, GL_STATIC_DRAW);
glBindBuffer(GL_UNIFORM_BUFFER, 0);
UBO已经完全准备好了,然后就是填充UBO中的数据。为什么分配的 2*sizeof(mat4) 的空间呢?因为目前顶点目前着色器中的uniform block的结构如下所示,有两个mat4变量:1
2
3
4
5layout (std140) uniform Matrices
{
mat4 view;
mat4 projection;
};
填充view和projection:1
2
3
4
5
6
7
8
9
10/*view*/
glBindBuffer(GL_UNIFORM_BUFFER, UBO);
mat4 view = lookAt(vec3(0.0f, 0.0f, 10.0f), vec3(0.0f, 0.0f, 0.0f), vec3(0.0f, 1.0f, 0.0f));
glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(mat4), value_ptr(view));
glBindBuffer(GL_UNIFORM_BUFFER, 0);
/*projection*/
glBindBuffer(GL_UNIFORM_BUFFER, UBO);
mat4 projection = perspective(radians(45.0f), 800.0f / 600.0f, 0.1f, 100.0f);
glBufferSubData(GL_UNIFORM_BUFFER, sizeof(mat4), sizeof(mat4), value_ptr(projection));
glBindBuffer(GL_UNIFORM_BUFFER, 0);
绑定UBO
UBO已经创建完毕,数据也已经填充进去,下面就是要把UBO绑定到绑定点上,这就用到了glBindBufferBase 和 glBindBufferRange,两个函数的功能基本相同,glBindBufferBase是把当前缓冲区绑定到绑定点上,glBindBufferRange是把一定当前缓冲区一定范围内的数据绑定到绑定点上,下面是官方文档:1
2
3
4
5
6
7
8/*
把缓冲区对象buffer绑定到目标target的数组的index索引处的绑定点
*/
void glBindBufferBase(GLenum target, GLuint index, GLuint buffer);
/*
把缓冲区对象buffer中offset和size范围内数据绑定到目标target的数组的index索引处的绑定点
*/
void glBindBufferRange( GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size);
文档的说明看了好长时间才理解,此处重新解读一下(纯属个人理解,暂时认为是对的),函数传入target,有什么用吗?在这之前一直以为target是为了使用target绑定的UBO,但是UBO(即buffer)已经传入了,看了官网文档一段很拗口的话(原文:binds the buffer object buffer to the binding point at index index of the array of targets specified by target) ,琢磨了一会儿,终于明白了什么意思(暂时是这么理解的,不知道对不对),目标target对应着一个数组,这个数组每个元素的索引就是绑定点(这个数组的元素应该存储的就是指向UBO的指针)。
此处定义的UBO全部内容都用来存储uniform block中的内容,因为此处可以使用glBindBufferBase或者glBindBufferRange两种方式:1
glBindBufferBase(GL_UNIFORM_BUFFER, 0, UBO);
或者1
glBindBufferRange(GL_UNIFORM_BUFFER, 0, UBO, 0, 2*sizeof(mat4));
到目前为止UBO部分已经全部结束,下面是处理uniform block
绑定uniform block
UBO中数据已经有了,那怎么让uniform block对应到UBO呢,UBO已经绑定到绑定点上了,那uniform block也要绑定到对应的绑定点上才可以跟UBO对应起来,这就用到了glUniformBlockBinding,下面是官方文档:1
2
3
4/*
着色器程序的每一个uniform block都有一个对应的uniform缓冲区绑定点,如果成功,program会使用缓冲区对象的数据存储去绑定点uniformBlockBinding处获取uniformBlockIndex指定的uniform block中的uniform的值。
*/
void glUniformBlockBinding(GLuint program, GLuint uniformBlockIndex, GLuint uniformBlockBinding);
怎么获取uniform block索引?GLuint glGetUniformBlockIndex(GLuint program, const GLchar *uniformBlockName),用法很简单,从program中获取名为uniformBlockName的uniform block的索引值。1
2
3
4
5
6
7
8
9
10/*得到shader中uniform block索引*/
GLuint redMatricesIndex = glGetUniformBlockIndex(redShader->getProgram(), "Matrices");
GLuint greenMatricesIndex = glGetUniformBlockIndex(greenShader->getProgram(), "Matrices");
GLuint blueMatricesIndex = glGetUniformBlockIndex(blueShader->getProgram(), "Matrices");
GLuint yellowMatricesIndex = glGetUniformBlockIndex(yellowShader->getProgram(), "Matrices");
/*把uniform block索引绑定到绑定点上*/
glUniformBlockBinding(redShader->getProgram(), redMatricesIndex, 0);
glUniformBlockBinding(greenShader->getProgram(), greenMatricesIndex, 0);
glUniformBlockBinding(blueShader->getProgram(), blueMatricesIndex, 0);
glUniformBlockBinding(yellowShader->getProgram(), yellowMatricesIndex, 0);
以上就是uniform block 和 UBO的用法。在渲染的时候,就不用重复设置这些变量,一次搞定,终身受用。渲染示例:
渲染
1 | /*左上角*/ |
运行效果图:
学完这部分后,对VAO也是有点感触,简单的解释一下VAO(个人理解):
VAO:怎么理解VAO呢,全称是顶点数组对象(Vertex Array Object),绑定后,任何随后的设置顶点属性都会存贮在VAO中(glEnableVertexAttribArray、glDisableVertexAttribArray、glVertexAttribPointer),其实VAO中存储的一系列绑定的VBO的起始位置,跨度,数据长度等属性信息,当渲染时,OpenGL会用vertex shader中layout的值,找到当前绑定的VAO中对应layout的属性信息,然后进一步找到VBO中顶点信息。
参考教程:https://learnopengl-cn.github.io/
学习交流:我的OpenGL工程
再说一次,像均值和高斯这种计算方法,意味着对临近点或者临近点权重的计算。这个平均像素值可能会也可能不会等于临近点。但是,中值像素一点存在于临近点中,相对于均值,使用中值来替换中间的像素,可以明显减少噪点(椒盐噪点)