前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >滤镜之LUT

滤镜之LUT

作者头像
雪月清
发布2020-06-23 16:18:34
1.8K0
发布2020-06-23 16:18:34
举报
文章被收录于专栏:雪月清的随笔雪月清的随笔

滤镜基本是相机或者图像处理软件中的标配功能,它能对图像实现各种特殊效果,比如iPhone中的滤镜功能:

滤镜的实现主要分为两大类:

  • 基于颜色矩阵,分别对每个像素的RGBA做变换,这种方式是像素独立的。例如我们需要实现一个冷色滤镜的效果,根据人眼对绿色最敏感,红色、蓝色次之和蓝色属于冷色色调的基本原理,可以通过对每个像素的蓝色分量做一定增益来实现;
  • 基于卷积,每个像素的变换会受到周围像素的影响,例如浮雕化,模糊,锐化等效果

基于颜色矩阵的实现,需要对每个像素的各个分量做变换操作,在RGB色彩空间中,各个分量的取值区间为[0, 255],可以想到在计算过程中我们必将做不少重复性的计算工作。这种重复性计算在移动端或嵌入式等资源紧张的设备中是不可取的

通过缓存已经做过的颜色变换来实现优化,我们可以在运行时动态的进行缓存,也可以提前建立好映射关系

颜色LUT(LookupTable)就是这么一种提前做好映射关系的优化手段

颜色LUT的核心步骤:

  1. 通过RGB三个颜色分量,映射为LUT纹理的纹理坐标;
  2. 通过纹理坐标获取到LUT纹理上对应的颜色,将这个颜色重新作为该RGB的颜色输出,这样就达到了一种颜色映射为另外一种颜色

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来做映射的。

蓝色值用来定位两个相邻的小格子

第一个小格子:

代码语言:javascript
复制
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);

第二个小格子:

代码语言:javascript
复制
vec2 quad2;
// blueColor = 25.4,第3行的第2个小格子
// ceil:向上取整
quad2.y = floor(ceil(blueColor) / 8.0);
quad2.x = ceil(blueColor) - (quad2.y * 8.0);

红色值和绿色值用来确定相对于整个LUT的纹理坐标

代码语言:javascript
复制
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);

通过纹理坐标获取两个新的颜色

代码语言:javascript
复制
vec4 newColor1 = texture2D(u_LookupTable, texPos1);
vec4 newColor2 = texture2D(u_LookupTable, texPos2);

然后根据蓝色值小数部分作为权重做线性混合,获取最终的颜色输出

代码语言:javascript
复制
// mix(x, y, a) -> x * (1 - a) + y * a
vec4 newColor = mix(newColor1, newColor2, fract(blueColor));

完整的顶点着色器

代码语言:javascript
复制
attribute vec4 a_Position;
attribute vec4 a_TextureCoordinate;

varying vec2 vTextureUnitCoordinate;

void main() {
    gl_Position = a_Position;
    vTextureUnitCoordinate = a_TextureCoordinate.xy;
}

片元着色器代码

代码语言:javascript
复制
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即可实现不同的滤镜效果

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-04-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 雪月清的随笔 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档