美文网首页OpenGL
OpenGL ES 马赛克滤镜

OpenGL ES 马赛克滤镜

作者: Maji1 | 来源:发表于2020-08-12 17:04 被阅读0次

这里实现了四边形马赛克、六边形马赛克、三角形马赛克。
这里马赛克效果的代码也都是在片元着色程序.fsh中修改的,只需要处理纹理坐标即可。

  • 四边形马赛克.fsh代码:
precision highp float;
varying vec2 textureCoords;
uniform sampler2D texture;
const vec2 mosaicSize = vec2(0.03, 0.03);

void main()
{
    float mosaicX = floor(textureCoords.x/mosaicSize.x)*mosaicSize.x;
    float mosaicY = floor(textureCoords.y/mosaicSize.y)*mosaicSize.y;
    vec2 mosaicXY = vec2(mosaicX, mosaicY);
    gl_FragColor = texture2D(texture, mosaicXY); 
}

这里mosaicSize代表马赛克大小,我们这里绘制的马赛克是正方形。

floor(x)函数的意思是小于等于x的最大整数。 使用该函数的目的是为了将纹理坐标以0.03的大小为一个单位进行分割,也就是 :
[0.0, 0.03) 范围的坐标全部改为0.0 * 0.03 = 0.0
[0.03, 0.06)范围的坐标全部改为1.0 * 0.03 = 0.03
[0.06, 0.09)范围的坐标全部改为2.0 * 0.03 = 0.06
... ...
[0.96, 0.99)范围的坐标全部改为32.0 * 0.03 = 0.96
[0.99, 1.0)范围的坐标全部改为33.0 * 0.03 = 0.99

纹理坐标范围是[0.0, 1.0],所以每一行每一列能够绘制的马赛克的个数是1.0/0.03个。

四边形马赛克效果图:
  • 六边形马赛克.fsh代码:
precision highp float;
varying vec2 textureCoords;
uniform sampler2D texture;
const float mosaicSize = 0.03;

void main ()
{
    float length = mosaicSize;

    float TR = 0.866025;
    float TB = 1.5;
    
    float x = textureCoords.x;
    float y = textureCoords.y;
    
    int wx = int(x / (TB * 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 * TB * float(wx), length * TR * float(wy));
            v2 = vec2(length * TB * float(wx + 1), length * TR * float(wy + 1));
        } else {//奇数列
            //(0,1),(1,0)
            v1 = vec2(length * TB * float(wx), length * TR * float(wy + 1));
            v2 = vec2(length * TB * float(wx + 1), length * TR * float(wy));
        }
    }else {//奇数行
        if (wy/2 * 2 == wy) {//偶数列
            //(0,1),(1,0)
            v1 = vec2(length * TB * float(wx), length * TR * float(wy + 1));
            v2 = vec2(length * TB * float(wx + 1), length * TR * float(wy));
        } else {//奇数列
            //(0,0),(1,1)
            v1 = vec2(length * TB * float(wx), length * TR * float(wy));
            v2 = vec2(length * TB * 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;
    
}

需要注意,我们这里虽然是六边形马赛克,但是我们的区域划分并不是以六边形区域划分的。而是以四边形的方式划分的,将整个纹理坐标划分成若干个宽和高是特定比例的四边形。

我们这里要求的六边形马赛克是正六边形。下面来计算一下宽和高特定的比例多少,看图: 宽高比例计算图

六边形的边为a、宽w、高h,看图中的计算公式一目了然:w/h = 3/√3
我们设定六边形边长a = 0.03,也就是代码中的mosaicSize,根据上图中的计算公式可以得出:
w = 0.03 * (3/2) = 0.03 * 1.5 = 0.045
h = 0.03 * (√3 / 2) ≈ 0.03 * 0.866025 ≈ 0.02598
代码中的TR 就是上图公式中的 √3 / 2TB 就是上图公式中的 3/2

上图只画了四个四边形,他们中间已经可以构成一个六边形。整个纹理坐标中可以分割成很多这样的四边形。每一行可以分成 1.0/w = 1.0/0.045 个四边形, 每一列可以分成 1.0/h = 1.0/0.02598 个四边形。

了解了如何将纹理坐标分割成一个一个四边形的区域,下面我们需要清楚,如何分割这些四边形的区域,才能使绘制出来的马塞克为六边形。

首先我们需要找出四边形中对应的六边形的中心点,看图:


屏幕快照 2020-08-12 下午3.02.16.png
  • 我们将这里的坐标,称为 四边形的坐标,理解:
    X方向上 是以四边形的宽w的大小为一个单位。
    Y方向上 是以四边形的高h的大小为一个单位。
    所以代码中
    int wx = int(x / (TB * length));:代表 纹理坐标中 X 的值对应在 四边形坐标 中X方向的值。
    int wy = int(y / (TR * length));:代表 纹理坐标中 Y 的值对应在 四边形坐标 中Y方向的值。

  • 图中的蓝点就是我们需要找的六边形的中心点,我们来找下规律:


    屏幕快照 2020-08-12 下午3.24.57.png

00、02、04 四边形:中心点坐标在左上角和右下角。
11、13、15 四边形:中心点坐标也在在左上角和右下角。

01、03、05 四边形:中心点坐标在右上角和左下角。
10、12、14 四边形:中心点坐标也在右上角和左下角。

不难发现:
偶数行偶数列,奇数行奇数列:中心点的坐标全部在左上角和右下角。
奇数行偶数列,偶数行奇数列:中心点的坐标全部在右上角和左下角。

所以上面代码中对行数和列书的逻辑处理就是将 四边形坐标 划分成了四部分,取对应六边形的中心点。再将 四边形坐标值 转回 纹理坐标值,也就是代码中的vec2 v1, v2

接下来我们需要判断纹理的像素点距离v1, v2的距离大小,代码:

    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;
    }

如果距离v1近就取v1的值,距离v2近就取v2的值赋值给vn

逻辑并不复杂,到这已经结束了。核心思想就是取出六边形的中心点,拿纹理坐标跟两个中心点的距离做比较。
下面看下效果:

六边形马赛克
  • 三角形形马赛克.fsh代码,在六边形代码的基础上添加如下代码:
    vec4 mid = texture2D(texture, vn);
    float a = atan((x - vn.x)/(y - vn.y));
    
    vec2 area1 = vec2(vn.x, vn.y - length * TR / 2.0);
    vec2 area2 = vec2(vn.x + length / 2.0, vn.y - length * TR / 2.0);
    vec2 area3 = vec2(vn.x + length / 2.0, vn.y + length * TR / 2.0);
    vec2 area4 = vec2(vn.x, vn.y + length * TR / 2.0);
    vec2 area5 = vec2(vn.x - length / 2.0, vn.y + length * TR / 2.0);
    vec2 area6 = vec2(vn.x - length / 2.0, vn.y - length * TR / 2.0);
    
    if (a >= PI6 && a < PI6 * 3.0) {//30-90
        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;
    }

我们在六边形的基础上再对区域进行划分,每个六边形包含六个三角形。


  • float a = atan((x - vn.x)/(y - vn.y));:求出纹理坐标相对于中心点vn的弧度值。
  • 求出六个三角形的中心点坐标area1、area2、area3、area4、area5、area6
    这里的计算非常简单,结合 宽高比例计算图 很容易就明白了。
  • const float PI6 = 0.523599; 是30度的弧度值。
    所以每个区域的弧度范围值是:
    area1:[30°, 90°)
    area2:[90°, 150°)
    area3:[150°, 180°]、(-180°,-150°)
    area4:[ -150°, -90°)
    area5:( -90°, -30°]
    area6:( -30°, 30°)

效果图:

三角形马赛克

相关文章

网友评论

    本文标题:OpenGL ES 马赛克滤镜

    本文链接:https://www.haomeiwen.com/subject/ifyudktx.html