漩涡滤镜

558 阅读4分钟

漩涡滤镜

顾名思义,这个滤镜效果就像一个🌀,在一个半径范围内,从中心到边缘,旋转角度逐渐减小。

那么在代码实现中,就是要在指定的半径范围内,把当前采样点旋转一定程度,并且用当前采样点的纹素覆盖掉原本这个位置的纹素。当前采样点的旋转角度也要随着它距离中心点的距离,越近越大。

🌀滤镜效果.png

这个滤镜效果的主要代码都在片元着色器中:

precision highp float;

//纹理坐标
varying vec2 textureCoordVarying;

//纹理采样器
uniform sampler2D textureSampler;

//旋转角度
const float rotateDegrees = 150.0;
//旋转半径(相对于纹理坐标)
const float rotateRadius = 0.5;

void main() {
		//设置滤镜范围
    ivec2 filterSize = ivec2(512, 512);
    //获取滤镜范围内的直径
    float diameter = float(filterSize.y);
    //获取滤镜范围内需要旋转的半径
    float radius = rotateRadius * diameter;
    
    //纹理坐标
    vec2 st = textureCoordVarying;
    //当前纹理坐标在滤镜范围内的位置
    vec2 currentPosition = st * diameter;
    
    //获取currentPosition相对于圆心(纹理坐标(0.5, 0.5)的位置)的那一段向量
    //把xy向左向下偏移一个半径的长度,后面会再移回原位,这样是为了方便计算xy到圆心的距离
    vec2 distanceXY = currentPosition - vec2(diameter / 2.0, diameter / 2.0);
    //获取currentPosition到圆心的距离
    float r = length(distanceXY);
    
    //递减因子
    float factor = 1.0 - (r / radius) * (r / radius);
    //最终旋转角度 = 当前的角度 + 需要旋转的角度 * 递减因子
    float beta = atan(distanceXY.y, distanceXY.x) + radians(rotateDegrees) * factor;
    
    //如果currentPosition距离圆心的距离小于等于半径
    if (r <= radius) {
    		//计算旋转后的位置
        distanceXY = r * vec2(cos(beta), sin(beta));
        //平移回原位
        currentPosition = vec2(diameter / 2.0, diameter / 2.0) + distanceXY;
    }
    //旋转后的纹理坐标
    st = currentPosition / diameter;
    
    //旋转后的纹素
    vec3 rotatedRGB = texture2D(textureSampler, st).rgb;
    
    gl_FragColor = vec4(rotatedRGB, 1.0);
}

详细解析

  • 首先看几个重要的变量
    • textureCoordVarying:从顶点着色器传过来的纹理坐标
    • textureSampler:uniform通道传入的纹理
    • rotateDegrees:可以旋转的最大角度
    • rotateRadius:旋转的半径
  • main函数
    • 定义变量
      • 设置一个滤镜范围filterSize,把纹理坐标(0~1之间)转为范围值,方便计算
      • 设置直径diameter并计算出半径radius
      • 由于需要计算旋转后的位置,另外定义一个变量st来存储纹理坐标
      • 当前纹理坐标st ✖️ 滤镜范围的直径diameter,就是这个坐标点在这个滤镜范围内的位置currentPosition
    • 计算当前位置currentPosition距离圆心的半径r
      • 先把当前位置currentPosition向左向下平移一个半径radius的长度。为什么要这样平移呢?

      • 首先纹理坐标的原点是在左下角,而我们要计算当前点距离圆心的位置,而圆心和原点之间的距离就是左下角方向一个半径的偏移量。

      • 平移.png

      • 比如图中有两个点,点O`平移到了点O',点P平移到了定p',O到圆心的距离和角度都和O'到原点的距离和角度一样。

      • 这样偏移到原点位置,就可以很方便的用坐标的x,y来计算角度了。后面会再移回原处的。

      • length函数就可以求得平移后的向量distanceXY的长度了。也就是当前位置距离圆心的半径长度(图中红色和绿色的虚线)。

    • 计算旋转角度
      • 抛物线递减因子:距离圆心越近,旋转角度越大;
      • float factor = 1.0 - (r / radius) * (r / radius);
      • 当前位置的角度:distanceXY向量的atan值;(下图中角θ)
      • tan θ = 对边 / 邻边
      • tan θ = y' / x'
      • tan θ = distanceXY.y / distanceXY.x
      • atan函数求反正切值
      • 当前位置的角度.png
      • 再➕(需要旋转的角度✖️递减因子)
      • 等于最终角度beta
      • 旋转后的beta角.png
    • 计算旋转后的位置
      • 如果当前位置currentPosition距离圆心的半径r小于等于旋涡半径radius,就计算旋转后的位置;
      • 根据旋转角度的余弦值cos(beta)和正弦值sin(beta)乘以半r径计算旋转后位置的两个边长,也就是x,y的值;
      • cos beta = 邻边 / 斜边
      • cos beta = x'' / r
      • sin beta = 对边 / 斜边
      • sin beta = y'' / r
      • (cos(beta), sin(beta)) * r = (x'', y'')
      • 计算旋转后的位置.png
      • 再反向平移回原处,得到旋转后的位置;
    • 范围值转换成旋转后的纹理坐标st
    • 获取旋转后的纹素rotatedRGB的颜色值
    • 使用旋转后的颜色