几种常见的简单滤镜

本文将介绍几种常见的滤镜效果,同时介绍它们的实现原理

image-20200817145323833

灰度滤镜

原理

图片的显示是由三个颜色通道 RGB 所决定,而灰度滤镜的本质就是所有颜色通道的值相同即可,即只需设置亮度值。

GPUImage 中的权重值:RGB分别占比为 0.2125, 0.7154, 0.0721

类似的几种权重值:

  • 浮点算法: Gray = R * 0.3 + G * 0.59 + B * 0.11
  • 整数算法: Gray = (R * 30 + G * 59 + B * 11) / 100
  • 移位算法: Gray = (R * 76 + G * 151 + B * 28) >> 8
  • 平均值法: Gray = (R + G + B) / 3
  • 仅取绿色: Gray = G

片段着色器代码

  1. 设置 RGB 权重值

  2. 提取原始纹理的纹素

    纹素(Texel)是纹理元素的简称,它是计算机图形纹理空间中的基本单元。

    如同图像由像素排列而成,纹理则是由纹素排列表示的。

  3. 利用向量与向量的点积,得到亮度值

  4. 将亮度值赋值给 gl_FragColor

precision highp float;

varying vec2 textureCoordinate;
 
uniform sampler2D inputImageTexture;

const highp vec3 ratio = vec3(0.2125, 0.7154, 0.0721);

void main()
{
    vec4 mask = texture2D(inputImageTexture, textureCoordinate);
    float luminance = dot(mask.rgb, ratio);
    gl_FragColor = vec4(vec3(luminance), 1.0);
}

旋涡滤镜

原理

旋涡效果是在某一个半径范围内,把当前采样点旋转一定角度,旋转以后当前点的颜色就被旋转后的点的颜色替代,因此整个半径范围内都会由旋转的效果。

如果旋转的时候,旋转角度随着当前点离原先的距离递减,整个图像就会出现旋涡效果。

抛物线递减因子:$(1.0 - (r / Radius) * (r / Radius))$,r 是采样点距离圆心的距离,Radius 是半径

image-20200817152817479

如上图所示,点 A 为纹理采样点(0, 0),B 点为圆心(0.5, 0.5),C 点在圆上。

向量 AC 为 xy, 向量 AB 为 (Radius / 2, Radius / 2),因此向量AC-向量AB = 向量BC,即 dxy,其长度为 r,角度为 atan(dxy.y, dxy.x)。

  • r = 半径时,递减因子为 0,角度不变,C 点所在位置像素不会发生变化
  • r < 半径时,递减因子逐渐增大,C点开始旋转偏移

片段着色器代码

precision highp float;

varying vec2 textureCoordinate;
 
uniform sampler2D inputImageTexture;
 
const float uD = 80.0;

const float uR = 0.5;

void main()
{
    float Radius = uR;
    vec2 xy = textureCoordinate;
    
    vec2 dxy = xy - vec2(0.5, 0.5);
    
    float r = length(dxy);
    
    float beta = atan(dxy.y, dxy.x) + radians(uD) * 2.0 * (1.0 - (r / Radius) * (r / Radius));
    
    if (r <= Radius) {
        xy = 0.5 + r * vec2(cos(beta), sin(beta));
    }

    vec3 irgb = texture2D(inputImageTexture, xy).rgb;
    gl_FragColor = vec4(irgb, 1.0);
}

颠倒滤镜

原理

对 y 值进行取反即可

片段着色器代码

precision highp float;

varying vec2 textureCoordinate;
 
uniform sampler2D inputImageTexture;
 
void main()
{
    vec4 color = texture2D(inputImageTexture, vec2(textureCoordinate.x, 1.0 - textureCoordinate.y));
    gl_FragColor = color;
    
}

马赛克

马赛克效果是把图片的一个相当大小的区域用同一个点的颜色来填充,是大规模的降低图像的分辨率,从而将图像的细节进行隐藏

矩形马赛克

原理

将纹理划分为一个个矩形,算出每一个矩形的颜色即可

片段着色器代码

  • 设置矩形所占纹理范围比例mosaicSizeRatio
  • 计算纹理范围内最大能够切割的矩形数totalXY
  • 确定当前纹素所在矩形范围eachXY
  • 计算当前纹素对应的纹理坐标UVMosaic
precision highp float;

varying vec2 textureCoordinate;
 
uniform sampler2D inputImageTexture;
 
const vec2 mosaicSizeRatio = vec2(0.05, 0.05);

void main()
{
    vec2 totalXY = vec2(floor(1.0 / mosaicSizeRatio.x), floor(1.0 / mosaicSizeRatio.y));
    
    vec2 eachXY  = vec2(floor(textureCoordinate.x / mosaicSizeRatio.x), floor(textureCoordinate.y / mosaicSizeRatio.y));
    
    vec2 UVMosaic = vec2(eachXY.x / totalXY.x , eachXY.y / totalXY.y);
    
    
    gl_FragColor = texture2D(inputImageTexture, UVMosaic);
}

六边形马赛克

原理

将一张图片切割成由六边形组装而成的,然后取每个六边形的中心点,连接成矩形;

计算纹理坐标与中心点的距离,采用距离近的中心点的颜色值进行填充六边形。

image-20200817162016735

片段着色器代码

  • 设置矩形的长宽比 TR, TB(TB:TR 符合比例 3:$\sqrt 3$)

    image-20200817162243482

  • 获取纹理坐标x,y

  • 根据纹理坐标计算对应的矩形坐标 wx,wy

    image-20200817162822622

  • 根据行列的奇偶情况,求对应的中心点纹理坐标v1、v2

    • 偶行偶列:(0,0)(1,1)/,即左上、右下
    • 偶行奇列:(0,1)(1,0)\,即左下、右上
    • 奇行偶列:(0,1)(1,0)\,即左下、右上
    • 奇行奇列:(0,0)(1,1)/,即左上、右下

    image-20200817162952700

    对于每个矩形,4 个点的坐标计算为:

    image-20200817163124966

  • 计算当前纹理距离两个中心点的距离

  • 比较两个距离,取距离更近的点的颜色值

precision highp float;

varying vec2 textureCoordinate;
 
uniform sampler2D inputImageTexture;
 
const float mosaicSize = 0.02;

void main()
{
    float length = mosaicSize;

    float TR = sqrt(3.0) / 2.0;

    float x = textureCoordinate.x;
    float y = textureCoordinate.y;

    int wx = int(x / 1.5 / length);
    int wy = int(y / TR / length);
    
    vec2 v1, v2, vn;
    if(wx / 2 * 2 == wx){
        if(wy / 2 * 2 == wy){
            v1 = vec2(length * 1.5 * float(wx), length * TR * float(wy));
            v2 = vec2(length * 1.5 * float(wx + 1), length * TR * float(wy + 1));
        }else {
            v1 = vec2(length * 1.5 * float(wx), length * TR * float(wy + 1));
            v2 = vec2(length * 1.5 * float(wx + 1), length * TR * float(wy));
        }
    }else {
        if(wy / 2 * 2 == wy){
            v1 = vec2(length * 1.5 * float(wx), length * TR * float(wy + 1));
            v2 = vec2(length * 1.5 * float(wx + 1), length * TR * float(wy));
        }else {
            v1 = vec2(length * 1.5 * float(wx), length * TR * float(wy));
            v2 = vec2(length * 1.5 * float(wx + 1), length * TR * float(wy + 1));
        }  
    }

    if(distance(v1, textureCoordinate) < distance(v2, textureCoordinate)){
        vn = v1;
    }else {
        vn = v2;
    }

    gl_FragColor = texture2D(inputImageTexture, vn);
}

三角形马赛克

原理

三角形马赛克是在六边形马赛克的基础上,进行一些变形。本质是将六边形分成 6 个三角形组成

片段着色器代码

  • 计算当前纹理坐标与中心点的夹角

    image-20200817164642919

  • 计算 6 个三角形的中心点

    image-20200817164729531

  • 判断夹角属于哪个三角形,则获取哪个三角形的中心点坐标

    image-20200817164848900

precision highp float;

varying vec2 textureCoordinate;
 
uniform sampler2D inputImageTexture;

const float mosaicSize = 0.02;
const float PI6 = 0.523599;

void main()
{
    float length = mosaicSize;

    float TR = sqrt(3.0) / 2.0;

    float x = textureCoordinate.x;
    float y = textureCoordinate.y;

    int wx = int(x / 1.5 / length);
    int wy = int(y / TR / length);
    
    vec2 v1, v2, vn;
    if(wx / 2 * 2 == wx){
        if(wy / 2 * 2 == wy){
            v1 = vec2(length * 1.5 * float(wx), length * TR * float(wy));
            v2 = vec2(length * 1.5 * float(wx + 1), length * TR * float(wy + 1));
        }else {
            v1 = vec2(length * 1.5 * float(wx), length * TR * float(wy + 1));
            v2 = vec2(length * 1.5 * float(wx + 1), length * TR * float(wy));
        }
    }else {
        if(wy / 2 * 2 == wy){
            v1 = vec2(length * 1.5 * float(wx), length * TR * float(wy + 1));
            v2 = vec2(length * 1.5 * float(wx + 1), length * TR * float(wy));
        }else {
            v1 = vec2(length * 1.5 * float(wx), length * TR * float(wy));
            v2 = vec2(length * 1.5 * float(wx + 1), length * TR * float(wy + 1));
        }
    }

    if(distance(v1, textureCoordinate) < distance(v2, textureCoordinate)){
        vn = v1;
    }else {
        vn = v2;
    }
    
    vec4 mid = texture2D(inputImageTexture, textureCoordinate);
    float a = atan(y- vn.y, x - vn.x);

    vec2 area1 = vec2(vn.x, vn.y - mosaicSize * TR / 2.0);
    vec2 area2 = vec2(vn.x + mosaicSize / 2.0, vn.y - mosaicSize * TR / 2.0);
    vec2 area3 = vec2(vn.x + mosaicSize / 2.0, vn.y + mosaicSize * TR / 2.0);
    vec2 area4 = vec2(vn.x, vn.y + mosaicSize * TR / 2.0);
    vec2 area5 = vec2(vn.x - mosaicSize / 2.0, vn.y + mosaicSize * TR / 2.0);
    vec2 area6 = vec2(vn.x - mosaicSize / 2.0, vn.y - mosaicSize * TR / 2.0);

    if (a >= PI6 * 2.0 && a < PI6 * 4.0) {
        vn = area1;
    } else if (a >= 0.0 && a < PI6 * 2.0) {
        vn = area2;
    } else if (a>= -PI6 * 2.0 && a < 0.0) {
        vn = area3;
    } else if (a >= -PI6 * 4.0 && a < -PI6 * 2.0) {
        vn = area4;
    } else if(a >= -PI6 * 6.0&& a < -PI6 * 4.0) {
        vn = area5;
    } else if (a >= PI6 * 4.0 && a < PI6 * 6.0) {
        vn = area6;
    }
    vec4 color = texture2D(inputImageTexture, vn);
    gl_FragColor = color;
}

atan是GLSL中的内建函数,这是一个计算角度的反正切函数,有两种计算方式

1、atan(y,x) 值域是[-π,π],

2、atan(y/x) 值域是[-π/2, π/2]

文中片段着色器代码获取