一、简介
备注:内容来自于《OpenGL ES 3.0编程指南》一书。
片段着色器和顶点着色器一样都是运行在 GPU 上的程序,片段着色器将顶点着色器输出的片段作为输入,并负责输出每个片段的颜色。
片段着色器的输入输出图示:
二、内建变量
内建变量是指着色器语言中已经定义好的,并有特殊意义的一些变量。
1. 内建特殊变量
变量 | 描述 |
---|---|
gl_FragCoord | 片段着色器中的一个只读变量,保存了片段的窗口相对坐标(x,y,z,l/w)。可以使用窗口坐标作为某个随机噪声贴图纹理读取的偏移量,噪声贴图的值用于旋转阴影贴图的过滤核心,这种技术用于减少阴影贴图的锯齿失真。 |
gl_FrontFacing | 片段着色器中的一个只读的布尔变量,片段是正面图片时为 true,否则为 false。 |
gl_PointCoord | 一个只读变量,可以在渲染点精灵时使用。保存了点精灵的纹理坐标。 |
gl_FragDepth | 一个只写输出变量,会覆盖片段的固定功能深度值,应该谨慎使用,因为它可能禁用许多 GPU 的深度优化。 |
2. 内建常量
类型 | 名称 | 最小值 | 描述 |
---|---|---|---|
const mediump int | gl_MaxFragmentInputVectors | 15 | 片段着色器输入的最大数量 |
const mediump int | gl_MaxTextureImageUnits | 16 | 可用纹理图像单元的最大数量 |
const mediump int | gl_MaxFragmentUniformVectors | 224 | 片段着色器中可以使用的 vec4 统一变量的最大数量 |
const mediump int | gl_MaxDrawBuffers | 4 | 多重渲染目标(MRT)的最大支持数量 |
const mediump int | gl_MinProgramTexelOffset | -8 | 内建 ESSL 函数 texture*Offset() 偏移参数支持的最小值 |
const mediump int | gl_MaxProgramTexelOffset | 7 | 内建 ESSL 函数 texture*Offset() 偏移参数支持的最大值 |
注意,以上最小值是 OpenGL ES 3.0 实现必须支持的最小值,不同的实现可能支持大于上述最小值,内建数值的实际硬件特定值可以从 API 中查询,如:
GLint maxTextureImageUnits;
glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &maxTextureImageUnits);
三、精度限定符
在片段着色器中,浮点值没有默认精度,必须由开发者声明。
较低的精度效率更高,较高的精度效果更高。这种提升效率是以精度为代价的,不合适的精度限定符可能会导致伪像。
精度限定符种类:
- lowp : 低精度
- mediump : 中精度
- highp : 高精度
四、常见功能实现示例
在 OpenGL ES 1.x 固定管线提供的 API 中,可以执行多重纹理、雾化、Alpha 测试和用户裁剪平面等技术。这些在 OpenGL ES 3.0 中都没有直接提供,但可以使用着色器去实现。
1. 多重纹理
多重纹理常用于组合多个纹理贴图,例如将一个基本纹理贴图和一个照明纹理贴图相结合,实现静态照明的效果。
片段着色器示例:
#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);
// Add a 0.25 ambient light to the texture light color
outColor = baseColor * (lightColor + 0.25);
}
上面片段着色器中用到了两个采样器,分别对应了两个不同的纹理。
调用代码示例:
// 绑定 base map 纹理对象到 TEXTURE0
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, userData->baseMapTexId);
glUniform1i(userData->baseMapLoc, 0);
// 绑定 light map 纹理对象到 TEXTURE1
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, userData->lightMapTexId);
glUniform1i(userData->lightMapLoc, 1);
2. 雾化
雾化是渲染 3D 场景的一种常用技术。要计算任何类型的雾化,需要两个输入:像素到眼睛的距离以及雾化的颜色。要计算线性雾化,还需要雾化所覆盖的最小和最大距离的范围。
线性雾化因子方程式:
顶点着色器
计算到眼睛的距离,即 v_eyeDist
的计算。
#version 300 es
uniform mat4 u_matViewProjection;
uniform mat4 u_matView;
uniform vec4 u_eyePos;
in vec4 a_vertex;
in vec2 a_texCoord0;
out vec2 v_texCoor;
out float v_eyeDist;
void main() {
// 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_texCoor = a_texCoord0.xy;
}
片段着色器
渲染线性雾化
#version 300 es
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() {
float factor = computeLinearFogFactor();
vec4 baseColor = texture(baseMap, v_texCoord);
// compute final color as a lerp with fog factor
outColor = baseColor * factor + u_fogColor * (1.0 - factor);
}
3. Alpha 测试(使用 Discard)
如渲染链状栅栏等物体,需要绘制一些完全透明的图元。通常我们可以把链状栅栏保存在一个 RGBA 纹理中,其中 RGB 表示栅栏的颜色,A 表示纹理的透明度。
传统功能管线渲染中,这种功能使用 Alpha 测试完成。通过将片段的 Alpha 值和参考值比较,测试不通过则删除片段,便不会渲染。OpenGL ES 3.0 中去除了 Alpha 测试,但是可以使用 discard 关键字实现相同的效果。
片段着色器
#version 300 es
precision mediump float;
uniform sampler2D baseMap;
in vec2 v_texCoord;
layout(location = 0) out vec4 outColor;
void main() {
vec4 baseColor = texture(baseMap, v_texCoord);
// discard all fragments with alpha value less than 0.25
if (baseColor.a < 0.25) {
discard;
} else {
outColor = baseColor;
}
}
4. 用户裁剪平面
实现裁剪平面的关键也是使用 discard 关键字。
首先回顾一些基本的数学知识,平面可以由下面方程式指定:
向量 (A, B, C) 代表平面的法线,D 表示平面沿该向量到原点的距离。点 P 到一个平面的距离可以用下面方程式得出:
如果距离小于0,则该点在平面下,应该裁剪。
顶点着色器
在顶点着色器中计算到平面的距离,从性能角度上讲,在顶点着色器中进行计算比在每个片段中计算距离更经济。
#version 300 es
uniform vec4 u_clipPlane;
uniform mat4 u_matViewProjection;
in vec4 a_vertex;
out float v_clipDist;
void main() {
// 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;
}
片段着色器
如果 v_clipDist
变量为负,则意味着在裁剪平面之下,需要抛弃,否则正常处理片段。
#version 300 es
precision mediump float;
in float v_clipDist;
layout(location = 0) out vec4 outColor;
void main() {
// reject fragments behind the clip plane
if (v_clipDist < 0.0)
discard;
outColor = vec4(0.5, 0.5, 1.0, 0.0);
}
转载:https://blog.csdn.net/afei__/article/details/100083646