今日来学习下GLSL中的一些有意思的特性
1、GLSL的内建变量
咱们之前现已接触过两个内建变量了:
- gl_Position,极点着色器的输出变量
- gl_FragCoord,表明当时片元着色器处理的候选片元窗口相对坐标信息,是一个 vec4 类型的变量 (x, y, z, 1/w), 其间 x, y 是当时片元的窗口坐标,z是深度值,w则是齐次坐标
glsl中还有一些其它的内建变量,咱们一起来了解下。
1.1、极点着色器变量
咱们能够选用的其间一个图元是GL_POINTS
,假如运用它的话,每一个极点都是一个图元,都会被烘托为一个点。咱们能够经过OpenGL的glPointSize
函数来设置烘托出来的点的巨细,但咱们也能够在极点着色器中修正这个值
GLSL界说了一个叫做gl_PointSize
输出变量,它是一个float变量,你能够运用它来设置点的宽高(像素)。在极点着色器中修正点的巨细的话,你就能对每个极点设置不同的值了。
在极点着色器中修正点巨细的功能默许是禁用的,假如你需求启用它的话,你需求启用OpenGL的GL_PROGRAM_POINT_SIZE
:
glEnable(GL_PROGRAM_POINT_SIZE);
一个简略的例子便是将点的巨细设置为裁剪空间方位的z值,也便是极点距观察者的间隔。点的巨细会跟着观察者距极点间隔变远而增大。
void main() {
gl_Position = projection * view * model * vec4(aPos, 1.0);
gl_PointSize = gl_Position.z;
}
1.2、片段着色器变量
gl_FragCoord
,它是片段着色器的一个输入变量,gl_FragCoord
的z重量等于对应片段的深度值
gl_FragCoord
的x和y重量是片段的窗口空间(Window-space)坐标,其原点为窗口的左下角。咱们现已运用glViewport设定了一个800×600的窗口了,所以片段窗口空间坐标的x重量将在0到800之间,y重量在0到600之间。
经过利用片段着色器,咱们能够依据片段的窗口坐标,核算出不同的色彩。gl_FragCoord
的一个常见用处是用于比照不同片段核算的视觉输出作用,这在技术演示中能够常常看到。比方说,咱们能够将屏幕分成两部分,在窗口的左边烘托一种输出,在窗口的右侧烘托另一种输出。下面这个例子片段着色器会依据窗口坐标输出不同的色彩:
void main() {
if(gl_FragCoord.x < 400)
FragColor = vec4(1.0, 0.0, 0.0, 1.0);
else
FragColor = vec4(0.0, 1.0, 0.0, 1.0);
}
最终,它的作用类似于这样:
gl_FrontFacing
是另一个输入变量,它是一个bool值,告知咱们当时片段是属于正向面的一部分仍是背向面的一部分,假如当时片段是正向面的一部分那么便是true
,否则便是false
留意,假如你开启了面除掉,你就看不到箱子内部的面了,所以现在再运用gl_FrontFacing
就没有意义了。
输入变量gl_FragCoord
能让咱们读取当时片段的窗口空间坐标,并获取它的深度值,但是它是一个只读(Read-only)变量。咱们不能修正片段的窗口空间坐标,但实际上修正片段的深度值仍是或许的。GLSL供给给咱们一个叫做gl_FragDepth
的输出变量,咱们能够运用它来在着色器内设置片段的深度值。
要想设置深度值,咱们直接写入一个0.0到1.0之间的float值到输出变量就能够了:
gl_FragDepth = 0.0; // 这个片段现在的深度值为 0.0
假如着色器没有写入值到gl_FragDepth
,它会自动取用gl_FragCoord.z
的值。
但是,由咱们自己设置深度值有一个很大的缺点,只需咱们在片段着色器中对gl_FragDepth
进行写入,OpenGL就会禁用一切的提前深度测试(Early Depth Testing)。它被禁用的原因是,OpenGL无法在片段着色器运行之前得知片段将拥有的深度值,由于片段着色器或许会完全修正这个深度值。
2、Uniform缓冲目标
到本文为止,咱们现已写了不少示例代码了,几乎每个着色器中都会有view
和projection
的uniform变量,假如示例中存在两个着色器,那咱们就要设置两遍,有没有一个办法能让咱们设置一次就行了呢?有,便是Uniform缓冲目标
OpenGL为咱们供给了一个叫做Uniform缓冲目标(Uniform Buffer Object)的东西,它答应咱们界说一系列在多个着色器中相同的全局Uniform变量。当运用Uniform缓冲目标的时分,咱们只需求设置相关的uniform一次。当然,咱们仍需求手动设置每个着色器中不同的uniform。
#version 300 es
layout(location = 0) in vec3 aPos;
layout(std140) uniform Matrices {
mat4 projection;
mat4 view;
};
uniform mat4 model;
void main() {
gl_Position = projection * view * model * vec4(aPos, 1.0);
}
咱们声明晰一个叫做Matrices
的Uniform块,它储存了两个4×4矩阵。Uniform块中的变量能够直接访问,不需求加块名作为前缀。接下来,咱们在OpenGL代码中将这些矩阵值存入缓冲中,每个声明晰这个Uniform块的着色器都能够访问这些矩阵。
运用方法:
//获取uniform缓冲的索引
auto uniformRedIndex = glGetUniformBlockIndex(redShader.ID, "Matrices");
auto uniformBlueIndex = glGetUniformBlockIndex(blueShader.ID, "Matrices");
//绑定着色器的uniform缓冲索引到绑定点1上(当然也能够绑定到0或2之类的)
glUniformBlockBinding(redShader.ID, uniformRedIndex, 1);
glUniformBlockBinding(blueShader.ID, uniformBlueIndex, 1);
//生成索引缓冲
glGenBuffers(1, &uboMatrices);
glBindBuffer(GL_UNIFORM_BUFFER, uboMatrices);
//给uniform缓冲分配内存,但不填充数据
glBufferData(GL_UNIFORM_BUFFER, 2 * sizeof(glm::mat4), NULL, GL_STATIC_DRAW);
glBindBuffer(GL_UNIFORM_BUFFER, 0);
//将uboMatrices uniform缓冲也绑定到绑定点2上
glBindBufferRange(GL_UNIFORM_BUFFER, 1, uboMatrices, 0, 2 * sizeof(glm::mat4));
代码如上,或许有些同学比较模糊,看一个图:
在OpenGL上下文中,界说了一些绑定点(Binding Point),咱们能够将一个Uniform缓冲链接至它。在创建Uniform缓冲之后,咱们将它绑定到其间一个绑定点上,并将着色器中的Uniform块绑定到相同的绑定点,把它们连接到一起。
将Uniform块绑定到一个特定的绑定点中,咱们需求调用glUniformBlockBinding函数,它的第一个参数是一个程序目标,之后是一个Uniform块索引和链接到的绑定点。Uniform块索引(Uniform Block Index)是着色器中已界说Uniform块的方位值索引。这能够经过调用glGetUniformBlockIndex来获取
glUniformBlockBinding(redShader.ID, uniformRedIndex, 1);
咱们还需求绑定Uniform缓冲目标到相同的绑定点上,这能够运用glBindBufferBase或glBindBufferRange来完成。
glBindBufferBase(GL_UNIFORM_BUFFER, 2, uboExampleBlock);
// 或
glBindBufferRange(GL_UNIFORM_BUFFER, 2, uboExampleBlock, 0, 152);
现在,一切的东西都配置完毕了,咱们能够开始向Uniform缓冲中增加数据了。只需咱们需求,就能够运用glBufferSubData函数,用一个字节数组增加一切的数据,或者更新缓冲的一部分
glm::mat4 projection = glm::perspective(glm::radians(45.0f), rat, 0.1f, 100.0f);
glm::mat4 view = camera.getViewMatrix();
glBindBuffer(GL_UNIFORM_BUFFER, uboMatrices);
glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(glm::mat4), glm::value_ptr(projection));
glBufferSubData(GL_UNIFORM_BUFFER, sizeof(glm::mat4), sizeof(glm::mat4), glm::value_ptr(view));
glBindBuffer(GL_UNIFORM_BUFFER, 0);
2.1、Uniform块布局
上文中说到的 layout (std140)
是啥意思呢?
Uniform块的内容是储存在一个缓冲目标中的,它实际上只是一块预留内存。由于这块内存并不会保存它具体保存的是什么类型的数据,咱们还需求告知OpenGL内存的哪一部分对应着着色器中的哪一个uniform变量。
简略来说,由于opengl并不知道Uniform块会存储啥东西,假如我要更新最终一个数据,那调用 glBufferSubData
时,内存偏移应该是多少呢?
layout (std140)
给咱们指定了一种简略的、易算的一种内存布局。方便咱们写代码,能够这么了解
每个变量都有一个基准对齐量(Base Alignment),它等于一个变量在Uniform块中所占有的空间(包括填充量(Padding)),这个基准对齐量是运用std140布局的规矩核算出来的。接下来,对每个变量,咱们再核算它的对齐偏移量(Aligned Offset),它是一个变量从块开始方位的字节偏移量。一个变量的对齐字节偏移量有必要等于基准对齐量的倍数。
GLSL中的每个变量,比方说int、float和bool,都被界说为4字节量。每4个字节将会用一个N
来表明。
类型 | 布局规矩 |
---|---|
标量,比方int和bool | 每个标量的基准对齐量为N。 |
向量 | 2N或者4N。这意味着vec3的基准对齐量为4N。 |
标量或向量的数组 | 每个元素的基准对齐量与vec4的相同。 |
矩阵 | 储存为列向量的数组,每个向量的基准对齐量与vec4的相同。 |
结构体 | 等于一切元素依据规矩核算后的巨细,但会填充到vec4巨细的倍数。 |
举个粟子:
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
};
所以,假如我要更新如上的uniform块的bool值,应该怎么写代码呢?
glBindBuffer(GL_UNIFORM_BUFFER, uboExampleBlock);
int b = true; // GLSL中的bool是4字节的,所以咱们将它存为一个integer
glBufferSubData(GL_UNIFORM_BUFFER, 144, 4, &b);
glBindBuffer(GL_UNIFORM_BUFFER, 0);