OpenGLES uniform和 Uniform Buffer Object

原文地址:http://www.zwqxin.com/archives/shaderglsl/communication-between-opengl-glsl-2.html

本文从OpenGL和GLSL之间数据的传递这一点 ,介绍Unform Buffer Objecct(UBO)这一重要特性。

1. attribute变量

绑定方法1

GLuint nHandle = glCreateProgram()

glAttachShader(nHandle, nShaderHandle1);
....

glBindAttribLocation(nHandle, 2,  "attribName");
glLinkProgram(nHandle);

GLSL中每一个attribute变量都有一个“位置”值(Location),在ShaderProgram链接(link)前,可以Bind之,链接之后,可以Get之。通过这种方式都可以建立attribute变量与顶点属性的联系。如今引入另一种——直接在GLSL代码中指定这些位置值

#version 300  es
  
layout(location = 0) in vec3 attrib_position;  
layout(location = 1) in vec2 attrib_texcoord;  
layout(location = 2) in vec3 attrib_normal;  
layout(location = 3) in int  attrib_clustercount;  

这里使用了layout关键字(通常是layout(layoutAttrib1=XXX, layoutAttrib2=XXX, ...)这样的形式)。这个关键字用于一个具体变量前,用于显式标明该变量的一些布局属性,这里就是显式设定了该attribute变量的位置值(location),其作用跟ShaderProgram(着色程序)链接前调用glBindAttribLocation来设定atribute变量的位置值是等效的。

为什么采用这种方式更好呢?其一当然是编码量减少了,二来也避免了去Get某个attribute的location带来的开销,三来,最重要的是,它重定义了OpenGL和GLSL之间attribute变量属性的依赖。过去我们的OpenGL端必须首先要知道GLSL端某个attribute的名字,才能设置/获得其位置值,如今两者只需要location对应起来就可以完成绘制时顶点属性流的传递了。不再需要在ShaderProgram的compile和link之间插入代码也更方便于其模块化。

2.uniform变量

 #version 300 es 
  
uniform sampler2D   basetex1;  
uniform float fAlphaRestrictVal;  
uniform mat4 matModel;  
uniform mat4 matView;  
uniform mat4 matProj;  

每一个uniform变量也都有其一个“位置值”(Location),在OpenGLES中,我们可以通过glGetUniformLocation来获得。那么我们可以不可以像attribute变量那样,在Shader代码中显式指定这个Location呢?(其好处也是跟上述差不多的,但就是如果uniform变量太多的话这样做也麻烦,因为得在代码中一个一个指定不重复的location。)

那我们就像往常一样,在glUseProgram启用了某个ShaderProgram之后,一个一个地给每个unifom变量关联数据咯(通过其location)——等等,这是在运行期间设置数据值吧,那如果我这个关联数据并不是每帧都变化的,甚至它是一个固定值,这样做岂不太无聊太浪费了?事实上我们还是可以在glUseProgram之外绑定数据的——乃至直接在初始化时

这得益于glProgramUniform系列函数的引入,它比起往常的glUniform要多一个参数用来接收一个ShaderProgram的ID。在建立ShaderProgram后,我们也不需要glUseProgram来预先绑定它就可以直接取得某个uniform变量的location值并用glProgramUniform系列函数关联数据,而且这个数据在其后运行期间的每次glUseProgram后都不会失效。从理论上将,这族函数完全可以替代glUniform系列函数(是它们功能的一个超集),但是就不知道会不会有性能上的损失了(这个暂时目前找不到说法),所以我暂时建议是只对那些非动态变化的uniform变量使用了。

再来看看uniform变量的问题。通常一个稍微复杂点点、更多控制参数的Shader,都会有大量的Uniform变量需要设置,所以导致了我们很多时候在glUseProgram之后要调用一长串的glUniform函数来传递该Pass的数据。有没有方法尽量把这些操作合并呢?另外,我们知道一个Shader的可用Uniform数据大小是有一个上限值的(例如我目前显卡的一个vertex shader的GL_MAX_VERTEX_UNIFORM_COMPONENTS值是4096,意味着我在一个VertexShader里使用的active uniforms,大概就是最多4096个float/int值了,或者说最多1024个vec4、最多256个mat16),那么有没办法提高这个上限呢?

Uniform Buffer Object(UBO)

UBO,顾名思义,就是一个装载Uniform变量数据的Buffer Object。反正就是显存中一块用于储存特定数据的区域了。在OpenGL端,它的创建、更新、销毁的方式都与其他Buffer Object没什么区别,我们只不过把一个或多个uniform数据交给它,以替代glUniform的方式传递数据而已。这里必须明确一点,这些数据是给到这个UBO,存储于这个UBO上,而不再是交给ShaderProgram,所以它们不会占用这个ShaderProgram自身的uniform存储空间,所以UBO是一种全新的传递数据的方式,从路径到目的地,都跟传统uniform变量的方式不一样。自然,对于这样的数据,在Shader中不能再使用上面代码中的方式来指涉了。随着UBO的引入,GLSL也引入了uniform block这种指涉工具。

  1. 更新一组大量统一值 更简单
  2. 不同程序间的共享一组统一值
#version 300 es
  
layout(std140) uniform matVP  
{  
   mat4 matProj;  
   mat4 matView;  
};  

uniform block是Interface block的一种,(layout意义容后再述)在unifom关键字后直接跟随一个block name和大括号,里面是一个或多个uniform变量。一个uniform block可以指涉一个UBO的数据——我们要把block里的uniform变量与OpenGL里的数据建立关联。 因为这些uniform变量不是存储在Shader的“uniform区域”里的,所以也就没有那一套“位置值”(location),那么我们通过什么建立关联呢?

对于每一个uniform block,都有一个“索引值”(index),这个索引值我们可以在OpenGL中获得,并把它与一个具体的UBO关联起来。这样block内的数据声明就会与UBO中的实质数据联系起来了:

GLint nMatVPBlockIndex = glGetUniformBlockIndex(nProgramHandler, "matVP");  
  
//GLint nMatVPBlockIndex = glGetProgramResourceIndex(nProgramHandler, GL_UNIFORM_BLOCK, "matVP");  
  
if (GL_INVALID_INDEX != nMatVPBlockIndex)  
{  
    GLint nBlockDataSize = 0;  
  
    glGetActiveUniformBlockiv(nProgramHandler, nMatVPBlockIndex, GL_UNIFORM_BLOCK_DATA_SIZE, &nBlockDataSize);  
  
    glGenBuffers(1, &m_nUBO);  
  
    glBindBuffer(GL_UNIFORM_BUFFER, m_nUBO);  
  
    glBufferData(GL_UNIFORM_BUFFER, nBlockDataSize, NULL, GL_DYNAMIC_DRAW);  
  
    glBindBufferRange(GL_UNIFORM_BUFFER, 0, m_nUBO, 0, nBlockDataSize);  
  
    glUniformBlockBinding(nProgramHandler, nMatVPBlockIndex, 0);  
  
    glBindBuffer(GL_UNIFORM_BUFFER, NULL);  
}  

一般我们可以使用glGetUniformBlockIndex来获取这个Index,但扩展GL_ARB_program_interface_query引入了比较统一的获取ShaderProgram内资源的相关属性的API(详见此扩展的spec),所以也可以以GL_UNIFORM_BLOCK调用glGetProgramResourceIndex来获取资源的Index。得到名为matVP的uniform block的Index后,我们可以查询这个block的相关信息(glGetActiveUniformBlockiv)。为了建立合适大小的UBO,这里查询了这个block所需的字节大小(GL_UNIFORM_BLOCK_DATA_SIZE)的值(注意这个值代表此block所占的大小,它可能会比block内数据实际相加后的值要大,下面会再述)。

建立一个UBO的过程跟建立其他类型的Buffer Object相似,不过Target是GL_UNIFORM_BUFFER,数据为空。接下来是把一个UBO(ID为m_nUBO)和Shader内的uniform block(Index为nMatVPBlockIndex)相关联:把它们都关联到同一个uniform buffer binding-point。其中前者通过glBindBufferBase或glBindBufferRange来完成,其中第二个参数就是binding-point,这里选择的是binding-point_0(参数值为0,当然你可以输入1、2、3...以选择binding-point_1、binding-point_2、binding-point_3…);同样,对于后者uniform block,也通过glUniformBlockBinding来完成,其中第三个参数是binding-point,这里同样选择了第0个binding-point——这样OpenGL端的UBO和GLSL端的uniform block就联系在一起了。Shader中需要使用block中的uniform变量时,就会索引到对应的UBO中对应的位置的数据。

所谓binding-point(或者说binding-location),我理解为是OpenGL的Context上的一个个状态位。通常来说,我们可以建立非常多的UBO,它们的数据区在显存中,以ID标识,一般通过Context绑定一个UBO的ID的方式让OpenGL去寻找对应的显存位置——这是一种非常耗时的操作(应该说,所有bind类的操作都是)。数据需要更新就算了,但如果Shader执行时也必须为每个uniform block去绑定、寻觅数据区……为避免这样的情况所以就需要一个足以减少消耗的桥梁物,这个中间物件保存着能够直达具体某个UBO数据区的“方式”(不妨暂假想为该数据区的起始显存地址、长度等),然后我们把这个中间物件的位置告诉Shader,让Shader在需要时直接“来到”这个中间件中获取某个显存区的实质数据。这里与前者最大的区别应该就是Shader到中间件的用时——这应该足够快。所以首先这个中间物件应该存储在OpenGL的Context上(于是它名义上就是一个OpenGL状态),OpenGL内的对象的交流是比较便捷的,至少比Bind方式去存取“遥远的”显存数据要快不少,其次这个中间物件自身也应该容易表示,让Shader能“直接认门牌”——这些中间物件就是单纯Zero-Base数字序列形式的uniform binding-point,OpenGL通过它一步定位到实质数据处。

OpenGL Context本身也应该是一个尽量小体积的东西,所以不便在它身上放太多这种binding-point。在我的显卡上,GL_MAX_UNIFORM_BUFFER_BINDINGS的个数为36,这表示同一时间能映射的UBO-uniform block关系最多只有36对(间接也限制了一个ShaderProgram中uniform block的个数),哪怕你有大量的UBO,为了以上机制的实行,也只能接受这个限制。我们就是通过glBindBufferBase/glBindBufferRange来我UBO或UBO中的某分区的信息存储至某个binding-point上,然后通过glUniformBlockBinding来“通知”ShaderProgram某个uniform block的数据信息存储在哪个binding-point上。如果把glUniformBlockBinding当成glUniform族函数,这个操作会更亲切一点:只不过如今对于目标block使用的是Index而不是Location(事实上它的行为更类似上面提到的glProgramUniform族函数,因为不需要事先glUseProgram启用某个ShaderProgram而是作为首参罢)。

写的很乱,以后再来整理

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值