作者:憨豆酒(YinDou),联系我[email protected],熟悉图形学,图像处理领域,本章的源代码可在此仓库中找到: https://github.com/douysu/person-summary 如果大家发现错误以及不合理之处,还希望多多指出。
写在前面:最近在使用GLSL编程,还未掌握Uniform缓冲对象的相关知识,感觉处处碰壁,在这里对Uniform缓冲对象相关知识进行总结,同时本节案例是基于OpenGL ES 3.0实现的,
本篇内容参考来自:
在没有掌握Uniform缓冲对象时,已经可以开发出很多高级特效了,但同时也遇到了一些繁琐的事情。比如说,当在一个项目(Project)中出现多个着色器,每一个着色器中大部分的Uniform变量都是相同的,例如:摄像机位置、光源位置此类所有物体都共同的属性等,每一次都需要我们重复的去设置它们,这便是一件繁琐的事情。在大型游戏中,多则上百个着色器,以前者的方式是很容易带来问题的。
OpenGL给我们提供了Uniform缓冲对象,该对象允许我们在多个着色器中定义全局的Uniform变量,在多个着色器中使用摄像机位置、光源位置时,不必多次重复设置,只需设置一次即可。这样做并不代表我们可以不用在着色器中定义Uniform变量了,这样只不过减少了宿主语言(C++、Java)的工作量。
Uniform块的内容是存储在缓冲对象中的,它实际上只是一块预留的内存,这块内存并不知道它具体保存的是什么类型的数据,我们还需要告诉OpenGL内存中哪一部分对应哪一个Uniform变量。
假设着色器有以下的Uniform块:
layout (std140) uniform ExampleBlock{
float value;
vec3 vector;
mat4 matrix;
float values[3];
bool boolean;
int integer;
};
这里的std140就代表着一种Uniform块的布局,Uniform块的布局常见的有如下三种。
字节对齐最经典的案例就是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布局时已经提到每个变量的偏移都是基于一系列的规则而决定的,OpenGL中的Uniform缓冲的规范为,GLSL中的每个变量,比如说int、float和bool,都被定义为4字节量。每4个字节将会用一个N来表示。:
以上文的结构体为例,对当前的对齐偏移量进行计算得到如图的结果,第一幅图为LearnOpenGL中的相关教程,第二幅图为我自己绘制的结构图,都可进行参考。
首先如何才能让OpenGL ES知道哪个Uniform缓冲对应的是哪个Uniform块呢?这里使用的便是绑定点的知识(Binding),可以参考LearnOpenGL中的教程,如下。
可以看出,每一个Uniform缓冲对象都是绑定到对应的绑定点的,不同的着色器在使用对应的Uniform缓冲时,只需对应到相应Uniform缓冲的绑定点即可。
我完成了一个非常简单的基于OpenGL ES 3.0的一个Uniform缓冲的案例,案例中有两套着色器,一套用于绘制红色三角形,一套用于绘制绿色三角形 。
首先给出案例中绘制红色三角形顶点着色器的代码。
#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缓冲的内容还是比较重要的。如果有什么错误和不合理的地方还希望大家多多指出,我会虚心接受每一个建议。