美文网首页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