滤镜基本是相机或者图像处理软件中的标配功能,它能对图像实现各种特殊效果,比如iPhone中的滤镜功能:
滤镜的实现主要分为两大类:
基于颜色矩阵的实现,需要对每个像素的各个分量做变换操作,在RGB色彩空间中,各个分量的取值区间为[0, 255],可以想到在计算过程中我们必将做不少重复性的计算工作。这种重复性计算在移动端或嵌入式等资源紧张的设备中是不可取的
通过缓存已经做过的颜色变换来实现优化,我们可以在运行时动态的进行缓存,也可以提前建立好映射关系
颜色LUT(LookupTable)就是这么一种提前做好映射关系的优化手段
颜色LUT的核心步骤:
LUT中不需要改变的颜色映射出来的还是原来的颜色,可以根据需求做不同的映射关系
一张宽高为512x512,像素64*64*64的LUT如下:
PS: 这张颜色查找表是不进行颜色变换的,即f(x) = x
整张图从左到右和从上到下为蓝色的渐变,每个小格子从左到右为红色渐变,从上到下为绿色渐变
64只是一种颜色粒度的划分,可以是其它数值,具体的看设计师给出查找表是怎么规划的,此处的粒度为64,也就是将RGB的分量划分为64份,颜色[0.0, 1.0] -> [0.0, 63.0]
以一个具体的颜色Color(r = 30.0, g = 30.0, b = 25.4)为例子来说明是如何通过LUT来做映射的。
蓝色值用来定位两个相邻的小格子
第一个小格子:
float blueColor = textureColor.b * 63.0;
vec2 quad1;
// blueColor = 25.4, 第3行的第1个小格子
// floor:向下取整
quad1.y = floor(floor(blueColor) / 8.0);
quad1.x = floor(blueColor) - (quad1.y * 8.0);
第二个小格子:
vec2 quad2;
// blueColor = 25.4,第3行的第2个小格子
// ceil:向上取整
quad2.y = floor(ceil(blueColor) / 8.0);
quad2.x = ceil(blueColor) - (quad2.y * 8.0);
红色值和绿色值用来确定相对于整个LUT的纹理坐标
vec2 texPos1;
texPos1.x = (quad1.x * 1.0 / 8.0) + (63.0 / 512.0) * textureColor.r);
texPos1.y = (quad1.y * 1.0 / 8.0) + (63.0 / 512.0) * textureColor.g);
vec2 texPos2;
texPos2.x = (quad2.x * 1.0 / 8.0) + (63.0 / 512.0) * textureColor.r);
texPos2.y = (quad2.y * 1.0 / 8.0) + (63.0 / 512.0) * textureColor.g);
通过纹理坐标获取两个新的颜色
vec4 newColor1 = texture2D(u_LookupTable, texPos1);
vec4 newColor2 = texture2D(u_LookupTable, texPos2);
然后根据蓝色值小数部分作为权重做线性混合,获取最终的颜色输出
// mix(x, y, a) -> x * (1 - a) + y * a
vec4 newColor = mix(newColor1, newColor2, fract(blueColor));
完整的顶点着色器
attribute vec4 a_Position;
attribute vec4 a_TextureCoordinate;
varying vec2 vTextureUnitCoordinate;
void main() {
gl_Position = a_Position;
vTextureUnitCoordinate = a_TextureCoordinate.xy;
}
片元着色器代码
precision mediump float;
uniform sampler2D u_TextureSampler;
uniform sampler2D u_LookupTable;
uniform float u_Intensity;
varying vec2 vTextureUnitCoordinate;
void main() {
vec4 textureColor = texture2D(u_TextureSampler, vTextureUnitCoordinate);
float blueColor = textureColor.b * 63.0;
vec2 quad1;
quad1.y = floor(floor(blueColor) / 8.0);
quad1.x = floor(blueColor) - (quad1.y * 8.0);
vec2 quad2;
quad2.y = floor(ceil(blueColor) / 8.0);
quad2.x = ceil(blueColor) - (quad2.y * 8.0);
vec2 texPos1;
texPos1.x = (quad1.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r);
texPos1.y = (quad1.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g);
vec2 texPos2;
texPos2.x = (quad2.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r);
texPos2.y = (quad2.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g);
vec4 newColor1 = texture2D(u_LookupTable, texPos1);
vec4 newColor2 = texture2D(u_LookupTable, texPos2);
vec4 newColor = mix(newColor1, newColor2, fract(blueColor));
gl_FragColor = mix(textureColor, vec4(newColor.rgb, textureColor.w), u_Intensity);
}
使用不同的LUT即可实现不同的滤镜效果