image.png

前情提要

这篇滤镜效果的实现是在上一篇 分屏滤镜 的基础上来进行实现的,同样的前提是可以利用GLSL加载一张正常的图片。
image.png

下面步入这篇的正题:

灰度滤镜

一张图片的显示是由三个颜色通道(RGB)来决定的,所以图片也称为三通道图。

三通道图:图片每个像素点都有三个值表示 ,所以就是三通道。也有四通道的图。例如RGB图片即为三通道图片,RGB色彩模式是工业界的一种颜色标准,是通过对红(R)、绿(G)、蓝(B)三个颜色通道的变化以及它们相互之间的叠加来得到各式各样的颜色的,RGB即是代表红、绿、蓝三个通道的颜色,这个标准几乎包括了人类视力所能感知的所有颜色,是目前运用最广的颜色系统之一。总之,每一个点由三个值表示。

理解了三通道图的概念,那么灰度滤镜其实就是只有一个通道有值,也就是只要得到图片的亮度即可,其实这也就是单通道图。

单通道图:俗称灰度图,每个像素点只能有有一个值表示颜色,它的像素值在0到255之间,0是黑色,255是白色,中间值是一些不同等级的灰色。(也有3通道的灰度图,3通道灰度图只有一个通道有值,其他两个通道的值都是零)。

有5中方法来实现灰度滤镜的算法(前三种方法是利用权重来实现的):

  • 浮点算法: Gray = R 0.3 + G 0.59 + B * 0.11 (根据对应纹素的颜色值调整RGB的比例)
  • 整数算法: Gray = (R 30 + G 59 + B * 11) / 100 (同浮点算法)
  • 移位算法: Gray = (R 76 + G 151 + B * 28) >> 8
  • 平均值法: Gray = (R + G + B) / 3; (获取到对应纹素的RGB平均值,填充到三个通道上面)
  • 仅取绿色: Gray = G (一个颜色填充三个通道)

同样的,灰度滤镜只需要更改片元着色器的代码即可:
片元着色器代码

  1. precision highp float;
  2. uniform sampler2D Texture;
  3. varying vec2 TextureCoordsVarying;
  4. const highp vec3 W = vec3(0.2125, 0.7154, 0.0721);
  5. void main (void) {
  6. vec4 mask = texture2D(Texture, TextureCoordsVarying);
  7. float luminance = dot(mask.rgb, W);
  8. gl_FragColor = vec4(vec3(luminance), 1.0);
  9. }

实现效果
image.png

颠倒滤镜

其实对于颠倒滤镜,既可以在顶点着色器中修改,也可以在片元着色器中修改,但是在顶点着色器修改的好处是只需要颠倒顶点,计算量相比较少。

首先来修改顶点着色器的代码看下
顶点着色器代码

  1. attribute vec4 Position;
  2. attribute vec2 TextureCoords;
  3. varying vec2 TextureCoordsVarying;
  4. void main (void) {
  5. gl_Position = vec4(Position.x, - Position.y, 0.0, 1.0);
  6. TextureCoordsVarying = TextureCoords;
  7. }

或者修改片元着色器的代码
片元着色器代码

  1. precision highp float;
  2. uniform sampler2D Texture;
  3. varying vec2 TextureCoordsVarying;
  4. void main (void) {
  5. vec4 mask = texture2D(Texture, vec2(TextureCoordsVarying.x, 1.0 - TextureCoordsVarying.y));
  6. gl_FragColor = vec4(mask.rgb, 1.0);
  7. }

实现效果:
image.png

这里有个问题需要注意一下,在GLSL渲染图片时,原本图片就是倒置的,只不过在获取纹理的代码中,将图片进行了翻转。

  1. CGContextRef context = CGBitmapContextCreate(imageData, width, height, 8, width * 4, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
  2. //将图片翻转过来(图片默认是倒置的)
  3. CGContextTranslateCTM(context, 0, height);
  4. CGContextScaleCTM(context, 1.0f, -1.0f);
  5. CGColorSpaceRelease(colorSpace);
  6. CGContextClearRect(context, rect);

可能会有同学觉得,只要在此处不把图片翻转,就一样可以实现颠倒滤镜,实际上此法并不可取,我们在此处说的颠倒滤镜只是一种滤镜效果,当切换到别的滤镜就是另外一种效果了,如果不解决图片的翻转问题,就会导致别的滤镜效果是倒置的了。(个人理解,还请指教)。

马赛克滤镜

⻢赛克效果就是把图片的⼀个相当⼤小的区域⽤同一个点的颜色来表示.可以认为是大规模的降低图像的分辨率,而让图像的一些细节隐藏起来。
image.png
也就是说,根据马赛克单元格的宽高计算出图像总的马赛克行数和列数,然后将每个马赛克单元格遍历2次,第一次计算该单元格RGB的平均值,第二次遍历赋颜色值。

片元着色器代码

  1. precision mediump float;
  2. //纹理坐标
  3. varying vec2 TextureCoordsVarying;
  4. //纹理采样器
  5. uniform sampler2D Texture;
  6. //纹理图片size
  7. const vec2 TexSize = vec2(600.0, 600.0);
  8. //马赛克size
  9. const vec2 mosaicSize = vec2(16.0, 16.0);
  10. void main()
  11. {
  12. //计算图像的实际位置
  13. vec2 intXY = vec2(TextureCoordsVarying.x*TexSize.x, TextureCoordsVarying.y*TexSize.y);
  14. // floor (x) 内建函数,返回小于/等于X的最大整数值.
  15. // floor (intXY.x / mosaicSize.x) * mosaicSize.x 计算出一个⼩小⻢赛克的坐标.
  16. vec2 XYMosaic = vec2(floor(intXY.x/mosaicSize.x)*mosaicSize.x, floor(intXY.y/mosaicSize.y)*mosaicSize.y);
  17. //换算回纹理坐标
  18. vec2 UVMosaic = vec2(XYMosaic.x/TexSize.x, XYMosaic.y/TexSize.y);
  19. //获取到马赛克后的纹理坐标的颜色值
  20. vec4 color = texture2D(Texture, UVMosaic);
  21. //将⻢赛克颜色值赋值给gl_FragColor.
  22. gl_FragColor = color;
  23. }

实现效果:
image.png

六边形马赛克

首先看看六边形马赛克的结构,如下图:
image.png
下面先来看下片元着色器代码和实现效果,之后再来研究下原理
片元着色器代码

  1. precision highp float;
  2. uniform sampler2D Texture;
  3. varying vec2 TextureCoordsVarying;
  4. const float mosaicSize = 0.03;
  5. void main (void)
  6. {
  7. float length = mosaicSize;
  8. float TR = 0.866025;
  9. float x = TextureCoordsVarying.x;
  10. float y = TextureCoordsVarying.y;
  11. int wx = int(x / 1.5 / length);
  12. int wy = int(y / TR / length);
  13. vec2 v1, v2, vn;
  14. if (wx/2 * 2 == wx) {
  15. if (wy/2 * 2 == wy) {
  16. //(0,0),(1,1)
  17. v1 = vec2(length * 1.5 * float(wx), length * TR * float(wy));
  18. v2 = vec2(length * 1.5 * float(wx + 1), length * TR * float(wy + 1));
  19. } else {
  20. //(0,1),(1,0)
  21. v1 = vec2(length * 1.5 * float(wx), length * TR * float(wy + 1));
  22. v2 = vec2(length * 1.5 * float(wx + 1), length * TR * float(wy));
  23. }
  24. }else {
  25. if (wy/2 * 2 == wy) {
  26. //(0,1),(1,0)
  27. v1 = vec2(length * 1.5 * float(wx), length * TR * float(wy + 1));
  28. v2 = vec2(length * 1.5 * float(wx + 1), length * TR * float(wy));
  29. } else {
  30. //(0,0),(1,1)
  31. v1 = vec2(length * 1.5 * float(wx), length * TR * float(wy));
  32. v2 = vec2(length * 1.5 * float(wx + 1), length * TR * float(wy + 1));
  33. }
  34. }
  35. float s1 = sqrt(pow(v1.x - x, 2.0) + pow(v1.y - y, 2.0));
  36. float s2 = sqrt(pow(v2.x - x, 2.0) + pow(v2.y - y, 2.0));
  37. if (s1 < s2) {
  38. vn = v1;
  39. } else {
  40. vn = v2;
  41. }
  42. vec4 color = texture2D(Texture, vn);
  43. gl_FragColor = color;
  44. }

实现效果:
image.png

思路: 我们要做的效果就是让⼀张图片,分割成由多个六边形组成,让每 个六边形中的颜色相同(直接取六边形中心点纹素RGB比较⽅便)

image.png

如上图,画出很多长和宽比例为 3:√3 的的矩形阵。然后我们可以 对每个点进行编号,如上图中,采⽤坐标系标记.
假如我们的屏幕的左上点为上图的==(0,0)点,则屏幕上的任一点我 们找到它所对应的那个矩形。
假定我们设定的矩阵比例为 3LEN : √3_LEN ,那么屏幕上的任意 点(x, y)所对应的矩阵坐标为(int(x/(3_LEN)), int(y/ (√3_LEN)))。那么(wx, wy)== 表示纹理坐标在所对应的矩阵坐标为:
wx = int(x/(1.5
length)); wy = int(y/(TR _ length))

先来看下其中一个六边形:
image.png
image.png
虽然一个六边形周围有9个点,但是是中心点的只有5个(图中绿色的点:当前这个六边形的中心点和周边四个六边形的中心点)。
这么来看可以把一个六边形分割成四块区域,两种类型:

左上点右下点为中心点(绿色点)
image.png
左下点和右上点为中心点(绿色点)
image.png
任一块区域四个点的计算公式如下 :
image.png
那么如果取任意一个点(下图中红色点),这个点的颜色值是由距离它最近的六边形的中心点决定的,距离这个点最近的有两个中心点(左上点右下点/左下点右上点),所以首先要算出这个红点到哪个中心点距离最近
image.png
那么该怎么判断这个红点距离较近的中心点是哪两个中心点呢?再来看一下上面的分割成六边形.png,在划分六边形的时候已经对分割的区域进行了行列标号,

  • 偶数行偶数列计算左上中心点和右下中心点
  • 偶数行奇数列计算左下中心点和右上中心点
  • 奇数行偶数列计算左下中心点和右上中心点
  • 奇数行奇数列计算左上中心点和右下中心点

计算出V1,V2(片元着色器代码中定义的)两点的坐标之后,再把当前坐标Vn(片元着色器代码中定义的)分别求出距离V1,V2的距离,然后判断这两个距离S1,S2的大小,然后获取距离小的中心点的颜色值,进行颜色赋值。
(六边形马赛克的理解大概就是这些了,如有错误,还请指正)

三角形马赛克

关于三角形马赛克首先可以肯定的是马赛克是等边三角形,这样才能真正的无缝拼接。
如果有理解了上面的六边形马赛克的原理,那么三角形马赛克就很好理解了,观察发现,一个六边形正好可以用六个三角形拼凑而成。

image.png

如果任意取一点(下图中红色的大点),该点颜色值就取它所在的三角形的中心点的颜色值,要判断一个点属于哪个三角形,必须先判断它属于那个六边形,这个在之前的六边形马赛克中已经提到了。
image.png

知道了该点在哪个六边形之后,也就知道了该点的坐标,然后根据该点和六边形中心点的夹角范围是不是就知道了这个点位于哪个三角形内了。
夹角的计算 float θ = atan((x-O.x)/(y-O.y));这里注意一下atan算出的范围是==-180度至180度,对应的数值是-PI至PI==。
根据这个角度就能知道这个点位于这六个三角形中的哪一个三角形了(在上图中已经对这六个三角形进行了标记划分)。然后再计算这六个三角形各自的中心点坐标(上图中的小红点),任取的点属于哪个三角形就取该三角形中心点的颜色值。最后再进行颜色赋值。

片元着色器代码

  1. precision highp float;
  2. uniform sampler2D Texture;
  3. varying vec2 TextureCoordsVarying;
  4. float mosaicSize = 0.03;
  5. void main (void){
  6. const float TR = 0.866025;
  7. const float PI6 = 0.523599;
  8. float x = TextureCoordsVarying.x;
  9. float y = TextureCoordsVarying.y;
  10. int wx = int(x/(1.5 * mosaicSize));
  11. int wy = int(y/(TR * mosaicSize));
  12. vec2 v1, v2, vn;
  13. if (wx / 2 * 2 == wx) {
  14. if (wy/2 * 2 == wy) {
  15. v1 = vec2(mosaicSize * 1.5 * float(wx), mosaicSize * TR * float(wy));
  16. v2 = vec2(mosaicSize * 1.5 * float(wx + 1), mosaicSize * TR * float(wy + 1));
  17. } else {
  18. v1 = vec2(mosaicSize * 1.5 * float(wx), mosaicSize * TR * float(wy + 1));
  19. v2 = vec2(mosaicSize * 1.5 * float(wx + 1), mosaicSize * TR * float(wy));
  20. }
  21. } else {
  22. if (wy/2 * 2 == wy) {
  23. v1 = vec2(mosaicSize * 1.5 * float(wx), mosaicSize * TR * float(wy + 1));
  24. v2 = vec2(mosaicSize * 1.5 * float(wx+1), mosaicSize * TR * float(wy));
  25. } else {
  26. v1 = vec2(mosaicSize * 1.5 * float(wx), mosaicSize * TR * float(wy));
  27. v2 = vec2(mosaicSize * 1.5 * float(wx + 1), mosaicSize * TR * float(wy+1));
  28. }
  29. }
  30. float s1 = sqrt(pow(v1.x - x, 2.0) + pow(v1.y - y, 2.0));
  31. float s2 = sqrt(pow(v2.x - x, 2.0) + pow(v2.y - y, 2.0));
  32. if (s1 < s2) {
  33. vn = v1;
  34. } else {
  35. vn = v2;
  36. }
  37. vec4 mid = texture2D(Texture, vn);
  38. float a = atan((x - vn.x)/(y - vn.y));
  39. vec2 area1 = vec2(vn.x, vn.y - mosaicSize * TR / 2.0);
  40. vec2 area2 = vec2(vn.x + mosaicSize / 2.0, vn.y - mosaicSize * TR / 2.0);
  41. vec2 area3 = vec2(vn.x + mosaicSize / 2.0, vn.y + mosaicSize * TR / 2.0);
  42. vec2 area4 = vec2(vn.x, vn.y + mosaicSize * TR / 2.0);
  43. vec2 area5 = vec2(vn.x - mosaicSize / 2.0, vn.y + mosaicSize * TR / 2.0);
  44. vec2 area6 = vec2(vn.x - mosaicSize / 2.0, vn.y - mosaicSize * TR / 2.0);
  45. if (a >= PI6 && a < PI6 * 3.0) {
  46. vn = area1;
  47. } else if (a >= PI6 * 3.0 && a < PI6 * 5.0) {
  48. vn = area2;
  49. } else if ((a >= PI6 * 5.0 && a <= PI6 * 6.0) || (a < -PI6 * 5.0 && a > -PI6 * 6.0)) {
  50. vn = area3;
  51. } else if (a < -PI6 * 3.0 && a >= -PI6 * 5.0) {
  52. vn = area4;
  53. } else if(a <= -PI6 && a> -PI6 * 3.0) {
  54. vn = area5;
  55. } else if (a > -PI6 && a < PI6) {
  56. vn = area6;
  57. }
  58. vec4 color = texture2D(Texture, vn);
  59. gl_FragColor = color;
  60. }

这里,TR其实是√3/2,而PI6明显就是PI/6,对应的是30度。

实现效果:
image.png