零. 前言
票圈里總能看到一些很唯美的濾鏡焕盟,讓我驚嘆之余又好奇,這個(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)算法吧= =