Learning OpenGL footprints: uniform blocks using switched in the shader https://blog.csdn.net/wangdingqiaoit/article/details/52717963

EDITORIAL
Currently, we have to pass a plurality of uniform shader variables, always use a plurality of uniform, and then set the values of these variables in the main program; and if the variable to be shared between a plurality Shader, for example, projection view projection matrix and transformation matrix view, then, still need to set these variables for different shader uniform respectively. This section will introduce interface block, and based on this uniform buffer object (UBO), these technologies will simplify the transfer and sharing shader variables. This section from the sample program can be downloaded my GitHub .

本节内容参考自:
1.www.learningopengl.com Advanced GLSL
2.GLSL Tutorial – Uniform Blocks
3.《OpenGL 4.0 Shading Language Cookbook》-Using Uniform Blocks and Uniform Buffer Objects

interface block

interfac block is set inside GLSL shader input, an output, a set of variables Uniform, some similar to the C language struct, struct but not as simple and clear, there are some other options contained in it. By using the interface block, we can shader variables managed as a group, so that the writing neater.

Declaration form interface block is:

storage_qualifier block_name
{
  <define members here>
} instance_name;

Wherein storage_qualifier specified block is stored in the qualifier, the qualifier can be used in, out, uniform, or buffer (GLSL4.3 support) and the like, block_name the given name, but the name given instance instance_name.

For example, we are before the point light source to achieve the process, the need to pass between the vertex shader and fragment shader normals, texture coordinates and other variables, to package them into a block, the code is more compact. Vertex shader output variable is defined in the following form:

// 定义输出interface block
out VS_OUT
{
    vec3 FragPos;
    vec2 TextCoord;
    vec3 FragNormal; }vs_out;

In the fragment shader, in order to accept the same block_name, instance name may be different, the form can be defined as:

// 定义输入interface block
in VS_OUT
{
    vec3 FragPos;
    vec2 TextCoord;
    vec3 FragNormal; }fs_in;

If you need to add the prefix specified instance_name instance_name, these variables are referenced in the fragment shader, for example:

   // 环境光成分
    vec3    ambient = light.ambient * vec3(texture(material.diffuseMap, fs_in.TextCoord));

On the other hand, if there is no instance_name is specified, this will block the variables are global and uniform, like, it can be used directly. If not given instance_name, you need to pay attention to, interface block in a given variable name not given uniform and repeated, or cause redefinition errors such as the following definitions will result in redefinition error:

uniform MatrixBlock
{
  mat4 projection;
  mat4 modelview;
};

uniform vec3 modelview; // 重定义错误 和MatrixBlock中冲突 

Organizational variables These variables are written in dispersed form compared to before, interface block allows you to set more reasonable, logical clearer.

The main program has not changed, the point light source to achieve the same effect, the effect here is given is as follows:

Write pictures described here

As can be seen from the above, interface block does solve the problems we have been trying to organize a reasonable shader variables. This is the first problem we mentioned.

UBO concept

The second problem mentioned at the beginning of this section, how simple shared among a plurality of shader variables. GLSL can be achieved by uniform buffer. buffer to achieve uniform idea is: the same as defined in the plurality of shader Block uniform (that is above interface block, using the uniform qualifier defined), and then bind them to the corresponding uniform uniform Block Object buffer, the buffer uniform Object actually stores these variables need to be shared. Uniform block of the main program and uniform buffer object shader, is connected through the OpenGL binding point (binding points), their relationship is as shown below (from www.learningopengl.com Advanced the GLSL ):

uniform buffer

When used, uniform block each shader has an index defined, connected to the OpenGL binding by the index point x; the main program to create uniform buffer object, after the data transfer, UBO to bind the corresponding x, After the shader uniform block on the UBO and OpenGL linking data UBO of our operations in the program, we will be able to share between different shaders. Matrices index figure above example, A and B shader define points to a binding point 0, they share the openGL uboMatrices the data of UBO. Data Lights shaders while B and A of shader, each directed to different UBO.

UBO use

在上面我们介绍了UBO的概念,下面通过实例了解UBO的实际使用。UBO的实现依赖于着色器中uniform block的定义,uniform block的内存布局四种形式:shared​, packed​, std140​, and std430​(GLSL4.3以上支持),默认是shared内存布局。本节我们重点学习shared和std140这两种内存布局形式,其他的形式可以在需要时自行参考OpenGL规范

  • shared 默认的内存布局 采用依赖于具体实现的优化方案,但是保证在不同程序中具有相同定义的block拥有相同的布局,因此可以在不同程序之间共享。要使block能够共享必须注意block具有相同定义,同时所有成员显式指定数组的大小。同时shared保证所有成员都是激活状态,没有变量被优化掉。

  • std140 这种方式明确的指定alignment的大小,会在block中添加额外的字节来保证字节对齐,因而可以提前就计算出布局中每个变量的位移偏量,并且能够在shader之间共享;不足在于添加了额外的padding字节。稍后会介绍字节对齐和padding相关内容

下面通过两个简单例子,来熟悉std140和默认的shared内存布局。这个例子将会在屏幕上通过4个着色器绘制4个不同颜色的立方体,在着色器之间共享的是投影矩阵和视变换矩阵,以及为了演示shared layout而添加的混合颜色的示例。

layout std140

字节对齐的概念

字节对齐的一个经典案例就是C语言中的结构体变量,例如下面的结构体:

struct StructExample {
    char c;  
    int i;  
    short s; 
}; 

你估计它占用内存大小多少字节? 假设在int 占用4字节,short占用2个字节,那么整体大小等于 1+ 4+ 2 = 7字节吗?

答案是否定的。在Windows平台测试,当int占用4个字节,short占用2个字节是,实际占用大小为12个字节,这12个字节是怎么算出来的呢? 就是用到了字节补齐的概念。实际上上述结构体的内存布局为:

   struct StructExample {
    char c;  // 0 bytes offset, 3 bytes padding
    int i;   // 4 bytes offset short s; // 8 bytes offset, 2 bytes padding }; // End of 12 bytes

内存布局如下图所示:
Memory layout

字节对齐的一个重要原因是为了使机器访问更迅速。例如在32字长的地址的机器中,每次读取4个字节数据,所以将字节对齐到上述地址 0x0000,0x0004和0x0008, 0x000C将使读取更加迅速。否则例如上面结构体中的int i将跨越两个字长(0x0000和0x0004),需要两次读取操作,影响效率。当然关于为什么使用字节对齐的更详细分析,感兴趣地可以参考SO Purpose of memory alignment

关于字节对齐,我们需要知道的几个要点就是(参考自wiki Data structure alignment):

  • 一个内存地址,当它是n字节的倍数时,称之为n字节对齐,这里n字节是2的整数幂。

  • 每种数据类型都有它自己的字节对齐要求(alignment),例如char是1字节,int一般为4字节,float为4字节对齐,8字节的long则是8字节对齐。

  • 当变量的字节没有对齐时,将额外填充字节(padding)来使之对齐。

上面的结构体中,int变量i需要4字节对齐,因此在char后面填充了3个字节,同时结构体变量整体大小需要满足最长alignment成员的字节对齐,因此在short后面补充了2个字节,总计达到12字节。

关于字节对齐这个概念,介绍到这里,希望了解更多地可以参考The Lost Art of C Structure Packing

std140的字节对齐

std140内存布局同样存在字节对齐的概念,你可以参考官方文档获取完整描述。常用标量int,float,bool要求4字节对齐,4字节也被作为一个基础值N,这里列举几个常用的结构的字节对齐要求:

类型 对齐基数(base alignment)
标量,例如 int bool 每个标量对齐基数为N
vector 2N 或者 4N, vec3的基数为4N.
标量或者vector的数组 每个元素的基数等于vec4的基数.
矩阵 以列向量存储, 列向量基数等于vec4的基数.
结构体 元素按之前规则,同时整体大小填充为vec4的对齐基数

例如一个复杂的uniform block定义为:

   layout (std140) uniform ExampleBlock
{
    //               // base alignment  // aligned offset
    float value;     // 4 // 0 vec3 vector; // 16 // 16 (must be multiple of 16 so 4->16) mat4 matrix; // 16 // 32 (column 0) // 16 // 48 (column 1) // 16 // 64 (column 2) // 16 // 80 (column 3) float values[3]; // 16 // 96 (values[0]) // 16 // 112 (values[1]) // 16 // 128 (values[2]) bool boolean; // 4 // 144 int integer; // 4 // 148 }; 

上面的注释给出了它的字节对齐,其中填充了不少字节,可以根据上面表中给定的对齐基数提前计算出来,在主程序中可以设置这个UBO的变量:

   GLuint exampleUBOId;
    glGenBuffers(1, &exampleUBOId);
    glBindBuffer(GL_UNIFORM_BUFFER, exampleUBOId);
    glBufferData(GL_UNIFORM_BUFFER, 152, NULL, GL_DYNAMIC_DRAW); // 预分配空间 大小可以提前根据alignment计算
    glBindBuffer(GL_UNIFORM_BUFFER, 0);
    glBindBufferBase(GL_UNIFORM_BUFFER, 1, exampleUBOId); // 绑定点为1 // step4 只更新一部分值 glBindBuffer(GL_UNIFORM_BUFFER, exampleUBOId); GLint b = true; // 布尔变量在GLSL中用4字节表示 因此这里用int存储 glBufferSubData(GL_UNIFORM_BUFFER, 144, 4, &b); // offset可以根据UBO中alignment提前计算 glBindBuffer(GL_UNIFORM_BUFFER, 0);

说明: 上面最终计算出的大小为152,UBO整体不必满足vec4的字节对齐要求。152 /4 = 38,满足N的对齐要求即可。

从上面可以看到,当成员变量较多时,这种手动计算offset的方法比较笨拙,可以事先编写一个自动计算的函数库,以减轻工作负担。

std140的简单例子

下面通过一个简单例子来熟悉UBO的使用。

Step1: 首先我们在顶点着色器中定义uniform block如下:

   #version 330 core

layout(location = 0) in vec3 position; layout(location = 1) in vec3 normal; uniform mat4 model; // 因为模型变换矩阵一般不能共享 所以单独列出来 // 定义UBO layout (std140) uniform Matrices { mat4 projection; mat4 view; }; // 这里没有定义instance name,则在使用时不需要指定instance name void main() { gl_Position = projection * view * model * vec4(position, 1.0); }

Step2 在主程序中设置着色器的uniform block索引指向到绑定点0:

   // step1 获取shader中 uniform buffer 的索引
    GLuint redShaderIndex = glGetUniformBlockIndex(redShader.programId, "Matrices");
    GLuint greeShaderIndex = glGetUniformBlockIndex(greenShader.programId, "Matrices");
    ...
    // step2 设置shader中 uniform buffer 的索引到指定绑定点
    glUniformBlockBinding(redShader.programId, redShaderIndex, 0); // 绑定点为0 glUniformBlockBinding(greenShader.programId, greeShaderIndex, 0); ...

这里为了演示代码中重复写出了4个着色器,实际中可以通过vector装入这4个着色器简化代码。

Step3: 创建UBO,并绑定到绑定点0
我们需要传入2个mat4矩阵,由于mat4中每列的vec4对齐,因此两个mat4中没有额外的padding,大小即为2*sizeof(mat4)。

   GLuint UBOId;
    glGenBuffers(1, &UBOId);
    glBindBuffer(GL_UNIFORM_BUFFER, UBOId);
    glBufferData(GL_UNIFORM_BUFFER, 2 * sizeof(glm::mat4), NULL, GL_DYNAMIC_DRAW); // 预分配空间
    glBindBuffer(GL_UNIFORM_BUFFER, 0); glBindBufferRange(GL_UNIFORM_BUFFER, 0, UBOId, 0, 2 * sizeof(glm::mat4)); // 绑定点为0

Step4: 更新UBO中的数据
这里使用前面介绍的glBufferSubData更新UBO中数据,例如更新视变换矩阵如下:

 glm::mat4 view = camera.getViewMatrix(); // 视变换矩阵
glBindBuffer(GL_UNIFORM_BUFFER, UBOId);
glBufferSubData(GL_UNIFORM_BUFFER,      sizeof(glm::mat4), sizeof(glm::mat4), glm::value_ptr(view));
    glBindBuffer(GL_UNIFORM_BUFFER, 0);

通过上面的步骤,我们完成了着色器中unifrom block和UBO的连接,实现了投影矩阵和视变换矩阵在4个着色器之间的共享,绘制4个立方体如下图所示:

layout std140

验证ExampleBlock

这里在着色器中添加一段代码测试下上面那个复杂的ExampleBlock的内容,我们在主程序中设置boolean变量为true,在着色器中添加一个判断,如果boolean为true,则输出白色立方体:

   if(boolean)
    {
      color = vec4(1.0, 1.0, 1.0, 1.0); }

最终显示获得了4个全是白色的立方体,效果如下:
Four white cubes

这就验证了上述计算出那个复杂ExampleBlock的大小为152,boolean变量位移偏量为144是正确的。

layout shared

同std140内存布局方式不一样,shared方式的内存布局依赖于具体实现,因此我们无法提前根据某种字节对齐规范计算出UBO中变量的位移偏量和整体大小,因此在使用shared方式时,我们需要多次利用OpenGL的函数来查询UBO的信息。

这里在着色器中定义一个用于混合颜色的uniform block:

#version 330 core
// 使用默认shared​方式的UBO
uniform mixColorSettings {
    vec4  anotherColor;
    float mixValue; }; out vec4 color; void main() { color = mix(vec4(0.0, 0.0, 1.0, 1.0), anotherColor, mixValue); }

在出程序中首先查询UBO整体大小,预分配空间:

GLuint colorUBOId;
glGenBuffers(1, &colorUBOId);
glBindBuffer(GL_UNIFORM_BUFFER, colorUBOId);
// 获取UBO大小 因为定义相同 只需要在一个shader中获取大小即可
GLint blockSize;
glGetActiveUniformBlockiv(redShader.programId, redShaderIndex,
    GL_UNIFORM_BLOCK_DATA_SIZE, &blockSize);
glBufferData(GL_UNIFORM_BUFFER, blockSize, NULL, GL_DYNAMIC_DRAW); // 预分配空间
glBindBuffer(GL_UNIFORM_BUFFER, 0);
glBindBufferBase(GL_UNIFORM_BUFFER, 1, colorUBOId); // 绑定点为1

然后,通过查询UBO中成员变量的索引和位移偏量来设置变量值:

   // 通过查询获取uniform buffer中各个变量的索引和位移偏量
const GLchar* names[] = {
    "anotherColor", "mixValue"
};
GLuint indices[2]; glGetUniformIndices(redShader.programId, 2, names, indices); GLint offset[2]; glGetActiveUniformsiv(redShader.programId, 2, indices, GL_UNIFORM_OFFSET, offset); // 使用获取的位移偏量更新数据 glm::vec4 anotherColor = glm::vec4(0.0f, 1.0f, 1.0f, 1.0f); GLfloat mixValue = 0.5f; glBindBuffer(GL_UNIFORM_BUFFER, colorUBOId); glBufferSubData(GL_UNIFORM_BUFFER, offset[0], sizeof(glm::vec4), glm::value_ptr(anotherColor)); glBufferSubData(GL_UNIFORM_BUFFER, offset[1], sizeof(glm::vec4), &mixValue); glBindBuffer(GL_UNIFORM_BUFFER, 0);

和上面std140定义的uniform block一起工作,产生的混合颜色效果如下图所示:

Mixed color

从上面可以看到,使用shared布局时,当变量较多时,这种查询成员变量索引和位移偏量的工作显得比较麻烦。

最后的说明

This segment of the interface block concept, as well as two memory layout of UBO. This more limited section, some of the specific function is not used in this deployed presentation, may require their own reference OpenGL document . Meanwhile this paper method for calculating the complex layout of UBO std140 of offset, and the method to get UBO overall size, index and offset through the use of shared mode query, we need to try to master.

Guess you like

Origin www.cnblogs.com/dongguolei/p/11489957.html