Metal與圖形渲染六:顏色查找表LUT

零. 前言

票圈里總能看到一些很唯美的濾鏡焕盟,讓我驚嘆之余又好奇,這個(gè)東西是怎么實(shí)現(xiàn)的呢宏粤,后面經(jīng)過一番搜索之后脚翘,發(fā)現(xiàn)了有LUT這個(gè)好東西,自己鼓搗了一番绍哎,借鑒了別的APP的濾鏡来农,然后得到了下面的效果:

雖然抄過來的濾鏡和原濾鏡相比還是有很大區(qū)別,原濾鏡應(yīng)該還有自己的算法崇堰,但這個(gè)效果也還算可以啦沃于,來看看怎么弄的吧~

一. 實(shí)現(xiàn)原理

LUT的實(shí)現(xiàn)原理其實(shí)是基于RGB的映射,由原始的RGB映射到結(jié)果的RGB中去海诲,而LUT的作用就是這次映射過程中的查找表繁莹。

舉個(gè)例子,比如你參加一場考試饿肺,現(xiàn)在給你一張表和三個(gè)數(shù)字蒋困,第一個(gè)數(shù)字代表哪棟樓盾似,第二個(gè)數(shù)字代表哪一層敬辣,第三個(gè)數(shù)字代表哪個(gè)房間

現(xiàn)在給個(gè)數(shù)字345雪标,那你在這張表里面就能找到,你要去科研樓12層的15號(hào)房間參加考試溉跃,這個(gè)原理和LUT的原理非常接近村刨,可以這樣初步理解一下。

我們知道撰茎,RGB是相互獨(dú)立的三種顏色通道嵌牺,其取值范圍均為[0, 255],如果我們需要建立一個(gè)映射表龄糊,就可以用一個(gè)三維的數(shù)組來存儲(chǔ)逆粹,總共有256 * 256 * 256種情況。

但有個(gè)問題炫惩,256的三次方僻弹,實(shí)在太大了,相當(dāng)于我們需要這么多個(gè)像素情況進(jìn)行一一映射他嚷,顯然蹋绽,對于一個(gè)APP來說,弄這么大的映射表實(shí)在浪費(fèi)性能筋蓖。

幸好卸耘,LUT通過巧妙的設(shè)計(jì),用一張圖包含了所有的信息粘咖,他是怎么做到的呢蚣抗,可以看下面這張圖:

LUT分割成了8 * 8 = 64個(gè)方格,每個(gè)方格等分成了64 * 64個(gè)像素涂炎,如果夠細(xì)心的話忠聚,我們可以看到這里可以用64 * 64 * 64來對應(yīng)RGB值了,和256 * 256 * 256相比唱捣,我們按1:4的值進(jìn)行了壓縮两蟀,也就是說,LUT里面的每一個(gè)像素跨度了4個(gè)值的信息震缭。

如上圖所示赂毯,LUT里,對于不同的小方格拣宰,其R党涕、G的分布規(guī)律是相同的,而在同一個(gè)小方格中巡社,從左到右包含了64個(gè)R值膛堤,從上到下包含了64個(gè)G值,呈現(xiàn)遞增關(guān)系晌该。

而對于同一個(gè)小方格肥荔,其B值是相同的绿渣,對于不同方格來說,B值隨著方格所在的位置變化而變化燕耿,從左到右中符、從上到下遞增。

一句話總結(jié)就是:根據(jù)B值定位到小方格誉帅、根據(jù)RG值定位到小方格里面的像素點(diǎn)淀散。

知道了上面的原理之后,我們就可以根據(jù)原有的RGB映射到新的RGB了

二. 開始實(shí)戰(zhàn)

我們來舉個(gè)例子蚜锨,我們來在LUT表里面找到(R, G, B) = (255, 255, 255)對應(yīng)的像素档插。

1. 歸一化

首先,將RGB歸一化除以255亚再,得到(1,1,1)阀捅。

2. 根據(jù)B值定位小方格

由于一共有[0, 63]共64個(gè)小方格,我們可以根據(jù)B * 63得到對應(yīng)的小方格的位置针余,所以我們的小方格是第64個(gè)小方格饲鄙,下標(biāo)n=63。

我們根據(jù)n圆雁,定位小方格相對于LUT圖的quadX忍级、quadY,可以得到

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

也就是該小方格處于第7行第7列伪朽。(下標(biāo)從0開始算)

3. 根據(jù)RG值定位像素位置

首先定位該像素在小方格里面的相對位置轴咱,R決定了x坐標(biāo),G決定了y坐標(biāo):

stepSize = 0.5 // 由于要取中點(diǎn)烈涮,所以得到像素的左上角之后再+0.5

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

每個(gè)小方格大小為64 * 64朴肺,再定位到該像素點(diǎn)的位置:

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

最后歸一化,得到該像素點(diǎn)對應(yīng)的坐標(biāo)

x = 511.5 / 512
y = 511.5 / 512

三. 更復(fù)雜的情況

當(dāng)然坚洽,一個(gè)像素點(diǎn)很大概率不是整數(shù)戈稿,而有可能是小數(shù),那就說一下小數(shù)的情況讶舰,如(0.6, 0.8, 0.2):

1. 根據(jù)B值定位小方格

由于B = 0.2鞍盗,我們根據(jù)n = 63 * 0.2 = 12.6,可以得到兩個(gè)小方格跳昼,n=12般甲、n=13,最終結(jié)果由這兩個(gè)小方格的像素點(diǎn)混合而成

其中:

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

可以看到兩個(gè)小方格分別分布在第二行(下標(biāo)為1)的第5個(gè)(下標(biāo)為4)和第6個(gè)(下標(biāo)為5)鹅颊。

2. 根據(jù)RG值定位像素位置

首先定位該像素在小方格里面的相對位置敷存,R決定了x坐標(biāo),G決定了y坐標(biāo):

stepSize = 0.5 // 由于要取中點(diǎn)堪伍,所以得到像素的左上角之后再+0.5

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

找到上面兩個(gè)小方格像素點(diǎn)相對于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

最后采樣锚烦、混合即可滔岳,值得注意的是,需要根據(jù)B值的小數(shù)部分決定mix的percent值挽牢,小數(shù)部分越大,越靠近正方形2

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

float4 newColor = mix(newColor1, newColor2, fract(blueColor)); // 根據(jù)小數(shù)點(diǎn)的部分進(jìn)行mix

我們還可以根據(jù)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; // 藍(lán)色部分[0, 63] 共64種
    
    float2 quad1; // 第一個(gè)正方形的位置, 假如blueColor=22.5摊求,則y=22/8=2禽拔,x=22-8*2=6,即是第2行室叉,第6個(gè)正方形睹栖;(因?yàn)閥是縱坐標(biāo))
    quad1.y = floor(floor(blueColor) * 0.125);
    quad1.x = floor(blueColor) - (quad1.y * 8.0);
    
    float2 quad2; // 第二個(gè)正方形的位置,同上茧痕。注意x野来、y坐標(biāo)的計(jì)算,還有這里用int值也可以踪旷,但是為了效率使用float
    quad2.y = floor(ceil(blueColor) * 0.125);
    quad2.x = ceil(blueColor) - (quad2.y * 8.0);
    
    // 該像素點(diǎn)相對于小方格的位置(取中間點(diǎn)曼氛,所以乘以63再加0.5)
    float squareX = textureColor.r * 63 + stepSize;
    float squareY = textureColor.g * 63 + stepSize;
    
    float2 texPos1; // 正方形1對應(yīng)像素點(diǎn)相對于LUT圖的位置
    texPos1.x = quad1.x * 64 + squareX;
    texPos1.y = quad1.y * 64 + squareY;
    
    float2 texPos2; // 正方形2對應(yīng)像素點(diǎn)相對于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)); // 根據(jù)小數(shù)點(diǎn)的部分進(jìn)行mix
    return mix(textureColor, float4(newColor.rgb, textureColor.a), intensity);
}

四. 怎么用其他軟件的濾鏡

有個(gè)很簡單的方法,下圖是原始的LUT圖令野,我們把這個(gè)圖放進(jìn)其他APP后舀患,可以得到另一個(gè)LUT圖

比如我找了個(gè)油畫濾鏡聊浅,得到了下面的效果:

然后把這個(gè)LUT圖導(dǎo)入,和原圖一起輸入顽冶,就能得到開頭的效果啦~不過和原濾鏡的效果還是有比較大的區(qū)別,可能他們加了點(diǎn)算法吧= =

五. 參考文章

圖像處理之LUT

Metal圖像處理——顏色查找表(Color Lookup Table)

一圖徹底弄懂LUT

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市拱燃,隨后出現(xiàn)的幾起案子碗誉,更是在濱河造成了極大的恐慌,老刑警劉巖弄跌,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件糠溜,死亡現(xiàn)場離奇詭異非竿,居然都是意外死亡红柱,警方通過查閱死者的電腦和手機(jī)锤悄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進(jìn)店門宽闲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人览徒,你說我怎么就攤上這事习蓬《愕穑” “怎么了枫慷?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵探孝,是天一觀的道長顿颅。 經(jīng)常有香客問我粱腻,道長,這世上最難降的妖魔是什么滔驶? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上吏口,老公的妹妹穿的比我還像新娘产徊。我一直安慰自己,他們只是感情好谆刨,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著生兆,像睡著了一般鸦难。 火紅的嫁衣襯著肌膚如雪击敌。 梳的紋絲不亂的頭發(fā)上沃斤,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天牲证,我揣著相機(jī)與錄音十厢,去河邊找鬼。 笑死包颁,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的待锈。 我是一名探鬼主播,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼套啤,長吁一口氣:“原來是場噩夢啊……” “哼萄涯!你這毒婦竟也來了枣察?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤叛赚,失蹤者是張志新(化名)和其女友劉穎英古,沒想到半個(gè)月后淀衣,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡召调,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年膨桥,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片唠叛。...
    茶點(diǎn)故事閱讀 39,834評論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡只嚣,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出艺沼,到底是詐尸還是另有隱情册舞,我是刑警寧澤,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布障般,位于F島的核電站调鲸,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏挽荡。R本人自食惡果不足惜藐石,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望定拟。 院中可真熱鬧于微,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至勺三,卻和暖如春雷滚,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背吗坚。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工祈远, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人商源。 一個(gè)月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓车份,卻偏偏與公主長得像,于是被迫代替她去往敵國和親牡彻。 傳聞我的和親對象是個(gè)殘疾皇子扫沼,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評論 2 354

推薦閱讀更多精彩內(nèi)容