OpenGL ES 滤镜实现_马赛克

486

注:本文旨在记录笔者的学习过程,仅代表笔者个人的理解,如果有表述不准确的地方,欢迎各位指正!因为涉及到的概念来源自网络,所以如有侵权,也望告知!

前言

本文主要在GLSL显示普通图⽚的基础上实现马赛克滤镜效果。如果对于GLSL显示图片任然有疑惑的同学可以看一下这篇文章——OpenGL ES GLSL 初体验

正文

一.何为马赛克

⻢赛克效果,本质上就是把图⽚的⼀个相当⼤⼩的区域⽤同⼀个点的颜⾊来表示。可以认为是⼤规模的降低图像的分辨率,⽽让图像的⼀些细节隐藏起来的一种技术手段。

二.实现原理

当然实现我们的马赛克效果,首先得能通过GLSL方式正常显示一张纹理图片。对应的顶点着色器和片元着色器的代码如下:

a.顶点着色器

attribute vec4 Position;
attribute vec2 TextureCoords;
varying vec2 TextureCoordsVarying;

void main (void) {
    gl_Position = Position;
    TextureCoordsVarying = TextureCoords;
}

b.片元着色器

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

void main (void) {
    vec4 mask = texture2D(Texture, TextureCoordsVarying);
    gl_FragColor = vec4(mask.rgb, 1.0);
}

在原图的基础上,无论是方形马赛克、六边形马赛克、三角形马赛克,原理上都是通过对片元着色器的纹理坐标的运算操作重新得到对应纹素的过程。

1.方形马赛克

实现原理:正方形马赛克相对较为简单,通过将floor(x)内建函数将一定大小的正方形区域内像素点纹理坐标进行向下取整,使得该正方形区域内的所有像素点的纹理坐标相同,最终取到同一位置的纹素。

片元着色器实现:

precision mediump float;
//纹理坐标
varying vec2 TextureCoordsVarying;
//纹理采样器
uniform sampler2D Texture;
//纹理图⽚size
const vec2 TexSize = vec2(400.0, 400.0);
//⻢赛克Size
const vec2 mosaicSize = vec2(16.0, 16.0);

void main() { 
    //计算实际图像位置 
    vec2 intXY = vec2(TextureCoordsVarying.x * TexSize.x, TextureCoordsVarying.y * TexSize.y);
    //floor(x)内建函数,返回⼩于等于X的最⼤整数值.计算出⼀个⼩⻢赛克的坐标.
    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.
    gl_FragColor = color;
}

2.六边形马赛克

实现原理:六边形马赛克的实现过程就比较复杂,我们要做的效果就是让⼀张图⽚,分割成由六边形组成,让每个六边形中的颜⾊相同(直接取六边形中⼼点像素RGB较⽅便,我们这⾥采⽤的就是这种⽅法)将它进⾏分割,取每个六边形的中⼼点画出⼀个矩阵,如下:

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

既然知道了屏幕上的任意点的坐标,那么我们到底怎么计算当前像素点所需要展示的纹理呢?如图,我们会发现矩形阵中存在两种形式的矩形:

a.矩形一:

b.矩形一:

以矩形一为例,对应纹理坐标在所对应的矩阵坐标的计算方式如下:

此时我们只需要比较参考点与当前纹素的距离,获取距离较近的参考点的颜色值即可。

片元着色器实现:

precision highp float;
uniform sampler2D Texture;
varying vec2 TextureCoordsVarying;
//六边形的边⻓ 
const float mosaicSize = 0.03;

void main (void) {
    float length = mosaicSize;
    //sqrt(3)/2;
    float TR = 0.866025;
    float TB = 1.5;

    //纹理坐标:(0,0)(0,1)(1,0)(1,1)     
    float x = TextureCoordsVarying.x;
    float y = TextureCoordsVarying.y;

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

    vec2 v1, v2, vn;  
    //判断wx,wy在矩形中的上半部还是下半部 
    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));
        }
    }

    // 计算参考点与当前纹素的距离 
    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中 
    gl_FragColor = color;
}

3.三角形马赛克

实现原理:三角形马赛克与六边形马赛克的原理比较接近,计算纹理坐标在所对应的矩阵坐标过程是相同的,唯一的区别在于最终获取纹素的时候是将六边形分割成了6个三角形区域,判断当前点落在6个区域中的哪个区域中,拿到最终的颜色值。

片元着色器实现:

precision highp float;
uniform sampler2D Texture;
varying vec2 TextureCoordsVarying;
//⻢赛克的边⻓ float mosaicSize = 0.03;

void main (void){
    //TR其实是√3/2 
    const float TR = 0.866025;
    //π/6 = 30.0 
    const float PI6 = 0.523599;

    //纹理坐标:(0,0)(0,1)(1,0)(1,1) 
    float x = TextureCoordsVarying.x;
    float y = TextureCoordsVarying.y;

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

    vec2 v1, v2, vn;
    //判断wx,wy在矩形中的上半部还是下半部 
    if (wx / 2 * 2 == wx) {
        if (wy/2 * 2 == wy) {
            v1 = vec2(mosaicSize * 1.5 * float(wx), mosaicSize * TR * float(wy));
            v2 = vec2(mosaicSize * 1.5 * float(wx + 1), mosaicSize * TR * float(wy + 1));
        } else {
            v1 = vec2(mosaicSize * 1.5 * float(wx), mosaicSize * TR * float(wy + 1));
            v2 = vec2(mosaicSize * 1.5 * float(wx + 1), mosaicSize * TR * float(wy));
        }
    } else {
        if (wy/2 * 2 == wy) {
            v1 = vec2(mosaicSize * 1.5 * float(wx), mosaicSize * TR * float(wy + 1));
            v2 = vec2(mosaicSize * 1.5 * float(wx+1), mosaicSize * TR * float(wy));
        } else {
            v1 = vec2(mosaicSize * 1.5 * float(wx), mosaicSize * TR * float(wy));
            v2 = vec2(mosaicSize * 1.5 * float(wx + 1), mosaicSize * 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;
    }

    //获取a与纹理中⼼的⻆度.     
    //atan算出的范围是-180⾄180度,对应的数值是-PI⾄PI     
    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);

    //判断夹⻆a 属于哪个三⻆形.则获取哪个三⻆形的中⼼点坐标 
    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 
    gl_FragColor = color;
}