官方教程位置
GLSL的内建变量:
-
我们已经学会使用顶点属性、uniform和采样器来完成这一任务了。然而,除此之外,GLSL还定义了另外几个以gl_为前缀的变量,它们能提供给我们更多的方式来读取/写入数据。我们已经在前面教程中接触过其中的两个了:
顶点着色器的输出向量gl_Position
,和
片段着色器的gl_FragCoord
。
顶点着色器变量
- List item我们已经见过gl_Position了,它是顶点着色器的裁剪空间输出位置向量。如果你想在屏幕上显示任何东西,在顶点着色器中设置gl_Position是必须的步骤。这已经是它的全部功能了。
-
gl_PointSize:
我们能够选用的其中一个图元是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;
}
-
gl_VertexID
(目前没有什么具体用途)
gl_Position和gl_PointSize都是输出变量,因为它们的值是作为顶点着色器的输出被读取的。我们可以对它们进行写入,来改变结果。顶点着色器还为我们提供了一个有趣的输入变量,我们只能对它进行读取,它叫做gl_VertexID。整型变量gl_VertexID储存了正在绘制顶点的当前ID。当(使用glDrawElements)进行索引渲染的时候,这个变量会存储正在绘制顶点的当前索引。当(使用glDrawArrays)不使用索引进行绘制的时候,这个变量会储存从渲染调用开始的已处理顶点数量。
虽然现在它
没有什么具体的用途
,但知道我们能够
访问
这个信息总是好的。
片段着色器变量
-
gl_FragCoord
:
我们已经见过gl_FragCoord很多次了,因为gl_FragCoord的z分量等于对应片段的
深度值
。然而,我们也能使用它的x和y分量来实现一些有趣的效果。
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);
}
自己代码效果:x小于400才进行光的反射渲染,注意X值随着角度发生改变
-
gl_FrontFacing
:段着色器另外一个很有意思的输入变量是gl_FrontFacing。在
面剔除教程
中,我们提到
OpenGL能够根据顶点的环绕顺序来决定一个面是正向还是背向面
。如果我们
不
(启用GL_FACE_CULL来)使用面剔除,那么gl_FrontFacing将会告诉我们当前片段是属于正向面的一部分还是背向面的一部分。举例来说,我们能够对正向面计算出不同的颜色。gl_FrontFacing变量是一个bool,如果当前片段是
正向面的一部分那么就是true,否则就是false
。比如说,我们可以这样子创建一个立方体,在内部和外部使用不同的纹理:
#version 330 core
out vec4 FragColor;
in vec2 TexCoords;
uniform sampler2D frontTexture;
uniform sampler2D backTexture;
void main()
{
//注意这里使用了 gl_FrontFacing来进行正反面判断
if(gl_FrontFacing)
FragColor = texture(frontTexture, TexCoords);
else
FragColor = texture(backTexture, TexCoords);
}
-
gl_FragDepth
:输入变量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无法在片段着色器运行之前得知片段将拥有的深度值,因为片段着色器可能会完全修改这个深度值。
在写入gl_FragDepth时,你就需要考虑到它所带来的性能影响。然而,从OpenGL 4.2起,我们仍可以对两者进行一定的调和,在片段着色器的顶部使用深度条件(Depth Condition)重新声明gl_FragDepth变量:
layout (depth_<condition>) out float gl_FragDepth;
通过将深度条件设置为greater或者less,OpenGL就能假设你只会写入比当前片段深度值更大或者更小的值了。这样子的话,当深度值比片段的深度值要小的时候,OpenGL仍是能够进行提前深度测试的。
下面这个例子中,我们对片段的深度值进行了递增,但仍然也保留了一些提前深度测试:
#version 420 core // 注意GLSL的版本!
out vec4 FragColor;
layout (depth_greater) out float gl_FragDepth;
void main()
{
FragColor = vec4(1.0);
gl_FragDepth = gl_FragCoord.z + 0.1;
}