【OpenGL编程】Uniform缓冲对象(Uniform Buffer Object)

作者:憨豆酒(YinDou),联系我[email protected],熟悉图形学,图像处理领域,本章的源代码可在此仓库中找到: https://github.com/douysu/person-summary 如果大家发现错误以及不合理之处,还希望多多指出。

  • 我的Github
  • 我的CSDN

Uniform缓冲对象(Uniform Buffer Object)

写在前面:最近在使用GLSL编程,还未掌握Uniform缓冲对象的相关知识,感觉处处碰壁,在这里对Uniform缓冲对象相关知识进行总结,同时本节案例是基于OpenGL ES 3.0实现的,

本篇内容参考来自:

  • LearnOpenGL CN——高级GLSL一章
    https://learnopengl-cn.github.io/04%20Advanced%20OpenGL/08%20Advanced%20GLSL/#_3
  • The fool的博客 http://blog.csdn.net/wangdingqiaoit/article/details/52717963
    感谢The fool的文章,他的文章中的部分内容非常有助于理解Uniform缓冲对象,我在此将两篇文章的重要内容进行了合并。

什么是Unform缓冲对象?

在没有掌握Uniform缓冲对象时,已经可以开发出很多高级特效了,但同时也遇到了一些繁琐的事情。比如说,当在一个项目(Project)中出现多个着色器,每一个着色器中大部分的Uniform变量都是相同的,例如:摄像机位置、光源位置此类所有物体都共同的属性等,每一次都需要我们重复的去设置它们,这便是一件繁琐的事情。在大型游戏中,多则上百个着色器,以前者的方式是很容易带来问题的。
OpenGL给我们提供了Uniform缓冲对象,该对象允许我们在多个着色器中定义全局的Uniform变量,在多个着色器中使用摄像机位置、光源位置时,不必多次重复设置,只需设置一次即可。这样做并不代表我们可以不用在着色器中定义Uniform变量了,这样只不过减少了宿主语言(C++、Java)的工作量。

什么是Unform块布局?

Uniform块的内容是存储在缓冲对象中的,它实际上只是一块预留的内存,这块内存并不知道它具体保存的是什么类型的数据,我们还需要告诉OpenGL内存中哪一部分对应哪一个Uniform变量。
假设着色器有以下的Uniform块:

	layout (std140) uniform ExampleBlock{
    	float value;
    	vec3  vector;
    	mat4  matrix;
    	float values[3];
    	bool  boolean;
    	int   integer;
	};

这里的std140就代表着一种Uniform块的布局,Uniform块的布局常见的有如下三种。

  • shared(共享布局):默认情况下,GLSL都会使用这个布局,共享一但硬件定义了偏移量,它们在整个程序中都是共享的,使用shared布局时,允许GLSL对Uniform块中的变量的存储位置进行变动,共享布局可以节省很多空间的优化。
  • std140布局:std140布局中确定每个变量的偏移都是基于一系列的规则而决定的,这显示的声明了每个变量类型的内存布局,需要我们手动去计算每个变量的偏移量。
  • packed(紧凑布局):是不能保证这个布局在每个程序中保持不变的(即非共享),因为它允许编译器去将uniform变量从Uniform块中优化掉,这在每个着色器中都可能是不同的,所以这种布局是无法共享的,不满足Uniform Buffer的使用需求。
    在使用Uniform缓冲时我们需要知道每个变量的字节和偏移量,每一个变量的大小都是确定的,我们在学习任何一门语言的初始都会学习变量的大小(字节),例如int类型的变量所占字节为4 Byte。但是Uniform块中变量的间距是无法确定的,这就允许硬件能够将变量放置在合适的位置,例如,一些硬件可能会将一个向量a放在float b之后,其他的硬件可能会将向量a放在float b之前。这个特性虽然合理的安排了变量的存储,但是却给我们使用Uniform缓冲带来了麻烦,因为有了这个特性,我们就无法知道整个Uniform块所占的字节,在初始分配Uniform缓冲时也无法合理的进行分配。
    为了满足我们Uniform缓冲的需要,我们使用的Uniform块通常都为std140布局,这就需要我们手动去计算每个变量的偏移量,这里便涉及到字节对齐的概念。

字节对齐的概念:

字节对齐最经典的案例就是C/C++中结构体的使用。例如。

struct MyByteStruct{
	char c;
	float a;
	int b;
};
/*按照正常理解为 4+4+1=9*/
/*此处使用了字节对齐,使机器访问更快,真是为4+4+4=12 */

按照我们通常的思维,此结构体所占的字节的 4(float)+4(int)+1(char)=9,但是事实并非如此,真实的所占的字节为4(float)+4(int)+4(char)=12。为什么会这样?
该结构体的内存布局如下所示:
这里写图片描述
字节对齐重要的一个原因就是为了使机器访问的更加快速,例如在32字长的地址的机器中,每次读取4个字节数据,所以将字节对齐到上述地址 0x0000,0x0004和0x0008, 0x000C将使读取更加迅速。
如果没有第一个padding,那么上面结构体中的int i将跨越两个字长(0x0000和0x0004),需要两次读取操作,影响效率。

std140布局的计算:

上文介绍std140布局时已经提到每个变量的偏移都是基于一系列的规则而决定的,OpenGL中的Uniform缓冲的规范为,GLSL中的每个变量,比如说int、float和bool,都被定义为4字节量。每4个字节将会用一个N来表示。:
【OpenGL编程】Uniform缓冲对象(Uniform Buffer Object)_第1张图片
以上文的结构体为例,对当前的对齐偏移量进行计算得到如图的结果,第一幅图为LearnOpenGL中的相关教程,第二幅图为我自己绘制的结构图,都可进行参考。
【OpenGL编程】Uniform缓冲对象(Uniform Buffer Object)_第2张图片

Uniform缓冲的对应关系。

首先如何才能让OpenGL ES知道哪个Uniform缓冲对应的是哪个Uniform块呢?这里使用的便是绑定点的知识(Binding),可以参考LearnOpenGL中的教程,如下。
【OpenGL编程】Uniform缓冲对象(Uniform Buffer Object)_第3张图片
可以看出,每一个Uniform缓冲对象都是绑定到对应的绑定点的,不同的着色器在使用对应的Uniform缓冲时,只需对应到相应Uniform缓冲的绑定点即可。

OpenGL ES的Uniform缓冲的案例

我完成了一个非常简单的基于OpenGL ES 3.0的一个Uniform缓冲的案例,案例中有两套着色器,一套用于绘制红色三角形,一套用于绘制绿色三角形 。
【OpenGL编程】Uniform缓冲对象(Uniform Buffer Object)_第4张图片
首先给出案例中绘制红色三角形顶点着色器的代码。

#version 300 es
uniform mat4 Modelview;//总变换矩阵
in vec3 Position;//顶点位置
out vec4 vColor1;//输出到片元着色器的颜色
layout (std140) uniform myColor{//输入的Uniform缓冲
    vec4 color1;
    vec4 color2;
};
void main(){
    gl_Position = Modelview * vec4(Position,1);//根据总变换矩阵计算此次绘制此顶点位置
    vColor1=color1;
}

着色器中的两个颜色变量color1和color2均来自于Uniform缓冲,在母体语言(C++)中我只传入了一次颜色值,同时可以在两套着色器中使用,这完全符合Uniform缓冲的使用规则。接着详细介绍如何分配一个Uniform缓冲。
首先通过以下代码分配一个内容字节大小为32 byte的Uniform缓冲,并将缓冲绑定到绑定点0。

void MyGLThread::iniUniformBuffer(){
    glGenBuffers(1, &uboExampleBlock);
    glBindBuffer(GL_UNIFORM_BUFFER, uboExampleBlock);
    glBufferData(GL_UNIFORM_BUFFER, 32, NULL, GL_STATIC_DRAW); // 分配32字节的内存
    glBindBuffer(GL_UNIFORM_BUFFER, 0);
}

然后通过以下代码得到着色器中得到Uniform缓冲的引用,只需要获取一次即可,不需要再另一套着色器中继续获取。

uniformBufferindex=glGetUniformBlockIndex(mProgram, "myColor");//得到Uniform缓冲的引用

最后通过以下代码将数据送入Uniform缓冲

glUniformBlockBinding(mProgram, uniformBufferindex, 0);//设置绑定点为0
    glBindBufferBase(GL_UNIFORM_BUFFER, 0, MyGLThread::uboExampleBlock);//绑定Uniform缓冲对象到对应的绑定点上
    glBindBuffer(GL_UNIFORM_BUFFER, MyGLThread::uboExampleBlock);//绑定Uniform缓冲
    float colorArray[8]={
            1.0f,0.0f,0.0f,1.0f,
            0.0f,1.0f,0.0f,1.0f
    };
    glBufferSubData(GL_UNIFORM_BUFFER, 0, 32, &colorArray);
    glBindBuffer(GL_UNIFORM_BUFFER, 0);

最后:

我贴出来的只是部分代码,建议各位朋友们自习研读LearnOpenGL章节中的代码,当然阅读我的案例中的也是可以的。关于Uniform缓冲的内容还是比较重要的。如果有什么错误和不合理的地方还希望大家多多指出,我会虚心接受每一个建议。

你可能感兴趣的:(3D图形学)