好久没有更新了,不行不行,怎么可以太监呢(`⌒´メ)
滤镜结构
滤镜主要是对于图像的处理,关于一款滤镜的制作方法可以看这里
既然是图像处理,那么滤镜的操作就主要是:卷积、像素映射、坐标映射,反映到具体效果上,就是模糊锐化,覆盖层(贴纸等),RGB曲线调整,旋转缩放扭曲之类的。
嗯,就这么简单。
图像处理可以使用CPU来进行,但是由于我们每次只对图像的一小部分进行处理,因此可以考虑用并行的方式进行加速,这是典型的单指令(滤镜)多数据(图像),这个时候GPU就派上用场了,在移动平台上,我们可以使用最通用的OpenGL来利用GPU的计算性能。而我们需要付出的代价就是将之前的图像处理算法使用OpenGL能够理解的方式进行重写,着色器语言(OpenGL Shading Language)就是我们的工具。
让一款滤镜可以使用
我们知道了滤镜是怎么制作的,但是要如何让滤镜可以使用呢?例如实时用这个滤镜处理相机的预览结果并且显示出来。
以Android平台和OpenGL ES2.0+为例,我们可以发现主流滤镜的结构大概是这个样子:
顶点着色器(vertex_shader)
片元着色器(fragment_shader)
颜色映射表/素材纹理(texture)
对应控制代码
一个个来看吧。
顶点着色器(vertex_shader)
顶点着色器在一款滤镜中往往是不变的,一个标准的顶点着色器(图像,2D纹理)长这样:
attribute vec4 aPosition;
attribute vec4 aTextureCoord;
varying vec2 vTextureCoord;
void main() {
gl_Position = aPosition;
vTextureCoord = aTextureCoord.xy;
}
是不是异常简单?aPosition是顶点坐标,aTextureCoord是纹理坐标,vTextureCoord是用来向片元着色器传递纹理坐标用的,片元着色器会根据这个坐标对图片进行取样,然后进行处理,然后我们就完成了图像一个小区域的处理,GPU会自动对于纹理的所有小区域进行处理,完成滤镜的操作。
片元着色器(fragment_shader)
片元着色器是一款滤镜的核心,这其实就是我们的图像处理算法的描述,只不过我们现在用glsl的方式表述出来。
如果我们不需要对于图像进行任何处理,可以这样写:
precision mediump float;
varying vec2 vTextureCoord;
uniform sampler2D sTexture;
void main() {
gl_FragColor=texture2D(sTexture, vTextureCoord);
}
是不是更简单?vTextureCoord是片元着色器传递过来的纹理坐标,sTexture就是我们的源图像了(相机预览、视频播放)。gl_FragColor是OpenGL的内置变量,他是一个vec4类型,代表当前片元的RGBA值,每个元素都是0-1的浮点数。
片元不一定是像素,他可能是相邻的好几个像素的集合,纹理的坐标是浮点数,而片元的中心是两个像素之间的中点。不过如果我们不使用glsl来做通用计算,而只是做图像处理的话,不需要特别在意这一点。
再分析一个高斯模糊的代码:
precision lowp float;
precision lowp int;
varying vec2 vTextureCoord;
uniform sampler2D sTexture;
varying vec2 blurCoordinates[5];
void main()
{
vec4 original = texture2D(sTexture, vTextureCoord);
lowp vec4 sum = vec4(0.0);
sum += texture2D(sTexture, blurCoordinates[0]) * 0.204164;
sum += texture2D(sTexture, blurCoordinates[1]) * 0.304005;
sum += texture2D(sTexture, blurCoordinates[2]) * 0.304005;
sum += texture2D(sTexture, blurCoordinates[3]) * 0.093913;
sum += texture2D(sTexture, blurCoordinates[4]) * 0.093913;
gl_FragColor =