在片段阶段存在活动的程序对象时,该程序对象的可执行代码用于处理由光栅化产生的传入片段。
实现允许跳过对某些片段着色器调用的执行,并且由于实现相关原因(包括在片段着色器阶段不存在活动的程序对象时),允许执行额外的片段着色器调用,只要渲染结果在其他方面保持不变。
在着色器执行之后,将执行第17章描述的固定功能操作。
片段着色器执行的特殊考虑因素在接下来的部分中描述。
第11.1.3.1节描述了顶点着色器可访问的纹理查找功能。在那里描述的texel fetch和纹理大小查询功能也适用于片段着色器。
当在片段着色器中执行纹理查找时,OpenGL会按照第8.14节和8.15节中描述的方式计算过滤后的纹理值τ,并将其转换为纹理基色Cb,如表15.1所示,然后根据纹理参数TEXTURE_SWIZZLE_R、TEXTURE_SWIZZLE_G、TEXTURE_SWIZZLE_B和TEXTURE_SWIZZLE_A的值对Cb的分量进行重新排列。如果TEXTURE_SWIZZLE_R的值由swizzler表示,则根据以下规则计算Cs的第一个分量:
if (swizzler == RED)
Cs[0] = Cb[0];
else if (swizzler == GREEN)
Cs[0] = Cb[1];
else if (swizzler == BLUE)
Cs[0] = Cb[2];
else if (swizzler == ALPHA)
Cs[0] = Ab;
else if (swizzler == ZERO)
Cs[0] = 0;
else if (swizzler == ONE)
Cs[0] = 1; // float or int depending on texture component type
Cs的其他分量(Cs[1]、Cs[2]和As)的重新排列方式由TEXTURE_SWIZZLE_G、TEXTURE_SWIZZLE_B和TEXTURE_SWIZZLE_A的值类似地控制。
最终的四分量向量(Rs,Gs,Bs,As)被返回给片段着色器。对于细节级别计算,可以通过差分算法来近似计算du/dx、du/dy、dv/dx、dv/dy、dw/dx和dw/dy的导数,方法在OpenGL着色语言规范的第8.13.1节(“导数函数”)中描述。
涉及深度和/或模板分量数据的纹理查找按照第11.1.3.5节中的描述执行。
片段着色器可以访问的内置输入值
gl_FragCoord:该内置变量包含了当前正在处理的片段在窗口坐标系中的位置信息,其包含四个分量(xf, yf, zf, wf)。xf 和 yf 分别是片段在窗口空间中的二维坐标,计算方式会根据像素中心和原点约定进行调整;zf 是片段的深度值(zw),已包含了启用时的多边形偏移量;wf 是裁剪空间中 w 成分的倒数,用于归一化操作。
gl_FrontFacing:这是一个布尔型变量,表示当前片段是否来自正向面对观察者的图元。对于三角形图元,通过计算面积的符号来确定正反面;其他类型的图元默认为正向面。
gl_PrimitiveID:如果几何着色器处于活动状态,此变量存储由几何着色器输出的触发顶点对应的ID号。若无几何着色器,则存储自上次绘图命令以来光栅器处理过的图元总数,并且每次处理一个点、线或面片时递增计数。
gl_SampleID:只读内置变量,提供了当前被处理样本的唯一编号,在多重采样帧缓冲区中范围从0到gl_NumSamples - 1
,而在非多重采样缓冲区中始终为0。使用此变量将导致片段着色器按每个样本独立执行。
gl_SamplePosition:同样为只读内置变量,它包含了当前样本在多重采样绘制缓冲区内的子像素位置,其x和y分量表示子像素坐标,范围在[0, 1]之间,像素中心的子像素坐标固定为(0.5, 0.5)。在非多重采样情况下,其值始终为(0.5, 0.5)。
gl_SampleMaskIn:这是一个整数数组,储存了位字段,表示对应片段着色器调用的图元所覆盖的所有样本集合。数组大小基于实现支持的最大颜色样本数,并按照一定的规则设置各个样本是否被当前图元覆盖的标志位。当渲染至非多重采样帧缓冲区或禁用多重采样光栅化时,所有位都为零,仅第一个元素的第一个位反映了像素是否被覆盖。同时,因SAMPLE_COVERAGE或SAMPLE_MASK导致被丢弃的样本对应的位不会被置1。在每样本着色模式下,只有当前样本对应的位会被设置。
此外,GLSL还规定了一个限制,即片段着色器可读取的内建及用户定义输入变量组件数量的最大值,这个值由实施相关的常数MAX_FRAGMENT_INPUT_COMPONENTS
决定。在链接程序时,片段着色器引用的所有输入变量的所有组件都会计入这个限制。如果片段着色器超出这一限制,除非设备特定的优化能够使其适应可用硬件资源,否则可能导致程序无法成功链接。对不同类型的变量以及变量声明的组件计数规则与MAX_VERTEX_OUTPUT_COMPONENTS
一致。
片段着色器允许程序员定义和输出自定义变量以及使用内置输出变量。
关键的内置输出包括:
gl_FragDepth:片段着色器可以重写此值来指定片段在深度缓冲区中的深度,从而影响像素是否可见以及如何进行深度测试。
gl_SampleMask:通过这个内置数组,片段着色器可以精细控制哪些样本应当参与后续的混合和其他操作,但不能用它使原本未被覆盖的样本生效。
用户自定义输出主要涉及颜色值,它们可以是多种数据类型,并且需要与颜色缓冲区格式相匹配。片段着色器可以输出多个颜色,这些颜色可以通过BindFragDataLocationIndexed
函数绑定到不同的颜色附件上,用于多重渲染目标(MRT)或自定义混合效果。
这个绑定可以在着色器文本或 SPIR-V 着色器中明确指定,也可以通过函数调用来完成。
void glBindFragDataLocationIndexed( uint program, uint colorNumber, uint index, const char * name );
program
:要将绑定应用于的着色器程序的 ID。colorNumber
:要将输出变量绑定到的片段颜色编号。index
:指定颜色将用作混合方程的第一个(0)还是第二个(1)颜色输入。name
:着色器代码中输出变量的名称。当调用 glBindFragDataLocationIndexed
时, OpenGL
将着色器程序中名为 name
的输出变量绑定到由 colorNumber
指定的片段颜色上。index
参数确定此颜色将作为混合方程的第一个或第二个颜色输入。
如果之前已经绑定了输出变量 name
,使用新的 colorNumber
调用 glBindFragDataLocationIndexed
将替换其分配的绑定。
请注意,绑定将在下次链接程序时生效。
void glBindFragDataLocation( uint program, uint colorNumber, const char * name );
// 等价于
glBindFragDataLocationIndexed(program, colorNumber, 0, name);
glBindFragDataLocation
在程序链接之前没有任何效果。特别地,它不会修改已经链接的程序中输出变量的绑定。
在链接包含片段着色器的程序时,每个活跃的用户自定义片段着色器输出变量都会分配一个由片段颜色编号、片段颜色索引和组件索引组成的绑定。这些绑定可以通过布局限定符在着色器代码中指定(如SPIR-V中的Location, Component, Index),或者通过BindFragDataLocationIndexed
和BindFragDataLocation
API函数来指定。未显式指定位置的变量将由链接器自动分配,并遵循特定规则以避免冲突。
当片段着色器执行结束时,其输出变量的值会被写入到相应绑定的颜色输出组件中,根据变量类型和绑定确定具体哪些组件被写入。数组类型的输出变量会按照其元素顺序和组件索引映射到连续的片段颜色编号上。
若出现以下情况,链接程序将会失败:
MAX_DRAW_BUFFERS
限制;MAX_DUAL_SOURCE_DRAW_BUFFERS
相关的限制冲突;此外,即使片段着色器还未附加至程序对象,也可以使用BindFragDataLocationIndexed
预先进行颜色编号和索引的绑定,对于实际不存在的变量名的绑定操作将被忽略。
最后,为了获取程序中片段着色器使用的输出属性变量信息,应用程序可以查询相关接口PROGRAM_OUTPUT
的资源和属性。
int glGetFragDataLocation( uint program, const char *name );
int glGetFragDataIndex( uint program, const char *name );
// 等价于
glGetProgramResourceLocation(program, PROGRAM_OUTPUT, name);
glGetProgramResourceLocationIndex(program, PROGRAM_OUTPUT, name);
在SPIR-V片段阶段中,通过OpEntryPoint和Output存储类声明的变量形成了片段输出接口。这些变量必须使用Location装饰,并且可以使用Component和/或Index装饰。
用户定义的片段着色器输出变量只通过它们的Location、Component和Index装饰进行匹配。如果两个输出位于同一位置,则它们必须具有相同的基本类型(浮点或整数)。不允许有输出变量的组件别名。换句话说,不能有两个具有相同位置、组件和索引的输出变量,无论是显式声明还是隐式声明。
由片段着色器写入的输出值必须使用OpTypeFloat或OpTypeInt声明,并且宽度为32。也可以使用这些类型的复合类型。
提供了显式控制以允许片段着色器启用早期片段测试。
layout(early_fragment_tests) in;
如果片段着色器指定了early_fragment_tests
布局限定符,那么描述在第14.9节中的每个片段测试将在片段着色器执行之前进行。否则,它们将在片段着色器执行之后进行。