Metal与图形渲染六:颜色查找表LUT

零. 前言

票圈里总能看到一些很唯美的滤镜,让我惊叹之余又好奇,这个东西是怎么实现的呢,后面经过一番搜索之后,发现了有LUT这个好东西,自己鼓捣了一番,借鉴了别的APP的滤镜,然后得到了下面的效果:

虽然抄过来的滤镜和原滤镜相比还是有很大区别,原滤镜应该还有自己的算法,但这个效果也还算可以啦,来看看怎么弄的吧~

一. 实现原理

LUT的实现原理其实是基于RGB的映射,由原始的RGB映射到结果的RGB中去,而LUT的作用就是这次映射过程中的查找表。

举个例子,比如你参加一场考试,现在给你一张表和三个数字,第一个数字代表哪栋楼,第二个数字代表哪一层,第三个数字代表哪个房间

现在给个数字345,那你在这张表里面就能找到,你要去科研楼12层的15号房间参加考试,这个原理和LUT的原理非常接近,可以这样初步理解一下。

我们知道,RGB是相互独立的三种颜色通道,其取值范围均为[0, 255],如果我们需要建立一个映射表,就可以用一个三维的数组来存储,总共有256 * 256 * 256种情况。

但有个问题,256的三次方,实在太大了,相当于我们需要这么多个像素情况进行一一映射,显然,对于一个APP来说,弄这么大的映射表实在浪费性能。

幸好,LUT通过巧妙的设计,用一张图包含了所有的信息,他是怎么做到的呢,可以看下面这张图:

LUT分割成了8 * 8 = 64个方格,每个方格等分成了64 * 64个像素,如果够细心的话,我们可以看到这里可以用64 * 64 * 64来对应RGB值了,和256 * 256 * 256相比,我们按1:4的值进行了压缩,也就是说,LUT里面的每一个像素跨度了4个值的信息。

如上图所示,LUT里,对于不同的小方格,其R、G的分布规律是相同的,而在同一个小方格中,从左到右包含了64个R值,从上到下包含了64个G值,呈现递增关系。

而对于同一个小方格,其B值是相同的,对于不同方格来说,B值随着方格所在的位置变化而变化,从左到右、从上到下递增。

一句话总结就是:根据B值定位到小方格、根据RG值定位到小方格里面的像素点。

知道了上面的原理之后,我们就可以根据原有的RGB映射到新的RGB了

二. 开始实战

我们来举个例子,我们来在LUT表里面找到(R, G, B) = (255, 255, 255)对应的像素。

1. 归一化

首先,将RGB归一化除以255,得到(1,1,1)。

2. 根据B值定位小方格

由于一共有[0, 63]共64个小方格,我们可以根据B * 63得到对应的小方格的位置,所以我们的小方格是第64个小方格,下标n=63。

我们根据n,定位小方格相对于LUT图的quadX、quadY,可以得到

quadY = floor(n / 8) = 7
quadX = n - quadY * 8 = 7

也就是该小方格处于第7行第7列。(下标从0开始算)

3. 根据RG值定位像素位置

首先定位该像素在小方格里面的相对位置,R决定了x坐标,G决定了y坐标:

stepSize = 0.5 // 由于要取中点,所以得到像素的左上角之后再+0.5

squareX = R * 63 + stepSize = 63.5
squareY = G * 63 + stepSize = 63.5

每个小方格大小为64 * 64,再定位到该像素点的位置:

x = quadX * 64 + squareX = 511.5
y = quadY * 64 + squareY = 511.5

最后归一化,得到该像素点对应的坐标

x = 511.5 / 512
y = 511.5 / 512

三. 更复杂的情况

当然,一个像素点很大概率不是整数,而有可能是小数,那就说一下小数的情况,如(0.6, 0.8, 0.2):

1. 根据B值定位小方格

由于B = 0.2,我们根据n = 63 * 0.2 = 12.6,可以得到两个小方格,n=12、n=13,最终结果由这两个小方格的像素点混合而成

其中:

quad1.y = floor(12.6) / 8 = 1
quad1.x = floor(12.6) - quad1.y * 8 = 4

quad2.y = ceil(12.6) / 8 = 1
quad2.x = ceil(12.6) - quad2.y * 8 = 5

可以看到两个小方格分别分布在第二行(下标为1)的第5个(下标为4)和第6个(下标为5)。

2. 根据RG值定位像素位置

首先定位该像素在小方格里面的相对位置,R决定了x坐标,G决定了y坐标:

stepSize = 0.5 // 由于要取中点,所以得到像素的左上角之后再+0.5

squareX = R * 63 + stepSize = 38.3
squareY = G * 63 + stepSize = 50.9

找到上面两个小方格像素点相对于LUT图的位置:

texPos1.x = (quad1.x * 64 + squareX) / 512 = 294.3 / 512
texPos1.y = (quad1.y * 64 + squareY) / 512 = 306.9 / 512

texPos2.x = (quad2.x * 64 + squareX) / 512 = 294.3 / 512
texPos2.y = (quad2.y * 64 + squareY) / 512 = 370.9 / 512

最后采样、混合即可,值得注意的是,需要根据B值的小数部分决定mix的percent值,小数部分越大,越靠近正方形2

float4 newColor1 = lookupTableTexture.sample(textureSampler, texPos1); // 正方形1的颜色值
float4 newColor2 = lookupTableTexture.sample(textureSampler, texPos2); // 正方形2的颜色值

float4 newColor = mix(newColor1, newColor2, fract(blueColor)); // 根据小数点的部分进行mix

我们还可以根据slideBar值决定最后的颜色更接近哪一边:

return mix(textureColor, float4(newColor.rgb, textureColor.a), intensity);

Shader代码如下:

#include <metal_stdlib>
#include "CCAVPShaderTypes.h"

using namespace metal;

constant float stepSize = 0.5;

fragment float4 lutFragment(SingleInputVertexIO input [[ stage_in ]],
                            texture2d<float> normalTexture [[ texture(0) ]],
                            texture2d<float> lookupTableTexture [[ texture(1) ]],
                            constant float &intensity [[ buffer(0) ]])
{
    constexpr sampler textureSampler (mag_filter::linear,
                                      min_filter::linear);
    float4 textureColor = normalTexture.sample(textureSampler, input.textureCoordinate); //正常的纹理颜色
    
    float blueColor = textureColor.b * 63.0; // 蓝色部分[0, 63] 共64种
    
    float2 quad1; // 第一个正方形的位置, 假如blueColor=22.5,则y=22/8=2,x=22-8*2=6,即是第2行,第6个正方形;(因为y是纵坐标)
    quad1.y = floor(floor(blueColor) * 0.125);
    quad1.x = floor(blueColor) - (quad1.y * 8.0);
    
    float2 quad2; // 第二个正方形的位置,同上。注意x、y坐标的计算,还有这里用int值也可以,但是为了效率使用float
    quad2.y = floor(ceil(blueColor) * 0.125);
    quad2.x = ceil(blueColor) - (quad2.y * 8.0);
    
    // 该像素点相对于小方格的位置(取中间点,所以乘以63再加0.5)
    float squareX = textureColor.r * 63 + stepSize;
    float squareY = textureColor.g * 63 + stepSize;
    
    float2 texPos1; // 正方形1对应像素点相对于LUT图的位置
    texPos1.x = quad1.x * 64 + squareX;
    texPos1.y = quad1.y * 64 + squareY;
    
    float2 texPos2; // 正方形2对应像素点相对于LUT图的位置
    texPos2.x = quad2.x * 64 + squareX;
    texPos2.y = quad2.y * 64 + squareY;
    
    float4 newColor1 = lookupTableTexture.sample(textureSampler, texPos1 / 512); // 正方形1的颜色值
    float4 newColor2 = lookupTableTexture.sample(textureSampler, texPos2 / 512); // 正方形2的颜色值
    
    float4 newColor = mix(newColor1, newColor2, fract(blueColor)); // 根据小数点的部分进行mix
    return mix(textureColor, float4(newColor.rgb, textureColor.a), intensity);
}

四. 怎么用其他软件的滤镜

有个很简单的方法,下图是原始的LUT图,我们把这个图放进其他APP后,可以得到另一个LUT图

比如我找了个油画滤镜,得到了下面的效果:

然后把这个LUT图导入,和原图一起输入,就能得到开头的效果啦~不过和原滤镜的效果还是有比较大的区别,可能他们加了点算法吧= =

五. 参考文章

图像处理之LUT

Metal图像处理——颜色查找表(Color Lookup Table)

一图彻底弄懂LUT

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345

推荐阅读更多精彩内容