一、内建变量
查询所有的内建变量的话,请查看OpenGL的wiki。
顶点着色器
Input
变量名称 |
变量类型 |
变量语义 |
作用 |
gl_VertexID |
int |
索引 |
当使用glDrawElements,存储的是正在绘制顶点的当前索引。
当使用glDrawArrays,储存的是从渲染调用开始的已处理顶点数量。
|
gl_InstanceID |
int |
索引 |
实例化渲染中,当前实例的索引,下标从0开始。 |
Output
变量名称 |
变量类型 |
变量语义 |
作用 |
gl_Position |
vec4 |
位置 |
当前顶点齐次裁剪空间的输出坐标 |
gl_PointSize |
float |
点的宽高(像素) |
该功能默认关闭,需调用 glEnable(GL_PROGRAM_POINT_SIZE); |
片元着色器
Input
变量名称 |
变量类型 |
变量语义 |
作用 |
gl_FragCoord
|
vec4 |
屏幕坐标 |
(x,y)为当前片元的屏幕坐标,
z分量等于对应片段的深度值(只读)
|
gl_FrontFacing |
bool |
片元朝向 |
片段是正面则true,否则false。 |
Output
变量名称 |
变量类型 |
变量语义 |
作用 |
gl_FragColor |
vec4 |
颜色 |
表示当前片元的颜色 |
gl_FragDepth |
float |
深度值 |
可修改片段的深度值 |
gl_FragDepth 详解
可设置为一个[0.0, 1.0] 之间的值。
如无修改,gl_FragDepth自动取gl_FragCoord.z的值。
若进行修改,会影响到提前深度测试,造成一定性能影响。可通过以下功能进行调和。
使用深度条件(Depth Condition)重新声明gl_FragDepth变量:
layout (depth_<condition>) out float gl_FragDepth;
条件 |
描述 |
any |
默认值。提前深度测试是禁用的,你会损失很多性能 |
greater |
你只能让深度值比gl_FragCoord.z更大 |
less |
你只能让深度值比gl_FragCoord.z更小 |
unchanged |
如果你要写入gl_FragDepth,你将只能写入gl_FragCoord.z的值 |
注意:使用 layout (depth_greater) 修饰 gl_FragDepth 时,深度缓冲区的比较函数应该设置为 GL_GREATER,否则就会导致深度测试不正确。如果深度缓冲区的比较函数设置为 GL_LESS 或者其他不匹配的比较函数,就会导致一些本应该被遮挡的物体显示在前面,从而影响渲染结果。
二、接口块
接口块是一种结构体类型,用于在着色器程序中定义输入输出变量的集合,并将这些变量打包成一个整体进行传递。
使用接口块可以方便地传递数据,避免了在着色器程序中单独定义大量的输入输出变量。同时,接口块也提高了程序的可读性和可维护性,方便进行代码管理和调试。
定义语法如下:
in/out interface BlockName {
DataType varName1;
DataType varName2;
...
};
三、Uniform缓冲对象
引入
在多个着色器中,我们会发现有些uniform变量是完全相同的,比如观察矩阵和透视矩阵。因此在设置和修改时,需要一个一个手动设置,十分麻烦。并且相同的uniform变量,一定程度上造成了资源的浪费。
所以引入了Uniform缓冲对象(Uniform Buffer Object)的工具,它允许我们定义一系列在多个着色器中相同的全局Uniform变量。当使用Uniform缓冲对象的时候,我们只需要设置相关的uniform一次。
好处如下:
1. 全局的Uniform进行设置和修改十分方便。
2. 突破了着色器中只能使用一定数量uniform的限制(可以通
GL_MAX_VERTEX_UNIFORM_COMPONENTS来查询上限)。
Uniform块布局
Uniform块的内容是储存在一个Uniform缓冲对象中的,它实际上只是一块预留内存。
由于这块内存并没有保存它具体存储的是什么类型的数据,因此需要通过布局限定符来告诉OpenGL如何解释这块内存。
布局方式 |
基本概念 |
变量的字节大小 |
适用场景 |
对比信息 |
std140 |
变量按照一定的规则对齐后被放入连续的内存空间中,对齐规则为变量占用空间的大小与4字节中的较大值,也可强制指定对齐方式。 |
4字节对齐 |
用于定义Uniform缓冲区中的变量布局,兼容性好。 |
std140布局与std430布局相比,在数组中间插入非vec4的变量时需要填充额外的空间。 |
std430 |
变量按照一定的规则对齐后被放入连续的内存空间中,对齐规则为变量占用空间的大小,也可强制指定对齐方式。 |
任意对齐 |
用于定义Uniform缓冲区中的变量布局,兼容性好。 |
std430布局与std140布局相比,在数组中间插入非vec4的变量不需要填充额外的空间。 |
packed |
变量不需要对齐,可以紧凑排列在内存中,但可能导致访问变量时的性能问题。 |
任意大小 |
用于定义Uniform缓冲区中的变量布局,可以最大限度地压缩内存使用。 |
packed布局不支持结构体成员中嵌套数组或结构体成员。 |
shared |
变量按照一定的规则对齐后被放入共享内存中,对齐规则与std140布局一致。 |
4字节对齐 |
用于定义着色器中共享内存的布局。 |
仅适用于Compute Shader中。 |
使用Uniform缓冲
创建Uniform缓冲对象,分配足够的内存,并添加相应数据:
// 创建Uniform缓冲对象
unsigned 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);
// 填充数据
glBindBuffer(GL_UNIFORM_BUFFER, uboExampleBlock);
glBufferSubData(GL_ARRAY_BUFFER, 24, sizeof(data), &data);
glBindBuffer(GL_UNIFORM_BUFFER, 0);
为了uniform块知道使用哪个uniform缓冲,因此定义了一些绑定点(Binding Point),以确定他们之间的连接关系。
Uniform块与绑定点的连接
方式一:
可通过调用glGetUniformBlockIndex获得着色器中Uniform块的位置值索引,接受一个程序对象和Uniform块的名称。然后再调用glUniformBlockBinding 将相应着色器中的Uniform块绑定到指定绑定点上。例子如下:
unsigned int lights_index = glGetUniformBlockIndex(shaderA.ID, "Lights");
glUniformBlockBinding(shaderA.ID, lights_index, 2);
方式二:
从OpenGL 4.2版本起,你也可以添加一个布局标识符,显式地将Uniform块的绑定点储存在着色器中,这样就不用再调用glGetUniformBlockIndex和glUniformBlockBinding了。
layout(std140, binding = 2) uniformLights { ... };
Uniform缓冲与绑定点的连接
glBindBufferBase(GL_UNIFORM_BUFFER, 2, uboExampleBlock);
// 或
glBindBufferRange(GL_UNIFORM_BUFFER, 2, uboExampleBlock, 0, 152);
glBindbufferBase需要一个目标,一个绑定点索引和一个Uniform缓冲对象作为它的参数。这个函数将uboExampleBlock链接到绑定点2上,自此,绑定点的两端都链接上了。
glBindBufferRange函数还需要一个附加的偏移量和大小参数,这样子你可以绑定Uniform缓冲的特定一部分到绑定点中。通过使用glBindBufferRange函数,你可以让多个不同的Uniform块绑定到同一个Uniform缓冲对象上。