C++OpenGL 共享布局继续随记
共享布局续篇
上一回说到共享布局,并且共享布局只学习到使用两个API glGetUniformindices() 和 glGetAcitveUniformsiv()获取不同信息类型的共享布局的uniform变量的信息,这一回继续说明glGetActiveUniformsiv()除了上回说到的偏移量、数组步长和矩阵步长外还有哪些信息。
共享布局的uniform变量信息类型
信息类型 | 获取到的信息含义 |
---|---|
GL_UNIFORM_TYPE | uniform变量的数据类型(返回值类型为GLenum) |
GL_UNIFORM_SIZE | 数组的大小(以GL_UNIFORM_TYPE为单位)。若uniform变量不是数组,则总为1. |
GL_UNIFORM_NAME_LENGTH | uniform变量名称长度 |
GL_UNIFORM_BLOCK_INDEX | uniform成员变量所属区块的索引值 |
GL_UNIFORM_OFFSET | uniform变量在区块内的偏移值 |
GL_UNIFORM_ARRAY_STRIDE | 数组内相邻成员间的字节间隔数。若uniform变量不是数组,则总为0. |
GL_UNIFORM_MATRIX_STRIDE | 列优先矩阵的每个列首元素间的字节间隔数或者行优先矩阵的每个行首元素间的字节间隔数。若uniform变量不是矩阵,则总为0. |
GL_UNIFORM_IS_ROW_MAJOR | 若uniform变量为行优先的矩阵,则为1,反之为0 |
一般就是获取偏移量,用于知道uniform变量在缓存中位置,然后将对应数据加载到对应的缓存位置中。将数据加载到缓存位置有两种方式:
1、边获取不同的uniform变量的偏移量边将偏移量通过glBufferSubData()将对应数据加载到对应缓存位置上
2、直接在代码中(C++中)使用偏移量在内存中组装缓存。
一般推荐使用第二种,因为这种调用OpenGL接口次数比较少。
而第二种方法的实现又有两种:
1、在c++代码中组装数据,然后使用glBufferSubData()直接加载数据到整个缓存中
2、使用glMapBufferRange()获取一个指向缓存的指针,然后将数据直接加载到缓存上。
下面我们使用1方法来做个例子(以下只是示例,实际上是需要做一些面向对象的封装会比较好):
//在顶点着色器中(默认是共享布局)
uniform TransformBlock
{
float scale;
vec3 translation;
float rotation[3];
mat4 projection_matrix;
} transform;
//在cpp中
//使用glGetUniformindices()获取uniform的索引值
static const GLchar* uniformNames[4]=
{
"TransformBlock.scale",
"TransformBlock.translation",
"TransformBlock.rotation",
"TransformBlock.projection_matrix"
};
GLuint uniformIndices[4];
glGetUniformindices(program,4,uniformNames,uniformIndices);
//使用glGetActiveUniformsiv()获取uniform偏移量信息
GLint uniformOffsets[4];
GLint uniformArrayStrides[4];
GLint uniformMatStrides[4];
glGetActiveUniformsiv(program,4,uniformIndices,GL_UNIFORM_OFFSET,uniformOffsets);
glGetActiveUniformsiv(program,4,uniformIndices,GL_UNIFORM_ARRAY_STRIDE,uniformArrayStrides);
glGetActiveUniformsiv(program,4,uniformIndices,GL_UNIFORM_MATRIX_STRIDE,uniformMatStrides);
//先设置cpp中缓存的scale变量(后续记得释放内存)
unsigned char* buffer = new char[4096];
//在这个cpp缓存中利用获取到的偏移量构造和共享内存一样的内存布局
*((float*)(buffer+uniformOffset[0]))=5.f;
接下去是轮到设置translation的值。这个实际上有两种实现方式,一种只利用偏移量设置cpp 的缓存对应位置的值;另一种是再利用获取到的数组步长来设置缓存:
第一种,只用偏移量:
std::vector<float> translation{4.f,3.f,2.f};
GLint offset = uniformOffset[1];
for(int i=0;i<3;++i)
{
((float*)(buffer+offset ))[i] =translation[i];
}
第二种,用偏移量和数组步长的布局信息:
std::vector<float> translation{4.f,3.f,2.f};
GLint offset = uniformOffset[1];
for(int i=0;i<3;i++)
{
*((float*)buffer+offset )) = translation[i];
offset += arrayStrides[2];
}
对于接下来的rotation数组也是同样的方法设置。
最后就是设置投影矩阵的变量projection_matrix了。需要注意的是OpenGL中矩阵默认的是列主序矩阵,就是一个矩阵数组是按照列顺序来读取矩阵的。
如,下面的矩阵数组
const GLfloat matrix[] =
{
1.0f, 2.0f, 3.0f, 4.0f,
9.0f, 8.0f, 7.0f, 6.0f,
2.0f, 4.0f, 6.0f, 8.0f,
1.0f, 3.0f, 5.0f, 7.0f
};
就是构造出矩阵:
1.0f, 9.0f, 2.0f, 1.0f,
2.0f, 8.0f, 4.0f, 3.0f,
3.0f, 7.0f, 6.0f, 5.0f,
4.0f, 6.0f, 8.0f, 7.0f,
设置投影矩阵:
// TransformBlock.projection_matrix的第一列是在缓存中的uniformOffsets[3] bytes偏移量中.
// 列是以matrixStride[3]字节作为间隔分离的并且本质是vec4s。这是源矩阵——记住它是一个列主序
// (column major)矩阵
const GLfloat matrix[] =
{
1.0f, 2.0f, 3.0f, 4.0f,
9.0f, 8.0f, 7.0f, 6.0f,
2.0f, 4.0f, 6.0f, 8.0f,
1.0f, 3.0f, 5.0f, 7.0f
};
for (int i = 0; i < 4; i++)
{
GLuint offset = uniformOffsets[3] + matrixStrides[i];
for (j = 0; j < 4; j++)
{
*((float*)(buffer+offset)) = matrix[i*4 + j];
offset += sizeof(GLfloat);
}
}
经过上面一系列折腾我们才完成了共享布局索要的C++缓存内存的配置,所以十分推荐使用标准布局来构造uniform block变量,虽然可能会牺牲一点点性能。
OK,不管选择哪种布局都可以打包好要传递给uniform block变量的缓存,接下来就是将c++的缓存传递给GLSL的uniform block中,同样的,我们需要先将c++缓存先传递给OpenGL的缓存对象中,再由缓存对象将数据转发给uniform block变量中。有两种方式,两者不同之处只是在于将uniform block变量绑定在自定义绑定点的方式:
1、先获取编译器给uniform block变量分配的索引,将这个索引值和自己指定的一个绑定点用glGetUniformBlockIndex函数进行绑定,再将缓存对象和指定的绑定点绑定,接着,程序运行时,OpenGL就会自动将绑定点上的对应缓存对象中的数据传递到uniform block变量中。
获取编译器给uniform block变量分配的索引(注意前面的都只是在获取uniform block中的成员信息)的OpenGL的API:
GLuint glGetUniformBlockIndex(GLuint program, const GLchar * uniformBlockName)
参数一就是GLSL的程序对象,参数二就是要查找uniform block变量的变量名
接着就是将获取到索引绑定到我们自己指定的绑定点,请出另一个OpenGL的API:
void glUniformBlockBinding(GLuint program, GLuint uniformBlockIndex, GLuint uniformBlockBinding);
第一个参数同样的是程序对象(已激活的),第二个就是获取到的索引值,最后一个参数就是自定义的绑定点。
接着,调用第三个API将我们的缓存对象绑定到前面的自定义绑定点上:
void glBindBufferBase(GLenum target, GLuint index, GLuint buffer);
第一个参数是绑定点的缓存类型,这里是GL_UNIFORM_BUFFER,第二个参数是绑定点,第三个参数是缓存对象。
2、第二种方式就是直接在GLSL源码中声明uniform block变量前加上binding限定符来直接指定绑定点,如
layout(std140, binding = 2) uniform TransformBlock
{
...
} transform;
然后直接调用第一个方式中的第三个API:glBindBufferBase将缓存对象绑定到绑定点上。
数据--》缓存对象--》绑定点 《-- uniform block变量
之所以这样设计这么繁琐的流程都是为了使得同一份数据可以被多个缓存对象使用,然后通过绑定点作为中间层可以让后续一个uniform block在不改变绑定点的情况下,自由的切换其上的数据输入源。
ok了,uniform block的赋值就到这吧,写挺多的了,下回继续OpenGL的学习咯。