1、管线
片段着色器在OpenGL ES 3.0的可编程管线的位置如下图所示。
2、输入输出
片段着色器为片段操作提供了通用功能的可编程方法,输入和输出如下图所示。
输入或者可变值,in,是顶点着色器生成的插值数据,顶点着色器输出跨图元进行插值,并作为输入传递给片段着色器。
统一变量,uniform,是片段着色器使用的状态,为常量值,在每个片段上不会变化。
采样器,sampler2D,用于访问着色器中的纹理对象。
输入,还包括代码,即片段着色器源代码或者二进制代码,描述在片段上执行的操作。
输出,是一个或多个片段颜色,传递到管线的逐片段操作部分,输出颜色的数量取决于使用了多少个颜色附着。
3、内建特殊变量
片段着色器中可用的内建特殊变量包括gl_FragCoord、gl_FrontFacing、gl_PointCoord、gl_FragDepth。
gl_FragCoord,只读,保存片段的相对窗口坐标(x, y, z, 1/w)。例如,用于较少阴影贴图锯齿失真的技术,可以使用窗口坐标作为某个随机噪声贴图纹理读取的偏移量,噪声贴图的值用于旋转阴影贴图的过滤核心。
gl_FrontFacing,只读,片段属于图元的正面时为ture,否则为false。
gl_PointCoord,只读,保存点的纹理坐标,在点的光栅化阶段自动生成,处于[0, 1]区间内。
gl_FragDepth,只写,覆盖片段的固定功能深度值。这个功能可能禁用许多GPU的深度优化,应该谨慎使用,因为许多GPU有所谓的Early-Z功能,在执行片断着色器之前进行深度测试,这样不能通过深度测试的片段永远不会被着色,从而保护了性能,所以使用gl_FragDepth时,必须禁用Early-Z功能。
4、内建常量
片段着色器相关的内建常量如下,每个内建常量所指定的值是所有OpenGL ES 3.0实现必须支持的最小值。
const mediump int glMaxFragmentInputVectors = 15;
const mediump int glMaxTextureImageUnits = 16;
const mediump int glMaxFragmentUniformVectors = 224;
const mediump int glMaxDrawBuffers = 4;
const mediump int glMinProgramTexelOffset = -8;
const mediump int glMaxProgramTexelOffset = 7;
5、精度限定符
精度限定符,在顶点着色器和片段着色器中不同,片段着色器中没有默认精度,每个片断着色器必须声明一个默认精度或者对所有变量声明提供精度限定符。
6、多重纹理
多重纹理,用于组合多个纹理贴图,是片段着色器中常见的操作。在片段着色器中以不同的方式组合纹理很简单,就是采用着色语言中存在的许多运算符和内建函数。下面是一个使用了多重纹理的例子,完整代码请参照https://github.com/geminy/aidear/tree/master/graphics/mu/examples/opengles3/FragmentShader/MultiTexture。
#version 300 es
precision mediump float;
in vec2 v_texCoord;
layout(location = 0) out vec4 outColor;
uniform sampler2D s_baseMap;
uniform sampler2D s_lightMap;
void main()
{
vec4 baseColor;
vec4 lightColor;
baseColor = texture( s_baseMap, v_texCoord );
lightColor = texture( s_lightMap, v_texCoord );
outColor = baseColor * (lightColor + 0.25);
}
这里使用了两个采样器,每个纹理使用一个,它们共用一个纹理坐标,在典型的纹理贴图照明中,基本贴图和照明贴图应该有一组单独的纹理坐标,照明贴图通常混合到单一的大型纹理中,纹理坐标可以使用离线工具生成。例子中,加载了一个基本纹理贴图和照明纹理贴图,这两个纹理贴图以及最后的两重纹理效果如下图。
7、雾化
雾化,是渲染3D场景的一种常用技术,可以用于减小绘图距离,并且清除靠近观看者的几何体的突现现象。雾化的计算有几种可能的方式,使用可编程片段着色器,就不必局限于任何特定的方程式。要计算任何类型的雾化,需要两个输入,像素到眼睛的距离以及雾化的颜色。要计算线性雾化,还需要雾化所覆盖的最小和最大距离范围。线性雾化因子方程式为(最大距离-眼睛距离)/(最大距离-最小距离)。计算一个线性雾化因子,用于乘以雾化颜色,这个颜色限制在[0.0, 1.0]的区间内,然后和片段的总体颜色进行线性插值,以算出最终的颜色。到眼睛的距离最好在顶点着色器中算出,然后用可变变量进行跨图元插值。下面例子的顶点着色器计算了到眼睛的距离。
#version 300
uniform mat4 u_matViewProjection;
uniform mat4 u_matView;
uniform vec4 u_eyePos;
in vec4 a_vertex;
in vec2 a_texCoord0;
out vec2 v_texCoord;
out float v_eyeDist;
void main( void )
{
// Transform vertex to view-space
vec4 vViewPos = u_matView * a_vertex;
// Compute the distance to eye
v_eyeDist = sqrt( (vViewPos.x - u_eyePos.x) *
(vViewPos.x - u_eyePos.x) +
(vViewPos.y - u_eyePos.y) *
(vViewPos.y - u_eyePos.y) +
(vViewPos.z - u_eyePos.z) *
(vViewPos.z - u_eyePos.z) );
gl_Position = u_matViewProjection * a_vertex;
v_texCoord = a_texCoord0.xy;
}
下面例子的片段着色器渲染了线性雾化。
#version 300
precision mediump float;
uniform vec4 u_fogColor;
uniform float u_fogMaxDist;
uniform float u_fogMinDist;
uniform sampler2D baseMap;
in vec2 v_texCoord;
in float v_eyeDist;
layout( location = 0 ) out vec4 outColor;
float computeLinearFogFactor()
{
float factor;
// Compute linear fog equation
factor = (u_fogMaxDist - v_eyeDist) /
(u_fogMaxDist - u_fogMinDist );
// Clamp in the [0,1] range
factor = clamp( factor, 0.0, 1.0 );
return factor;
}
void main( void )
{
float fogFactor = computeLinearFogFactor();
vec4 baseColor = texture( baseMap, v_texCoord );
// Compute final color as a lerp with fog factor
outColor = baseColor * fogFactor +
u_fogColor * (1.0 - fogFactor);
}
线性雾化效果图如下。
8、Alpha测试
Alpha测试,是传统的固定功能管线中的功能,在OpenGL ES 3.0中不存在,不过可以使用discard关键字实现相同的效果。下面是通过着色器实现Alpha测试功能的例子。
#version 300
uniform mat4 u_matViewProjection;
in vec4 a_vertex;
in vec2 a_texCoord0;
out vec2 v_texCoord;
void main( void )
{
gl_Position = u_matViewProjection * a_vertex;
v_texCoord = a_texCoord0.xy;
}
#version 300
precision mediump float;
uniform sampler2D baseMap;
in vec2 v_texCoord;
layout( location = 0 ) out vec4 outColor;
void main( void )
{
vec4 baseColor = texture( baseMap, v_texCoord );
if( baseColor.a < 0.25 )
{
discard;
}
else
{
outColor = baseColor;
}
}
Alpha测试的效果图如下。
9、用户裁剪平面
一般情况下,所有图元根据组成视锥的六个平面进行裁剪,但是,用户有时候可能想要根据一个或者多个额外的用户裁剪平面进行裁剪。例如,渲染反射时,需要根据反射平面翻转几何形状,然后将其渲染到屏幕外纹理中,在渲染到纹理时,需要根据反射平面裁剪几何形状,这就需要用户裁剪平面。下面的例子用到了用户裁剪平面,以及效果图如下所示。
#version 300
uniform vec4 u_clipPlane;
uniform mat4 u_matViewProjection;
in vec4 a_vertex;
out float v_clipDist;
void main( void )
{
// Compute the distance between the vertex and
// the clip plane
v_clipDist = dot( a_vertex.xyz, u_clipPlane.xyz ) +
u_clipPlane.w;
gl_Position = u_matViewProjection * a_vertex;
}
#version 300
precision mediump float;
in float v_clipDist;
layout( location = 0 ) out vec4 outColor;
void main( void )
{
// Reject fragments behind the clip plane
if( v_clipDist < 0.0 )
discard;
outColor = vec4( 0.5, 0.5, 1.0, 0.0 );
}