OpenGL-滤镜效果(二)

737 阅读3分钟

接上篇OpenGL-滤镜效果(一),接着探讨一些滤镜的算法实现。 原图放一张我家宠物怡宝: image.png

旋涡滤镜

图像旋涡主要是在某个半径范围内,把当前采样的点旋转一定角度,旋转后当前点的颜色就被旋转后的点颜色代替,因此整个半径范围里会有旋转的效果。 如果旋转时候的旋转角度随着当前点距离半径的距离递减,整个图像就会出现旋涡效果。 这里使用了抛物线递减因子:(1.0-(r/radius)*(r/radius))

片元着色器实例代码:

precision mediump float;

const float PI = 3.14159265;
uniform sampler2D Texture;

const float uD = 80.0;          //旋转角度参考值
const float uR = 0.5;

varying vec2 TextureCoordsVarying;

void main()
{
    ivec2 ires = ivec2(512, 512);
    float Res = float(ires.y);          //直径
    
    vec2 st = TextureCoordsVarying;     //纹理采样
    float Radius = Res * uR;            //半径
    
    vec2 xy = Res * st;                 //将纹理坐标对应到图片坐标
    
    vec2 dxy = xy - vec2(Res/2., Res/2.);       //获取连接当前图片坐标与图片中心的向量
    float r = length(dxy);      //获取当前图片坐标与图片中心的距离 以此作为旋涡的半径
    
    float attenValue = (1.0 -(r/Radius)*(r/Radius));        //获取选择因子
    
    float beta = atan(dxy.y, dxy.x) + radians(uD) * 2.0 * attenValue;   //获取旋转角度
  
    if(r <= Radius)
    {
        xy = Res/2.0 + r * vec2(cos(beta), sin(beta));  //获取旋转后的坐标
    }
    
    st = xy/Res;        //将坐标对应回纹理坐标
    
    vec3 irgb = texture2D(Texture, st).rgb;
    
    gl_FragColor = vec4( irgb, 1.0 );
}

效果如下: j

马赛克滤镜

马赛克效果的原理就是把图片的一个相当大小区域用同一个点的颜色来表示,可以认为是大规模降低了图像的分辨率,从而使图像一些细节隐藏起来。 #####普通马赛克实现

precision mediump float;

varying vec2 TextureCoordsVarying;
uniform sampler2D Texture;
const vec2 TexSize = vec2(400.0, 400.0);    //图像大小
const vec2 mosaicSize = vec2(16.0, 16.0);       //马赛克的大小

void main()
{
    //获取纹理坐标对应的图像坐标
    vec2 intXY = vec2(TextureCoordsVarying.x*TexSize.x, TextureCoordsVarying.y*TexSize.y);
    //floor为取整 将intXY除以马赛克的大小取整再乘以马赛克的大小,这样就使得这个马赛克区域内的像素都取自同一个像素
    vec2 XYMosaic = vec2(floor(intXY.x/mosaicSize.x)*mosaicSize.x, floor(intXY.y/mosaicSize.y)*mosaicSize.y);
    //恢复图像坐标为纹理坐标
    vec2 UVMosaic = vec2(XYMosaic.x/TexSize.x, XYMosaic.y/TexSize.y);
    vec4 color = texture2D(Texture, UVMosaic);
    gl_FragColor = color;
}

效果如下: image.png

六边形马赛克

我们要做的效果就是让一张图片,分割成由六边形组成,让每个六边形中的颜⾊相同(直接取六边形中心点像素RGB较方便,我们这里采用的就是这种⽅法) image.png

如上图,画出很多长和宽比例为 3:√3 的的矩形阵。然后我们可以对每个点进⾏编号,如上图中,采用坐标系标记. 假如我们的屏幕的左上点为上图的(0,0)点,则屏幕上的任⼀一点我们都能找到它所对应的那个矩形了。 假定我们设定的矩阵⽐例为 3LEN : √3LEN ,那么屏幕上的任意点(x, y)所对应的矩阵坐标为(int(x/(3LEN)), int(y/ (√3LEN)))。

//wx,wy -> 表示纹理坐标在所对应的矩阵坐标为(TR表示√3/2) int wx = int(x /( 1.5 * length)); int wy = int(y /(TR * length));

观察上图,不同的矩形计算方式不同,如(0,0),(0,1),(1,1),(1,0)所围成的矩形,处于六边形中心点的是(0,0)(1,1)这两个点,而(1,0),(1,1),(2,1),(2,0)围成的矩形,处于六边形中心点的是(1,1)(2,0)这两个点。

所以根据不同的行或列,分别需要取的矩形顶点有两种: 左上右下 和 左下右上

所以实现的思路大致如下:

  1. 找到当前坐标对应的矩形
  2. 判断该矩形的行与列来计算需要获得的中心点位置,
  3. 分别计算当前坐标与矩形顶点的距离,取距离小的那个顶点坐标
  4. 将计算完的顶点坐标还原为纹理坐标

代码如下:

precision highp float;
uniform sampler2D Texture;
varying vec2 TextureCoordsVarying;

const float mosaicSize = 0.03;          //马赛克大小

void main (void)
{
   float length = mosaicSize;
   float TR = 0.866025;            // √3/2
   
   float x = TextureCoordsVarying.x;
   float y = TextureCoordsVarying.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) {       //是否为偶数列
           //(0,0),(1,1)
           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 {
           //(0,1),(1,0)
           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) {
           //(0,1),(1,0)
           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 {
           //(0,0),(1,1)
           v1 = vec2(length * 1.5 * float(wx), length * TR * float(wy));
           v2 = vec2(length * 1.5 * float(wx + 1), length * TR * float(wy + 1));
       }
   }
   
   //获取 距离两个矩形顶点的距离
   float s1 = sqrt(pow(v1.x - x, 2.0) + pow(v1.y - y, 2.0));
   float s2 = sqrt(pow(v2.x - x, 2.0) + pow(v2.y - y, 2.0));
   
   //取距离近的顶点颜色值
   if (s1 < s2) {
       vn = v1;
   } else {
       vn = v2;
   }
   vec4 color = texture2D(Texture, vn);
   
   gl_FragColor = color;
   
}

效果如下: image.png

三角形马赛克

三角形马赛克就是在六边形马赛克的基础上,对每一个六边形分为6份,如下: image.png

通过计算每一个点与中心点的旋转角度来判断当前归属于哪一块三角形,最后计算该三角形的中心坐标 float a = atan((x - vn.x)/(y - vn.y)); //获取角度

举个例子: 第6个三角形中心坐标为: vec2 area6 = vec2(vn.x - mosaicSize / 2.0, vn.y - mosaicSize * TR / 2.0); 实现代码如下(基于上个六边形):

    const float PI6 = 0.523599;

    vec4 mid = texture2D(Texture, vn);
    float a = atan((x - vn.x)/(y - vn.y));

    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 && a < PI6 * 3.0) {
        vn = area1;
    } else if (a >= PI6 * 3.0 && a < PI6 * 5.0) {
        vn = area2;
    } else if ((a >= PI6 * 5.0 && a <= PI6 * 6.0) || (a < -PI6 * 5.0 && a > -PI6 * 6.0)) {
        vn = area3;
    } else if (a < -PI6 * 3.0 && a >= -PI6 * 5.0) {
        vn = area4;
    } else if(a <= -PI6 && a> -PI6 * 3.0) {
        vn = area5;
    } else if (a > -PI6 && a < PI6) {
        vn = area6;
    }
    
    vec4 color = texture2D(Texture, vn);
    gl_FragColor = color;

实现效果入下: image.png