高级数据&高级GLSL
GLSL内建变量
顶点着色器
- gl_Position
这个在顶点着色器里面已经见的太多了,设置顶点的坐标这便是它全部的功能。
- gl_PointSize
这个变量可以在顶点着色器中设置每个顶点像素的宽高,但是一般是被禁用的,需要用glEnable(GL_PROGRAM_POINT_SIZE)
进行开启。
- gl_VertexID
这个变量记录了当前绘制顶点的索引,如果不是使用glDrawElements
的话就会是从绘制开始已经绘制了的顶点的数。
片段着色器
-
gl_FragCoord
gl_FragCoord是片段着色器中记录每个像素的x、y以及深度信息的变量。 -
gl_FrontFacing
这个变量是bool类型,主要是判断当前像素是否是正面,如果是正面便设为true
反面则为false
。 -
gl_FragDepth
这个变量用来控制片段的深度值,虽然gl_FragDepth.z
是深度值,但它只是可读的变量,所以要改变深度值要用gl_FragDepth
。
但是如果使用了这个变量着色器将不会进行提前深度测试,因为提前深度测试在深度着色器之前运行,如果使用了这个变量,那么着色器将无法得知最终的深度值。
接口块
接口块可以将我们要传递的数据进行打包,例如我们可以声明一个这样的接口块:
1 | out VS_OUT{ |
可以看到接口块的声明和结构体非常相似,其中out
和之前功能一样,也是表示输出和输入,然后VS_OUT
则是接口块名称,最后vs_out
则是实例名称。然后在下一个着色器我们可以这样声明来接收:
1 | in VS_OUT{ |
其中接口名必须要求是一样的,但是实例名可以随便,因此我们可以取一个和着色器相对应的名字,例如在片段着色器则取名为fg_in
。
接口块可以方便我们对变量进行管理,同时还可以快速的声明数组,例如这样我们就声明了一个接口块数组:
1 | out VS_OUT{ |
Uniform缓冲对象
Uniform缓冲对象也叫(Uniform Buffer Object,UBO),它可以让我们在多个着色器中共享数据。例如如果我这样声明:
1 | layout (std140) uniform ExampleBlock |
这样凡是声明了同样的Uniform块的着色器都将得到这些数据。
Uniform块布局
由于uniform块最终要存储到UBO缓冲对象中,而缓冲对象也只是CPU为你预留的一块内存区域,就像VBO一样,在glBufferData
之后还是需要glVertexAttribPointer()
来指明每个部分对应着着色器中的location=x
,同样UBO也需要你指明,但是这里不再是像VBO那样需要自己手动设置,UBO会通过提前设置好的布局来指定数据,也就是上面着色器中的std140
。
std140
只是众多布局的一种,但是也是最好设定数据的,我们只需要按照它的这种布局规则将数据填入相应的位置即可。首先每种数据类型都会有基准对其量和对齐偏移量,基准对其量指的是每种数据的大小,每四个字节对应一个N,这里是几个基本数据的大小说明。
而对齐偏移量其实就是数据在内存中开始位置的偏移量,但是这里需要说明的是对齐偏移量必须是基准对其量的整数倍,也就是说,例如前面有个float
变量只用了四个字节,但是紧随其后的vec3
变量不能直接从四字节后开始,而是要从第十六个字节开始,因为vec3
的基准对其量是16,所以它对其偏移量只能是16、32、64…等等。因此这样就导致了std140
布局并不是最节省空间的布局,相反它中间可能会有很多这样的“空隙”。
使用Uniform缓冲对象
我们以这样一个Uniform块为例说一下Uniform缓冲对象要如何使用:
1 | layout(std140) uniform Matrices{ |
我们在着色器里面声明了这样一个Uniform块,对应的我们需要创建一个Uniform缓冲对象
1 | unsigned int UBO; |
之后和VBO一样,也需要绑定然后再填充数据
1 | glBindBuffer(GL_UNIFORM_BUFFER,UBO); |
这里可以注意到,我们首先并没有直接填写数据,这是因为UBO的数据要按照stb140
的规则来(前面讨论的),所以我们后面便使用glBufferSubData
来填写数据
1 | glBindBuffer(GL_UNIFORM_BUFFER,UBO); |
这里还需要说明的是,glBufferSubData
的参数,第一个便是绑定到的缓冲,第二个数据的起始位置,第三个是数据的长度,第四个便是数据的地址。这样我们就算是把数据成功绑定到UBO缓冲中了,但是这样还没结束,我们要知道Uniform块我们可以申请很多个,UBO我们也可以申请很多个,那么它们之间改如何对应呢?所以接下来就要开始将Uniform块和UBO缓冲绑定起来。
绑定点
我们是通过绑定点的方式来将Uniform块和UBO缓冲结合在一起的,也即是绑定到同一个绑定点的Uniform块将拥有相同的UBO缓冲,也即拥有相同的数据
首先先使用glUniformBlockBinding
函数将Uniform块绑定到绑定点上,这个函数的参数是第一个是着色器程序Id,第二个是Uniform块索引,第三个是绑定点序列,而Uniform块则是这个Uniform块在这个着色器程序里面的唯一标识,需要通过glGetUniformBlockIndex(Shader_ID,Uniform_Name)
函数获取。
UBO这里相对简单,只需要使用glBindBufferBase
或者glBindBufferRange
来绑定,后者比前者多了范围参数,可以指定绑定UBO缓冲中的哪一部分。
最后我们的Uniform块和UBO便设置完成并绑定到一起了,我们可以使用Uniform缓冲来储存一些常用的数据,例如Projection矩阵或者是View矩阵,这样我们只需要进行一次改变便可以将所有绑定相同的Uniform块的数据同步更新。最后欣赏一下微软图标:-),其中的四个正方体只有Model矩阵不同,所以就用Uniform块储存了Projection矩阵和View矩阵。