Shader - 波浪加载动效

TIP

使用 shader 实现,波浪加载动效。

看到波浪的表现特点我第一时间想到的就是正弦曲线(或者说是正弦波,又让我想起了示波器)。


# 一. 效果


# 二. 正弦曲线

「正弦曲线」是三角函数中的一种正弦(Sine)比例的曲线。正弦曲线表现为一条波浪线,形状犹如海上完美的波浪。

标准的正弦函数公式为:

正弦函数属于周期函数,其值域为 [-1, 1]

如下图就是一个纯正标准的正弦曲线:

而一般我们常用的正弦曲线公式为:

这条公式比标准公式多了几个常数,含义如下:

  • A「振幅(Amplitude)」,曲线最高点与最低点的差值,表现为曲线的整体高度
  • ω「角速度(Angular Velocity)」,控制曲线的周期,表现为曲线的紧密程度
  • φ「初相(Initial Phase)」,即当 x = 0 时的相位,表现为曲线在坐标系上的水平位置
  • k「偏距(Offset)」,表现为曲线在坐标系上的垂直位置

# 相位:

相位(Phase):上方公式中的 ωx±φ 部分称为相位,相位发生在周期性的运动之中,最直接的理解就是角度。

有了公式之后,我们可以尝试调整其中的常数来改变函数曲线的形态。

# 1. 改变曲线的高度

我们可以调整常数 A(振幅) 来改变曲线的值域(值域为 [-A, A]):

# 2. 改变曲线的周期

我们可以调整常数 ω(角速度) 来改变曲线的周期:

# 3. 改变曲线的水平位置

我们可以调整常数 φ(初相) 来改变曲线的水平位置:

其实对于 “曲线的水平位置” 这个描述是不太准确的,因为初相实际上改变的是当 x = 0 时的相位,也就直接影响函数曲线在 x = 0 处的位置。

所以说曲线的位置并没有真正改变,而只是曲线的形态发生了改变。

但是由于正弦曲线的周期性特点,曲线的这种形态变化看起来像是曲线进行了位移。

# 4. 改变曲线的垂直位置

我们可以调整常数 k(偏距) 来改变曲线的垂直位置:


# 三. Effect

shader 代码如下:

CCEffect %{
  techniques:
  - passes:
    - vert: vs
      frag: fs
      blendState:
        targets:
        - blend: true
      rasterizerState:
        cullMode: none
      properties:
        amplitude: { value: 0.05, range: [0.0, 0.5], editor: { tooltip: '振幅' } }
        angularVelocity: { value: 10.0, editor: { tooltip: '角速度' } }
        frequency: { value: 10.0, editor: { tooltip: '频率' } }
        offset: { value: 0.5, range: [0.0, 1.0], editor: { tooltip: '偏距' } }
        toLeft: { value: true, editor: { type: boolean, tooltip: '向左(方向)' } }
}%


CCProgram vs %{
  precision highp float;

  #include <cc-global>

  in vec3 a_position;
  in vec4 a_color;
  in vec2 a_uv0;

  out vec4 v_color;
  out vec2 v_uv0;

  void main () {
    // 使用内置矩阵转换坐标
    gl_Position = cc_matViewProj * vec4(a_position, 1);
    
    // 传递顶点颜色
    v_color = a_color;

    // 传递 UV 坐标
    v_uv0 = a_uv0;
  }
}%


CCProgram fs %{
  precision highp float;

  // 引入 Cocos Creator 内置的全部变量
  #include <cc-global>

  in vec4 v_color;  // 顶点颜色
  in vec2 v_uv0;    // UV 坐标

  uniform sampler2D texture;  // 纹理

  // 自定义属性
  uniform Properties {
    float amplitude;        // 振幅
    float angularVelocity;  // 角速度
    float frequency;        // 频率
    float offset;           // 偏距
    bool toLeft;            // 是否向左
  };

  void main () {
    // 保存顶点颜色
    vec4 color = v_color;

    // 叠加纹理颜色
    color *= texture(texture, v_uv0);

    // 直接丢弃原本就透明的像素
    if(color.a == 0.0) discard;
    
    // 初相位(正值表现为向左移动,负值则表现为向右移动)
    // cc.time 是 Cocos Creator 引擎提供的运行时间全局变量(类型:vec4)
    // float initiaPhase = frequency * cc_time.x;

    // 方向(左正右负)
    // float direction = toLeft ? 1 : -1;

    // 代入正弦曲线公式计算 y 值
    // y = Asin(ωx ± φt) + k
    float y = amplitude * sin((angularVelocity * v_uv0.x) + ((frequency * cc_time.x) * (toLeft ? 1. : -1.))) + offset;

    // 丢弃 y 值以上的像素(左上角为原点 [0.0, 0.0])
    if(v_uv0.y < y) discard;

    // 输出颜色
    gl_FragColor = color;
  }
}%