天天看点

OpenGLSL初探(六)使用GLSL实现滤镜之灰度滤镜、正方形马赛克滤镜、六边形马赛克滤镜和三角形马赛克滤镜

此博客只为记录滤镜的算法,所以修改的只是片元着色器代码

注:具体代码请查看上一篇博客:https://blog.csdn.net/weixin_40918107/article/details/107748865

1. 准备工作

  • 创建顶点着色器和片元着色器
  • 加载着色器,并链接
  • 创建图层和上下文
  • 清空缓冲区,并设置渲染缓冲区与帧缓冲区
  • 开始绘制

2.滤镜的实现

图片无滤镜正常显示的顶点着色器和片元着色器代码。

注:以下代码中的中文注释在复制代码时,请删除,否则会有未知的错误

顶点着色器:

//顶点坐标
attribute vec4 Position;
//纹理坐标
attribute vec2 TextureCoords;
//需要传入片元着色器的纹理坐标
varying vec2 TextureCoordsVarying;

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

片元着色器:

//声明高精度float
precision highp float;
//纹理
uniform sampler2D Texture;
//纹理坐标
varying vec2 TextureCoordsVarying;

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

2.1灰度滤镜

灰度滤镜实现的方案:

  • 浮点算法:Gray=R0.3+G0.59+B*0.11
  • 整数⽅法:Gray=(R30+G59+B*11)/100
  • 移位⽅法:Gray =(R76+G151+B*28)>>8;
  • 平均值法:Gray=(R+G+B)/3;
  • 只取绿色:Gray=G;

一般使用第一个方案:浮点算法。

实现灰度滤镜只需要修改片元着色器即可,顶点着色器不变。

precision highp float;
uniform sampler2D Texture;
varying vec2 TextureCoordsVarying;
//变换因子,即上方的浮点算法,此值取自GPUImage源码
const highp vec3 W = vec3(0.2125, 0.7154, 0.0721);

void main (void) {
    //获取对于纹理坐标下的颜⾊值
    vec4 mask = texture2D(Texture, TextureCoordsVarying);
    //将颜⾊mask 与 变换因⼦点乘得到灰度值.由向量转变成标量
    float luminance = dot(mask.rgb, W);
    //vec4、vec3是内建函数,vec3是将颜色值RGB都设置成luminance
    gl_FragColor = vec4(vec3(luminance), 1.0);
}
           

2.2 正方形马赛克滤镜

⻢赛克效果就是把图⽚的⼀个相当⼤⼩的区域⽤同⼀个点的颜⾊来表示.可以认为是⼤规模的降低图像的分辨率,⽽让图像的⼀些细节隐藏起来。其实与临近过滤非常的相似。

马赛克效果是不可逆的,因为这是把原图给修改了。

片元着色器代码:

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的最⼤整数值,向下取整
 // floor (intXY.x / mosaicSize.x) * mosaicSize.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.3六边形马赛克滤镜

六边形马赛克效果就是让⼀张图⽚,分割成由六边形组成,让每

个六边形中的颜⾊相同(直接取六边形中⼼点像素RGB较⽅便,我们

这⾥采⽤的就是这种⽅法),将它进⾏分割,取每个六边形的中⼼点画出⼀个矩阵,如下

OpenGLSL初探(六)使用GLSL实现滤镜之灰度滤镜、正方形马赛克滤镜、六边形马赛克滤镜和三角形马赛克滤镜

图片中的所有的黄点就是六边形的中心点,每个六边形的颜色就取其中心点的颜色。

我们把上图中四个矩形单独拿出进行下一步分析,如下图:

OpenGLSL初探(六)使用GLSL实现滤镜之灰度滤镜、正方形马赛克滤镜、六边形马赛克滤镜和三角形马赛克滤镜

思考:在左上角的长方形中,那个褐色的小点显示的颜色应该显示什么颜色呢?

答案显而易见应该与d2点的颜色一致,这个点距离d2这个中心点比较近,所以是d2六边形上点。

那么在片元着色器上点的颜色的值就应该与该点距离最近的六边形中心点的颜色一致。

在上图中我们还可以分析得到左上角的长方形只有(0,0)和(1,1)这两个点是六边形的中心点,左下角的长方形只有(0,1)和(1,0)这两个点是六边形的中心点。

并且在长方形线的的样式只有“\”和 “/”这两种。

并且由六边形的中心点组成的长方形有一个特点就是宽高比是3:√3,那么假定我们设定的矩阵⽐例为 3LEN : √3LEN ,那么屏幕上的任意点(x, y)所对应的矩阵坐标为(int(x/(3LEN)), int(y/

(√3LEN)))。那么这四个点的坐标为:

OpenGLSL初探(六)使用GLSL实现滤镜之灰度滤镜、正方形马赛克滤镜、六边形马赛克滤镜和三角形马赛克滤镜

片元着色器代码:

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) {//偶数列
            //(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 中
    gl_FragColor = color;
    
}
           

3.4 三角形马赛克滤镜

三角形马赛克滤镜是在六边形滤镜的基础上,将六边形分成6个相同的三角形。

这样我们只需要再进一步的分析,我们需要根据当前点与六边形的中心点之间的角度来确定属于哪个三角,然后再获取三角形的中心点的颜色进行赋值。

OpenGLSL初探(六)使用GLSL实现滤镜之灰度滤镜、正方形马赛克滤镜、六边形马赛克滤镜和三角形马赛克滤镜

片元着色的代码:

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) {//偶数列
            //(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 mid = texture2D(Texture, vn);
    //获取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) {
    //[30,90]
        vn = area1;
    } else if (a >= PI6 * 3.0 && a < PI6 * 5.0) {//[90,150]
        vn = area2;
    } else if ((a >= PI6 * 5.0 && a <= PI6 * 6.0)|| (a<-PI6 * 5.0 && a>-PI6*6.0)) {//[150,180],[-150,-180]
        vn = area3;
    } else if (a < -PI6 * 3.0 && a >= -PI6 * 5.0) {//[-90,-150]
        vn = area4;
    } else if(a <= -PI6 && a> -PI6 * 3.0) {//[-30,-90]
        vn = area5;
    } else if (a > -PI6 && a < PI6)
    {//[-30,30]
        vn = area6;
    }
    //获取对应三⻆形中⼼的颜⾊值
    vec4 color = texture2D(Texture, vn);
    //将颜⾊值填充到⽚元着⾊器内置变量gl_FragColor
    gl_FragColor = color;
    
}
           

继续阅读