当前我们在着色器中要传递多个uniform变量时,总是使用多个uniform变量,然后在主程序中分别设置这些变量的值;另外,如果要在多个shader之间共享变量,例如投影矩阵projection和视变换矩阵view的话,仍然需要为不同shader分别设置这些uniform变量。本节将为大家介绍interface block,以及uniform buffer object(UBO),这些技术将简化着色器中变量的传递和共享问题。
Interface blocks 接口块
interfac block是一组GLSL着色器里面的输入、输出、uniform等变量的集合,有一些类似于C语言中的struct,但是不像struct那样简单明了,还有一些其他的选项包含在里面。
接口块的一般形式:
storage_qualifier block_name
{
<define members here>
} instance_name;
存储限定符(storage_qualifier)定义创建的接口块类型。包括下列几种:
- uniform
- in or out (requires OpenGL 3.2)
- buffer (requires OpenGL 4.3 or ARB_shader_storage_buffer_object)
举个例子,我们声明一个叫做vs_out的接口块,它打包了我们希望发送到下一个着色器中的所有输出变量:
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoords;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
out VS_OUT
{
vec2 TexCoords;
} vs_out;
void main()
{
gl_Position = projection * view * model * vec4(aPos, 1.0);
vs_out.TexCoords = aTexCoords;
}
然后我们在接下来的片元着色器中定义一个输入接口块。块名(Block Name)应该是和着色器中一样的(VS_OUT),但实例名(Instance Name)(顶点着色器中用的是vs_out)可以是随意的。
#version 330 core
out vec4 FragColor;
in VS_OUT
{
vec2 TexCoords;
} fs_in;
uniform sampler2D texture;
void main()
{
FragColor = texture(texture, fs_in.TexCoords);
}
如果指定了instance_name,则在片元着色器中引用这些变量时需要加上instance_name前缀,例如:
// 环境光成分
vec3 ambient = light.ambient * vec3(texture(material.diffuseMap, fs_in.TextCoord));
反之如果没有指定instance_name,则这个block中的变量将和uniform一样是全局的,可以直接使用。如果没有给定instance_name,则需要注意,interface block中给定的变量名不要和uniform给定的重复,否则造成重定义错误,例如下面的定义将造成重定义错误:
uniform MatrixBlock
{
mat4 projection;
mat4 modelview;
};
uniform vec3 modelview; // 重定义错误 和MatrixBlock中冲突
Uniform buffer objects Uniform缓冲对象
问题:我们在编写shader的时候,会发现又很多数据,比如VP矩阵,是几乎每个shader都可以用到的,可是我们还需要每次都给各个shader设置。
方案:如何解决在多个shader之间共享变量的问题呢?OpenGL为我们提供了一个叫做Uniform缓冲对象(Uniform Buffer Object)的工具,即UBO,它允许我们定义一系列在多个着色器中相同的全局Uniform变量。当使用Uniform缓冲对象的时候,我们只需要设置相关的uniform一次。当然,我们仍需要手动设置每个着色器中不同的uniform。
uniform buffer的实现思路为:在多个着色器中定义相同的uniform block(就是上面的interface block,使用uniform限定符定义),然后将这些uniform block绑定到对应的uniform buffer object,而uniform buffer object中实际存储这些需要共享的变量。着色器中的uniform block和主程序中的uniform buffer object,是通过OpenGL的绑定点(binding points)连接起来的,它们的关系如下图所示:
使用时,每个shader中定义的uniform block有一个索引,通过这个索引连接到OpenGL的绑定点x;而主程序中创建uniform buffer object,传递数据后,将这个UBO绑定到对应的x。这样shader中的uniform block就和OpenGL中的UBO联系起来,我们在程序中操作UBO的数据,就能够在不同着色器之间共享了。
Uniform块布局
Uniform块的内容是储存在一个缓冲对象中的,它实际上只是一块预留内存。因为这块内存并不会保存它具体保存的是什么类型的数据,我们还需要告诉OpenGL内存的哪一部分对应着着色器中的哪一个uniform变量。
UBO的实现依赖于着色器中uniform block的定义,uniform block的内存布局四种形式:shared, packed, std140, and std430(GLSL4.3以上支持),默认是shared内存布局。本节我们重点学习shared和std140这两种内存布局形式,其他的形式可以在需要时自行参考OpenGL规范。
- shared. 共享布局,可省略不写。使用共享布局时,GLSL是可以为了优化而对uniform变量的位置进行变动的,只要变量的顺序保持不变。因为我们无法知道每个uniform变量的偏移量,我们也就不知道如何准确地填充我们的Uniform缓冲了。我们能够使用像是glGetUniformIndices这样的函数来查询这个信息。
- std140. 这种方式明确的指定alignment的大小,会在block中添加额外的字节来保证字节对齐,因而可以提前就计算出布局中每个变量的位移偏量,并且能够在shader之间共享;不足在于添加了额外的padding字节。
Uniform块的内容是储存在一个缓冲对象中的,它实际上只是一块预留内存。因为这块内存并不会保存它具体保存的是什么类型的数据,我们还需要告诉OpenGL内存的哪一部分对应着着色器中的哪一个uniform变量。
假设着色器中有以下Uniform块:
layout (std140) uniform ExampleBlock
{
float value;
vec3 vector;
mat4 matrix;
float values[3];
bool boolean;
int integer;
};
我们需要知道的是每个变量的大小(字节)和(从块起始位置的)偏移量,来让我们能够按顺序将它们放进缓冲中。每个元素的大小都是在OpenGL中有清楚地声明的,而且直接对应C++数据类型,其中向量和矩阵都是大的float数组。OpenGL没有声明的是这些变量间的间距(Spacing)。这允许硬件能够在它认为合适的位置放置变量。比如说,一些硬件可能会将一个vec3放置在float边上。不是所有的硬件都能这样处理,可能会在附加这个float之前,先将vec3填充(Pad)为一个4个float的数组。这个特性本身很棒,但是会对我们造成麻烦。
「如果这篇文章对你有用,请随意打赏」
如果这篇文章对你有用,请随意打赏
使用微信扫描二维码完成支付

comments powered by Disqus