GLSLvary、atrribute、in、out的区别
这个问题困扰我很久了,前⼀段时间⾯试,⾯试官问我GLSL中的vary、atrribute,当时我就懵逼了,⼀阵⼼虚,我只说我知道in out,应为我学的就只有in out,回来⾃⼰也查了查,GLSL中确实有vary、atrribute这么⽤的,但是⼀直没搞清楚他们与in、out的区别联系,今天看了这篇⽂章豁然开朗,原来是vary、attribute是⽼版本的GLSL中的关键字,现在已经统⼀使⽤in、out了,当时特么⽩⼼虚了,是⾯试官知识结构太⽼,当时我说in、out,他貌似不知道。。。废话不多说了,直接上原⽂。
OpenGL/GLSL规范在不断演进着,我们渐渐⾛进可编程管道的时代的同时,崭新的功能接⼝也让我们有点缭乱的感觉。本⽂再次从OpenGL和GLSL之间数据的传递这⼀点,记录和介绍基于OpenGL3.x的新⽅式,也会适时介绍Unform Buffer Objecct(UBO)这⼀重要特性。——
本⽂可视为⼤致⼀年半前的本博客的[]⼀⽂的延续。对这⽅⾯不熟悉的话请先浏览⼀下该⽂中介绍的基本概念。在该⽂中,我把这些传递分为attribute变量、uniform变量、varying变量和Fragment Shader输出,这四部分(主要讲述前两部分)。⽽本⽂再次按此四部分,谈谈在GL3.x(NVidia 8Series以后显卡所⽀持的OpenGL版本)中的数据传递⽅式的变化。
1. attribute变量
在前⽂中提及到GLSL中每⼀个attribute变量都有⼀个“位置”值(Location),在ShaderProgram链接(link)前,可以Bind之,链接之后,可以Get之。通过这两种⽅式都可以建⽴attribute变量与顶点属性的联系。如今引⼊第三种⽅式——直接在GLSL代码中指定这些位置值:
glsl3.x代码 (Vertex Program)
#version 330
h5页面源码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;
在上⾯的Vertex Program代码中,第⼀⾏(#version 330),表明我们现在使⽤的GLSL版本是GLSL3.3,以区别于以前的版本并允许我们使⽤基于GLSL3.3的功能。在过去,OpenGL的版本和GLSL版本是不统⼀的(前⽂中的GL2.2所对应的是GLSL1.2,⽽后来的对应关系是GL3.0-GLSL1.3,GL3.1-GLSL1.4,GL3.2-GLSL1.5),直到2010年OpenGL3.3/4.0规范的提出,khronos委员会决定让
两者版本统⼀,所以就有了现在本博客所使⽤的OpenGL3.3-GLSL3.3的对应关系(注,ShaderModel4.0的显卡可达到的最⾼版本)。
接下来的⼏⾏声明了4个attribute变量。在GL2.x中⼀个attribute变量通常是“attribute vec3 attrib_position;”这样来表⽰,在GL3.x中,废弃了attribute关键字(以及varying关键字),属性变量统⼀⽤in/out作为前置关键字,对每⼀个Shader stage来说,in表⽰该属性是作为输⼊的属性,out表⽰该属性是⽤于输出的属性。这⾥,attribute变量作为Vertex Shader的顶点输⼊属性,所以都⽤in标记。另外,这⾥使⽤了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变量
对于uniform变量的声明⽅式,跟GL2.x的⼀致,使⽤uniform关键字就可以了。
glsl代码
#version 330
uniform sampler2D basetex1;
uniform float fAlphaRestrictVal;
uniform mat4 matModel;
登录页面背景图片高清uniform mat4 matView;
uniform mat4 matProj;
每⼀个uniform变量也都有其⼀个“位置值”(Location),在OpenGL中,我们可以通过glGetUniformLocation来获得。那么我们可以不可以像attribute变量那样,在Shader代码中显式指定这个Location呢?(其好处也是跟上述差不多的,但就是如果uniform变量太多的话这样做也⿇烦,因为
得在代码中⼀个⼀个指定不重复的location。)嘛,attribute变量location的显式指定,是经由GL扩展
GL_ARB_explicit_attrib_location实现的,⽽事实上,现在也有GL_ARB_explicit_uniform_location这样⼀个GL扩展,能实现这样的功能,只不过它是OpenGL4.3标准的⼀部分,⾪属于GLSL4.3,所以即使GL3.x⽀持这个扩展,我们还是暂时不要⽤的好。
那我们就像往常⼀样,在glUseProgram启⽤了某个ShaderProgram之后,⼀个⼀个地给每个unifom变量关联数据咯(通过其location)——等等,这是在运⾏期间设置数据值吧,那如果我这个关联数据并不是每帧都变化的,甚⾄它是⼀个固定值,这样做岂不太⽆聊太浪费了?事实上我们还是可以在glUseProgram之外绑定数据的——乃⾄直接在初始化时。这得益于glProgramUniform系列函数的引⼊,它⽐起往常的glUniform要多⼀个参数⽤来接收⼀个ShaderProgram的ID。在建⽴ShaderProgram后,我们也不需要glUseProgram来预先绑定它就可以直接取得某个uniform变量的location值并⽤glProgramUniform系列函数关联数据,⽽且这个数据在其后运⾏期间的每次glUseProgram 后都不会失效。从理论上将,这族函数完全可以替代glUniform系列函数(是它们功能的⼀个超集),但是就不知道会不会有性能上的损失了(这个暂时⽬前不到说法),所以我暂时建议是只对那些⾮动态变化的uniform变量使⽤了。
再来看看uniform变量的问题。通常⼀个稍微复杂点点、更多控制参数的Shader,都会有⼤量的Unifor
m变量需要设置,所以导致了我们很多时候在glUseProgram之后要调⽤⼀长串的glUniform函数来传递该Pass的数据。有没有⽅法尽量把这些操作合并呢?另外,我们知道⼀个Shader的可⽤Uniform数据⼤⼩是有⼀个上限值的(例如我⽬前显卡的⼀个vertex shader的
GL_MAX_VERTEX_UNIFORM_COMPONENTS值是4096,意味着我在⼀个VertexShader⾥使⽤的active uniforms,⼤概就是最多4096个float/int值了,或者说最多1024个vec4、最多256个mat16),那么有没办法提⾼这个上限呢?在[]这篇⽂章中,因为担⼼uniform数量不⾜以⽀撑传⼊的众多个⾻骼矩阵,所以优先选择TBO(Texture Buffer Object)作为传⼊数据的媒介,把数据装⼊⼀个⼀维纹理的Buffer中以提供给Shader。那么除了使⽤纹理数据外,还有没有更直接的⽅式呢?
Uniform Buffer Object(UBO)
UBO,顾名思义,就是⼀个装载Uniform变量数据的Buffer Object。就概念⽽⾔,它跟VBO([] )之类Buffer Object差不多,反正就是显存中⼀块⽤于储存特定数据的区域了。在OpenGL端,它的创建、更新、销毁的⽅式都与其他Buffer Object没什么区别,我们只不过把⼀个或多个uniform数据交给它,以替代glUniform的⽅式传递数据⽽已。这⾥必须明确⼀点,这些数据是给到这个UBO,存储于这个UBO上,⽽不再是交给ShaderProgram,所以它们不会占⽤这个ShaderProgram⾃⾝的uniform存储空间,所以UBO是⼀种全新的传递数据的⽅式,从路径到⽬的地,都跟传统uniform变量的⽅式不⼀样。⾃然,
对于这样的数据,在Shader中不能再使⽤上⾯代码中的⽅式来指涉了。随着UBO 的引⼊,GLSL也引⼊了uniform block这种指涉⼯具。
glsl代码
#version 330
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中的实质数据联系起来了:
OpenGL代码
GLint nMatVPBlockIndex = glGetUniformBlockIndex(nProgram
Handler, "matVP");
//GLint nMatVPBlockIndex = glGetProgramResourceIndex(nPr
ogramHandler, GL_UNIFORM_BLOCK, "matVP");
if (GL_INVALID_INDEX != nMatVPBlockIndex)
efi shell是什么意思{
GLint nBlockDataSize = 0;
glGetActiveUniformBlockiv(nProgramHandler, nMatVPBlock
Index, GL_UNIFORM_BLOCK_DATA_SIZE, &nBlockDataSiz
e);
glGenBuffers(1, &m_nUBO);
glBindBuffer(GL_UNIFORM_BUFFER, m_nUBO);
glBufferData(GL_UNIFORM_BUFFER, nBlockDataSize, NU
LL, GL_DYNAMIC_DRAW);
glBindBufferRange(GL_UNIFORM_BUFFER, 0, m_nUBO, 0
, nBlockDataSize);
glUniformBlockBinding(nProgramHandler, nMatVPBlockInd
ex, 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⽽是作为⾸参罢)。
除了UBO,前⾯某篇博⽂[]中提到的Transform Feedback Buffer也是使⽤binding-point(参见⽂中代码段)的“好⼿”。因为Shader同样需要快速出需要feedback的那个Buffer的所在地,尤其是通过GL_SEPARATE_ATTRIBS的⽅式为每⼀个输出数据独⽴指定buffer时,就需要⽤到多个transform-feedback binding-point来储存各个buffer的信息了。其限制个数其实就是
GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS了(本显卡此数字为4)。
这⾥引出⼀个问题:我们能不能像TransformFeedback那样,为⼀个UBO对象⾮配数个binding-point呢?可以的。这样做的⽬的也很明确——单个UBO多个uniform-block。准确地说,是每个uniform block对应该UBO存储区域中不同的分区域(sub-region)——glBindBufferRange,就是你了!
OpenGL代码
GLint nMatVPBlockIndex = glGetUniformBlockIndex(nProgram
Handler, "matVP");
GLint nCloudScaleIndex = glGetUniformBlockIndex(nProgram
Handler, "Scale");
if (GL_INVALID_INDEX != nMatVPBlockIndex && GL_INVALI
D_INDEX != nCloudScaleIndex)
{
安卓10怎么rootint nUniformBufferAlignSize = 0;
glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNM
ENT, &nUniformBufferAlignSize);
position和location的区别
GLint nBlockDataSize1 = 0, nBlockDataSize2 = 0;
glGetActiveUniformBlockiv(nProgramHandler, nMatVPBlock
Index, GL_UNIFORM_BLOCK_DATA_SIZE, &nBlockDataSiz
e1);
glGetActiveUniformBlockiv(nProgramHandler, nCloudScaleI
ndex, GL_UNIFORM_BLOCK_DATA_SIZE, &nBlockDataSize
2);
glGenBuffers(1, &m_nUBO);
glBindBuffer(GL_UNIFORM_BUFFER, m_nUBO);
glBufferData(GL_UNIFORM_BUFFER, nUniformBufferAlign
Size + nBlockDataSize2, NULL, GL_DYNAMIC_DRAW);
glBindBufferRange(GL_UNIFORM_BUFFER, 0, m_nUBO, 0
, nBlockDataSize1);
glUniformBlockBinding(nProgramHandler, nMatVPBlockInd
ex, 0);
glBindBufferRange(GL_UNIFORM_BUFFER, 1, m_nUBO, n
一个软件的开发需要多少钱UniformBufferAlignSize, nBlockDataSize2);
glUniformBlockBinding(nProgramHandler, nCloudScaleInde
x, 1);
}
上⾯代码段中,我们把两个uniform-block关联到同⼀个UBO的两个区域:[0 ~ nBlockDataSize1]、[nUniformBufferAlignSize
~
nUniformBufferAlignSize+nBlockDataSize2]。为什么第⼆个block不是映射到[nBlockDataSize1 ~ nBlockDataSize1+nBlockDataSize2]呢?这⾥有个⽐较重要的概念:数据对齐。对于uniform-block,可以通过GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT出这个对齐值(本显卡上此数值是256字节,所以每个uniform block都是256字节对齐,相邻uniform block的间隔必须满⾜256字节的整数倍,否则会发现数据会对不上)。记住了,block与block的数据不是紧紧pack在⼀起的。很容易想象,跟CPU上存储结构体⼀样,这是为了数据存取效率考虑,⾄于为什么是这个值,就要更深⼊研究了。
⿇烦可不⽌这⼀个——单个uniform-block⾥⾯的数据,也有字节对齐的机制——这给uniorm变量的数据更新带来更⼤的⿇烦。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论