小言_互联网的博客

OpenGL ES 3.0 片段着色器介绍和使用

296人阅读  评论(0)

一、简介

备注:内容来自于《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 场景的一种常用技术。要计算任何类型的雾化,需要两个输入:像素到眼睛的距离以及雾化的颜色。要计算线性雾化,还需要雾化所覆盖的最小和最大距离的范围。

线性雾化因子方程式:

F = M a x D i s t E y e D i s t M a x D i s t M i n D i s t F=\frac{MaxDist-EyeDist}{MaxDist-MinDist}

顶点着色器

计算到眼睛的距离,即 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 x + B y + C z + D = 0 A_x+B_y+C_z+D=0

向量 (A, B, C) 代表平面的法线,D 表示平面沿该向量到原点的距离。点 P 到一个平面的距离可以用下面方程式得出:

D i s t = ( A P . x ) + ( B P . y ) + ( C P . z ) + D Dist=(A*P.x)+(B*P.y)+(C*P.z)+D

如果距离小于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
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场