Advanced Data
OpenGL
中的缓冲只是一个管理特定内存块的对象,没有其它更多的功能了。在我们将它绑定到一个缓冲目标(Buffer Target)
时,我们才赋予了其意义。除了调用glBufferData
函数一次性填充缓冲对象所管理的内存,也可以使用glBufferSubData
填充缓冲的特定区域,但是之前要先调用glBufferData
分配内存。
之前,我们通过glVertexAttribPointer
将每一个顶点的位置、法线和/或纹理坐标紧密放置在一起。现在将每一种属性类型的向量数据打包(Batch)为一个大的区块,而不是对它们进行交错储存。与交错布局123123123123
不同,我们将采用分批(Batched)
的方式111122223333
。
float positions[] = { ... };
float normals[] = { ... };
float tex[] = { ... };
// 填充缓冲
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(positions), &positions);
glBufferSubData(GL_ARRAY_BUFFER, sizeof(positions), sizeof(normals), &normals);
glBufferSubData(GL_ARRAY_BUFFER, sizeof(positions) + sizeof(normals), sizeof(tex), &tex);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), 0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)(sizeof(positions)));
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)(sizeof(positions) + sizeof(normals)));
Advanced GLSL
GLSL’s built-in variables
Vertex shader variables
gl_PointSize
我们能够选用的其中一个图元是GL_POINTS
,如果使用它的话,每一个顶点都是一个图元,都会被渲染为一个点。我们可以通过OpenGL
的glPointSize
变量来设置渲染出来的点的宽高(像素),但我们也可以在顶点着色器中修改这个值。前提glEnable(GL_PROGRAM_POINT_SIZE);
// 将点的大小设置为裁剪空间位置的z值,也就是顶点距观察者的距离。点的大小会随着观察者距顶点距离变远而增大。
void main()
{
gl_Position = projection * view * model * vec4(aPos, 1.0);
gl_PointSize = gl_Position.z;
}
gl_VertexID
整型变量gl_VertexID
储存了正在绘制顶点的当前ID。当(使用glDrawElements
)进行索引渲染的时候,这个变量会存储正在绘制顶点的当前索引。当(使用glDrawArrays
)不使用索引进行绘制的时候,这个变量会储存从渲染调用开始的已处理顶点数量。
Fragment shader variables
gl_FragCoord
gl_FragCoord
的x和y分量是片段的窗口空间(Window-space)
坐标,z分量对应片段的深度值,其原点为窗口的左下角。
gl_FrontFacing
如果我们不(启用GL_FACE_CULL
来)使用面剔除,那么gl_FrontFacing
将会告诉我们当前片段是属于正向面的一部分还是背向面的一部分。举例来说,我们能够对正向面计算出不同的颜色。
gl_FragDepth
GLSL
提供给我们一个叫做gl_FragDepth(0.0到1.0)
的输出变量,我们可以使用它来在着色器内设置片段的深度值。
只要我们在片段着色器中对gl_FragDepth
进行写入,OpenGL
就会(像深度测试小节中讨论的那样)禁用所有的提前深度测试(Early Depth Testing)
。它被禁用的原因是,OpenGL
无法在片段着色器运行之前得知片段将拥有的深度值,因为片段着色器可能会完全修改这个深度值。
然而,从OpenGL 4.2
起,我们仍可以对两者进行一定的调和,在片段着色器的顶部使用深度条件(Depth Condition)
重新声明gl_FragDepth
变量:
// condition取值:
// any 默认值。提前深度测试是禁用的,你会损失很多性能
// greater 你只能让深度值比gl_FragCoord.z更大
// less 你只能让深度值比gl_FragCoord.z更小
// unchanged 如果你要写入gl_FragDepth,你将只能写入gl_FragCoord.z的值
layout (depth_<condition>) out float gl_FragDepth;
Interface blocks
GLSL
为我们提供了一个叫做接口块(Interface Block)
的东西,来方便我们组合着色器的变量。接口块的声明和struct
的声明有点相像,不同的是,现在根据它是一个输入还是输出块(Block),使用in
或out
关键字来定义的。
out VS_OUT
{
vec2 TexCoords;
} vs_out;
in VS_OUT
{
vec2 TexCoords;
} fs_in;
使用多个着色器时,有些Uniform
变量是不会变的。OpenGL
为我们提供了一个叫做Uniform缓冲对象(Uniform Buffer Object)
的工具,它允许我们定义一系列在多个着色器中相同的全局Uniform
变量。当使用Uniform缓冲对象
的时候,我们只需要设置相关的Uniform
一次。
Uniform block layout std140
每个变量都有一个基准对齐量(Base Alignment)
,它等于一个变量在Uniform
块中所占据的空间(包括填充量(Padding)
),这个基准对齐量是使用std140
布局的规则计算出来的。接下来,对每个变量,我们再计算它的对齐偏移量(Aligned Offset)
,它是一个变量从块起始位置的字节偏移量。一个变量的对齐字节偏移量必须等于基准对齐量的倍数。
每4个字节将会用一个N来表示:
layout (std140) uniform ExampleBlock
{
// 基准对齐量 // 对齐偏移量
float value; // 4 // 0
vec3 vector; // 16 // 16 (必须是16的倍数,所以 4->16)
mat4 matrix; // 16 // 32 (列 0)
// 16 // 48 (列 1)
// 16 // 64 (列 2)
// 16 // 80 (列 3)
float values[3]; // 16 // 96 (values[0])
// 16 // 112 (values[1])
// 16 // 128 (values[2])
bool boolean; // 4 // 144
int integer; // 4 // 148
};
Using uniform buffers
nsigned int uboExampleBlock;
glGenBuffers(1, &uboExampleBlock);
glBindBuffer(GL_UNIFORM_BUFFER, uboExampleBlock);
glBufferData(GL_UNIFORM_BUFFER, 152, NULL, GL_STATIC_DRAW); // 分配152字节的内存
glBindBuffer(GL_UNIFORM_BUFFER, 0);
在OpenGL
上下文中,定义了一些绑定点(Binding Point)
,我们可以将一个Uniform缓冲
链接至它。在创建Uniform缓冲
之后,我们将它绑定到其中一个绑定点上,并将着色器中的Uniform
块绑定到相同的绑定点,把它们连接到一起。下面的这个图示展示了这个:
// 将图示中的Lights Uniform块链接到绑定点2:
unsigned int lights_index = glGetUniformBlockIndex(shaderA.ID, "Lights");
glUniformBlockBinding(shaderA.ID, lights_index, 2);
// 绑定Uniform缓冲对象到相同的绑定点上
glBindBufferBase(GL_UNIFORM_BUFFER, 2, uboExampleBlock);
// 或
glBindBufferRange(GL_UNIFORM_BUFFER, 2, uboExampleBlock, 0, 152);
// 向Uniform缓冲中添加数据
glBindBuffer(GL_UNIFORM_BUFFER, uboExampleBlock);
int b = true; // GLSL中的bool是4字节的,所以我们将它存为一个integer
glBufferSubData(GL_UNIFORM_BUFFER, 144, 4, &b);
glBindBuffer(GL_UNIFORM_BUFFER, 0);