Uniform变量
https://zhuanlan.zhihu.com/p/569758915
uniform变量¶
-
uniform是全局的(Global)。全局意味着uniform变量必须在每个着色器程序对象中都是独一无二的,而且它可以被着色器程序的任意着色器在任意阶段访问。
-
无论你把uniform值设置成什么,uniform会一直保存它们的数据,直到它们被重置或更新。
-
如果你声明了一个uniform却在GLSL代码中没用过,编译器会静默移除这个变量,导致最后编译出的版本中并不会包含它,这可能导致几个非常麻烦的错误。
-
给uniform变量添加数据:我们首先需要找到着色器中uniform属性的索引/位置值。当我们得到uniform的索引/位置值后,我们就可以更新它的值了。
-
我们用glGetUniformLocation查询uniform ourColor的位置值。我们为查询函数提供着色器程序和uniform的名字(这是我们希望获得的位置值的来源)。如果glGetUniformLocation返回-1就代表没有找到这个位置值。
-
我们可以通过glUniform4f函数设置uniform值。注意,查询uniform地址不要求你之前使用过着色器程序,但是更新一个uniform之前你必须先使用程序(调用glUseProgram),因为它是在当前激活的着色器程序中设置uniform的。
全局亦是,有专门的函数将该结果传给GPU或CPU。
Uniform数据块¶
类似C语言的结构体,当创建了一个结构体之后,对应缓存就会产生一个数据块,这个数据块对应了你定义的结构体实体。在CPU与GPU之间也存在这样的数据块,那就是uniform数据块和buffer数据块。
如果CPU和GPU之间传递单一Uniform变量,
只需要在着色器中写如下代码,例如顶点着色器
#version 450
layout (location = 0) in vec4 vPos;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
gl_position = projection * view * model * vec4(vPos,1.0f);
}
代码定义了三个uniform变量,model view projection,用来对输入的顶点数据进行矩阵变换。
在CPU执行的代码中,首先通过
类似上述的gl函数获得model名称对应的location,然后使用
这样的方式来传递数据到着色器。
上述方式是比较简单的方法,那么如果需要使用数据块的方式批量传入数据到GPU怎么办呢?也许uniform块的东西就开始有用起来了。
定义uniform块的方式如下,修改着色器代码
#version 450
layout (location = 0) in vec4 vPos;
uniform matBlock{
mat4 model;
uniform mat4 view;
uniform mat4 projection;
};
void main()
{
gl_position = projection * view * model * vec4(vPos,1.0f);
}
红宝书上说,如果给matBlock添加了新的名字matBlockName,那么着色器就可以使用'.'操作符来访问成员,否则只能通过名字来访问。
对于uniform的布局,我认为我还处于初级阶段,暂时不需要考虑布局的问题,可以通过
layout (...) 修饰符来控制uniform的布局问题,见红宝书P46
笔者认为,重要的是CPU和GPU用相同的方式解析一段内存,如果着色器和CPU代码保持默认的布局方式那么不会影响uniform数据块的数据传递。
到此,使用uniform块传递数据的工作,运行在GPU端的着色器代码完成了,接下来考虑CPU端如何访问。
应用程序访问uniform块¶
GLint UBOSize;
enum{
enum_model=0,
enum_view,
enum_projection,
NumberOfUniform,
};
void uinformBlockInit(void){
//获取uniform块的index
matblockPos = glGetUniformBlockIndex(program , "matBlock");
//获得uniform块的大小
glGetActiveUniformBlockiv(program,matblockPos,GL_UNIFORM_BLOCK_DATA_SIZE,&UBOSize);
printf("uniform size = 0x%x\r\n",UBOSize);
//创建uniform块大小的buffer空间
GLvoid * buffer = malloc(UBOSize);
if(buffer == NULL){
printf("malloc failed\r\n");
}else{
printf("malloc success\r\n");
}
//初始化需要写入的值
glm::mat4 model;
model = glm::translate(model, glm::vec3( 0.0f, 0.0f, 0.0f));
glm::mat4 projection;
projection = glm::perspective(glm::radians(45.0f), 800.0f / 600.0f, 0.1f, 100.0f);
glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 3.0f);
glm::vec3 cameraFront = glm::vec3(0.0f, 0.0f, -1.0f);
glm::vec3 cameraUp = glm::vec3(0.0f, 1.0f, 0.0f);
glm::mat4 view;
view = glm::lookAt( cameraPos,
cameraPos + cameraFront,
cameraUp);
//建立变量名称数组
const char * names[NumberOfUniform] =
{
"model",
"view",
"projection",
};
//查询每个uniform成员对应的属性
GLuint indices[NumberOfUniform];
GLint size[NumberOfUniform];
GLint offset[NumberOfUniform];
GLint type[NumberOfUniform];
glGetUniformIndices(program,NumberOfUniform,names,indices);
glGetActiveUniformsiv(program,NumberOfUniform,indices,GL_UNIFORM_OFFSET,offset);
glGetActiveUniformsiv(program,NumberOfUniform,indices,GL_UNIFORM_SIZE,size);
glGetActiveUniformsiv(program,NumberOfUniform,indices,GL_UNIFORM_TYPE,type);
for(GLuint i = 0 ; i< NumberOfUniform ; i++){
printf("\r\n-------------------------\r\n");
printf("uniform name = %s \r\n", names[i]);
printf("uniform indices = %d \r\n",indices[i]);
printf("uniform offset = %d\r\n",offset[i]);
printf("uniform size = %d\r\n",size[i]);
printf("uniform type = 0x%x\r\n",type[i]);
}
/* 将数据存入buffer
* mat4 是长度是 4 * 4 个 GLfloat, GLfloat的大小是4个字节
*/
memcpy(buffer + offset[enum_model] ,glm::value_ptr(model),size[enum_model] * 16 * 4);
memcpy(buffer + offset[enum_view] ,glm::value_ptr(view),size[enum_view] * 16 * 4);
memcpy(buffer + offset[enum_projection] ,glm::value_ptr(projection),size[enum_projection] * 16 * 4);
/*创建GL_UNIFORM_BUFFER类型的buffer */
glGenBuffers(1,&vbo_uniform);
glBindBuffer(GL_UNIFORM_BUFFER,vbo_uniform);
glBufferData(GL_UNIFORM_BUFFER,UBOSize,buffer,GL_STATIC_DRAW);
//绑定VBO和uniform块
glBindBufferBase(GL_UNIFORM_BUFFER,matblockPos,vbo_uniform);
}
如上述代码,应用程序访问uniform块主要有如下几个步骤
- 通过名称找到着色器对应名称的uniform块的index,glGetUniformBlockIndex函数
- 通过glGetActiveUniformBlockiv函数根据uniform的index信息获取uniform块的大小信息,为后面CPU在创建和GPU uniform块一样大的内存空间做准备。
- CPU这边在堆中获取一块和uniform块一样大的内存空间。
- 通过glGetUniformIndices函数获取uniform块中每一个成员的index信息,存放在indices[NumberOfUnifrom]中,并且使用glGetActiveUniformsiv函数配合获得的indices[NumberOfUnifrom]信息,获得每一个uniform块中的成员变量的其他基础信息,例如OFFSET,大小,以及成员的数据类型。(官方例程中还给出了根据GL定义的数据类型换算对应大小空间的函数--TypeSize,但是我这边因为想简化代码去掉了这部分函数,我知道mat4是由4 * 4个float型数据构成的,所以一个mat4的长度是 4 * 4 * 4 个字节)
- 创建VBO,缓存类型是GL_UNIFORM_BUFFER类型,然后将我们需要写入uniform块的数据写入CPU创建的buffer缓存中 。同样的使用glBufferData来为这个VBO填入数据,显然,我们将和GPU uniform块同样大小的CPU数据块buffer填入缓存。
- 最后使用glBindBufferBase函数绑定vbo缓存对象和uniform块的index,完成链接。
上述过程完成后,CPU和GPU就共享了一块内存,达到了使用uniform块传递数据的目的。
OpenGL编程指南(原书第9版)Page 52
glBufferData的第三个参数作者应该是想写 GL_STATIC_DRAW,但是笔误了[狗头]。
小结¶
我原先是使用分立的glUniformMatrix4fv函数来单独的为每一个uniform变量赋值,但是现在改为了使用uinform块进行数据传递,效果和单个的一致,所以我认定我的使用方法是无误的。
这里应该是沿用了C语言结构体相关的理念,按照数据块来完成赋值,传递信息,十分精妙!