目錄
-
LUT原理
-
LUT濾鏡特效Shader解讀
演示demo
前言:
平常我們使用的圖片編輯app真竖,一般都有這個最基本的LUT濾鏡,都是UI設(shè)計師設(shè)計好風(fēng)格后吵护,導(dǎo)出LUT效果圖焦读,有了這個LUT效果圖,在app端睁冬,借助GPUImage庫,便可以方便的實現(xiàn)這種特效看疙。
甚至有些開發(fā)者豆拨,是直接破解別人的app直奋,拿到里面的LUT圖,不需要自己專門設(shè)計施禾,便能輕易的竊取到別人app里這些好看的風(fēng)格的濾鏡
LUT在圖像處理中的位置
先從大方向上看LUT在數(shù)字圖像處理上處于什么位置
圖像處理大致可以分為以下幾類:
- 獨立像素點運算
包括亮度脚线、對比、飽和度弥搞、色調(diào)邮绿、灰色化等
- 多個像素點運算
一般是進行卷積變換,求均值攀例,求中值斯碌,插值等,包括邊緣檢測肛度、浮雕化傻唾、模糊、銳化
- 幾何變化
矩陣變換承耿。包括縮放冠骄、旋轉(zhuǎn)、傾斜加袋、扭曲凛辣、液化等
- 多圖像合成
多張圖像的處理,包括添加水印职烧,貼紙扁誓,美妝等。
LUT歸為獨立像素點運算這種圖像處理
LUT介紹
LUT 是 LookUpTable 的簡稱蚀之,也稱作顏色查找表
從名字上看蝗敢,我們大概可以知道,是用來查找顏色的足删,他確實就是這樣的寿谴,是通過一種顏色,查找其映射后的顏色失受,可以理解為一個函數(shù)LUT(R1,G1,B1),帶R,G,B三個自變量的函數(shù)讶泰,輸出為其對應(yīng)映射后的值R2,G2,B2
LUT(R1, G1, B1) = (R2, G2, B2)
對于RGB顏色空間來說,顏色是由RGB三種基色構(gòu)成拂到,所有像素點的顏色值痪署,都可以通過RGB組合而成,我們量化顏色時兄旬,一般使用8bit來表示RGB中的一個分量狼犯,所以每一個分量可以表示為2的8次方,即0~255。
那么要建立一個顏色映射表辜王,我們就可以使用255* 255* 255種來表示所有顏色映射后的值
這是非常精準(zhǔn)的,每一種顏色都可以查找到
當(dāng)我們需要把一幅原始圖像的顏色風(fēng)格轉(zhuǎn)換為一種懷舊風(fēng)格時罐孝,
上面的這個需求呐馆,我們完全可以搞一個三維數(shù)組,數(shù)組的大小256 * 256 * 256莲兢,數(shù)組存儲所有懷舊風(fēng)格顏色映射后的值汹来,直接通過查表,就能轉(zhuǎn)換風(fēng)格了改艇,確實可以的
但是上面的這種實現(xiàn)存在一些問題:
1.數(shù)字太難管理收班,難于保存,難于復(fù)用谒兄,容易出錯摔桦,一不小心修改一個數(shù)字,因為什么原因丟失承疲,弄錯邻耕,效果就變了,定位錯誤非常困難
2.顏色查找表格過大
而LUT能很好的解決上面的問題燕鸽。
首先解決第一個難以管理兄世,容易出錯,出錯難以排查的問題啊研。
如果我們使用一張圖存儲上述信息御滩,那么就不會容易出錯了,圖保存和復(fù)用非常方便党远,也不容易出錯削解,圖沒錯,信息就不會有錯誤沟娱。
沒有找到 256 * 256 * 256 的LUT圖(即16 * 16個方格的LUT圖钠绍,每個方格里有16 * 16個小格),因為沒有這種設(shè)計花沉,所以找不到柳爽,但是完全可以使用64 * 64 * 64的圖,代替256 * 256 * 256的表格碱屁,來保存數(shù)據(jù)磷脯,這樣圖代替表格完成數(shù)據(jù)保存,就解決了表格數(shù)據(jù)容易出錯娩脾,出錯后排查困難的問題赵誓。
我們現(xiàn)在使用64 * 64 * 64顆粒度的LUT圖保存數(shù)據(jù),即將256歸化到64而已。
那怎么用一張圖來代替數(shù)據(jù)表來保存信息呢俩功,這是一個巧妙的設(shè)計幻枉。
下面是一個LUT效果圖
LUT就是用圖代替了表,完美的解決了上面表存在的問題
我們來看下诡蜓,LUT如何能代替顏色查找表的功能
比如想查找純藍色色(0熬甫,0,1)對應(yīng)的映射值
LUT(R1, G1, B1) = (?, ?, ?)
我們先看這個顏色查找表的特征:
粗看的話蔓罚,我們可以得出一個結(jié)論:
8 * 8個方格椿肩,總體上,底部越來越藍豺谈;而對于每一個方格郑象,則越往右越紅,越往下越綠茬末;
哈哈厂榛,沒錯,我猜你會關(guān)聯(lián)到了我們的RGB顏色了丽惭。
這是一個64 * 64 * 64顆粒度的LUT設(shè)計噪沙,總的方格大小為512 * 512, 8 * 8 64個方格吐根,所以每個方格大小為64 * 64正歼。
64個方格,每個方格大小為 64 * 64 拷橘, 所以叫做64 * 64 * 64顆粒度的設(shè)計局义。因為顏色值的范圍為0~255,即256個取值冗疮,將256個取值歸化到64萄唇。
從左上到右下(可以想作z方向),越來越藍术幔,藍色值B從0~255另萤,代表用來查找的B,即LUT(R1,G1,B1) = (R2,G2,B2)中的B1诅挑,
每一個方格里四敞,從左往右(x方向),紅色值R從0~255拔妥,代表用來查找的R忿危,即LUT(R1,G1,B1) = (R2,G2,B2)中的R1;
每一個方格里没龙,從上往下(y方向)铺厨,綠色值G從0~255缎玫,代表用來查找的G,即LUT(R1,G1,B1) = (R2,G2,B2)中的G1解滓;
因為一個顏色分量是0~255赃磨,所以一個方格表示的藍色范圍為4,比如最左上的方格藍色為0~4洼裤,查找時邻辉,如果有某個像素的藍色值在0~4之間,則一定是在第一個方格里查找其映射后的顏色
通過顏色找位置逸邦,找到的位置對應(yīng)的點的顏色即是這個顏色映射后的顏色,如下圖的五角星??的顏色既是某個顏色映射后的顏色
這樣在扰,我們就完成了使用圖代替表格缕减,進行數(shù)據(jù)的存儲,并且對圖的數(shù)據(jù)存儲進行了4 * 4 * 4倍的壓縮處理
有了這些了解后芒珠,我們來嘗試查找像素點歸一化后的純藍色(0桥狡,0,1)的映射后的顏色皱卓。
需求變?yōu)椋?/p>
我們是想通過顏色S裹芝,從LUT圖上,查找其映射后的顏色T
思路是娜汁,通過顏色S嫂易,找到其在LUT上的位置,位置上對應(yīng)的顏色即是要找的顏色T
-
1.使用藍色B定位方格n
n = 1(藍色值) * 63(一共64個方格掐禁,從第0個算起) = 63
故要定位的方格n是第63個
-
2.定位在方格里的位置怜械,使用R,G定位位置x,y
x = 0(R值) * 63(每個方格大小為 64 * 64) = 0, y = 0(G值) * 63(每個方格大小為 64 * 64) = 0
所以方格的(0,0)位置為要定位的x傅事,y
-
3.定位在整個圖中位置
在512 * 512的大小上缕允,其坐標(biāo)為:
Py = floor(n/8) * 64 + y = 7 * 64 + 0 = 448;
Px = [n - floor(n/8) * 8] * 64 + x = [63 - 7*8] * 64 + 0 = 448;
P = (448, 448)
其中 floor(n/8)代表位置所在行蹭越,每一行的長度為64障本,y為方格里的G定位的位置;
[n - floor(n/8) * 8]代表位置所在列數(shù)响鹃,每一列的長度為64驾霜,x為方格里的R定位的位置;
floor為向下取整买置,ceil為向上取整寄悯。比如2.3, floor(2.3) = 2; ceil(2.3) = 3;
方格大小為512 * 512, 位置為P = (448, 448), 歸一化后為(7/8堕义, 7/8)猜旬,很明顯脆栋,顏色值(0,0洒擦,1)的位置確實在第63個方格的左上角
4.計算映射后顏色
// 這里使用GPU采樣器對紋理采樣
vec4 newColor = texture(sample, texPos);
其中texPos就是第三步的歸一化后的P
現(xiàn)在我們已經(jīng)完成了通過LUT查找顏色的映射值椿争,理解了這個之后,我們后面用代碼來實現(xiàn)熟嫩。
現(xiàn)在秦踪,再回到我們說LUT能夠解決前面的顏色查找表存在的問題
1.數(shù)據(jù)大小:這里使用的是 512 * 512大小的尺寸的LUT圖掸茅,比前面的顏色查找表要小
2.圖片很方便存儲椅邓,移植性非常好,做好一次后昧狮,非常方便重復(fù)使用
其他的優(yōu)點:
所有的算法計算景馁,都是對LUT進行計算位置,取位置處的顏色值逗鸣,有助于算法本身的保護
LUT的設(shè)計更容易進行熱更新合住,設(shè)計師設(shè)計好效果可以動態(tài)發(fā)布
缺點:
- 1.LUT資源容易被破解、泄密(得到LUT圖撒璧,就相當(dāng)于得到一個算子)
- 2.尺寸512 * 512 還是比較大透葛,增加軟件包的體積(可以考慮后下發(fā)來優(yōu)化)
思考:
對于缺點2,LUT圖大小一定要使用512 * 512嗎卿樱?是否可以再縮小呢僚害?
我們前面論述的時候,已經(jīng)將256 * 256 * 256 壓縮到了 64 * 64 * 64繁调,完全可以再壓縮贡珊。
實際上,我們一般設(shè)計為方形的涉馁,算法也簡化门岔,對于R,G烤送,B系數(shù)是一樣的處理寒随,我們可以將256歸化為64,還可以繼續(xù)歸化為16帮坚。 即從256 * 256 * 256變?yōu)?6 * 16 * 16妻往,其對應(yīng)的LUT如下:
這個公司的一個項目里是有使用的
補充
如何查找顏色A(0.4,0.6试和,0.2)映射的顏色呢讯泣?
-
1.使用藍色B定位方格n
n = b * 63 = 0.2 * 63 = 12.6
要定位的方格n是第12.6個,是個小數(shù)阅悍,那到底是用第12個好渠,還是用第13個呢昨稼?我們采用兩個,對兩個方格的取色結(jié)果進行混合即可,首先使用第12個方格計算拳锚,然后再使用第13個方格計算假栓,最后混合兩個方格的顏色
-
2.在方格里,使用R霍掺,G定位位置x,y
x = 0.4 * 63 = 25.2, y = 0.6 * 63 = 37.8
-
3.定位兩個方格對應(yīng)的位置
在512 * 512的大小上匾荆,計算其坐標(biāo):
// 先使用第12個方格定位其坐標(biāo)P1
Py = floor(n/8) * 64 + y = 1 * 64 + 37.8 = 101.8;
Px = [n - floor(n/8) * 8] * 64 + x = [12 - 1*8] * 64 + 25.2 = 281.2;
P1 = (Px, Py)=(281.2杆烁, 111.8);
歸一化后為P1 = (281.2牙丽, 111.8)/512 = (0.549, 0.2184);
// 先使用第13個方格定位其坐標(biāo)P2
Py = floor(n/8) * 64 + y = 1 * 64 + 37.8 = 101.8;
Px = [n - floor(n/8) * 8] * 64 + x = [13 - 1*8] * 64 + 25.2 = 345.2;
P2 = (Px, Py)=(345.2, 111.8);
歸一化后為P2 = (345.2, 111.8)/512 = (0.674, 0.2184);
-
4.計算顏色
// 這里使用GPU采樣器對紋理采樣
vec4 newColor1 = texture(sample, texPos1);
vec4 newColor2 = texture(sample, texPos2);
其中texPos1就是第三步的P1兔魂, texPos2是第三步的P2
-
5.混合顏色
resColor = mix(newColor1, newColor2, a);
a = fract(blueColor);
blueColor 為12.6烤芦,小數(shù)部分越大,越接近13入热,所以第13個方格占比越大
ps:
a = fract(blueColor); //fract(x) 獲取x的小數(shù)部分
mix(x, y, a); //取x,y的線性混合,x(1-a)+ya
了解了這個后拍棕,再來看LUT濾鏡里glsl代碼的shader算法部分晓铆,就很容易了
GLSL的LUT濾鏡shader解讀
fragment half4 lookupFragment(TwoInputVertexIO fragmentInput [[stage_in]],
texture2d<half> inputTexture [[texture(0)]],
texture2d<half> inputTexture2 [[texture(1)]],
constant IntensityUniform& uniform [[ buffer(0) ]])
{
constexpr sampler quadSampler;
half4 base = inputTexture.sample(quadSampler, fragmentInput.textureCoordinate);
// 獲取藍色
half blueColor = base.b * 63.0h;
// 通過藍色計算兩個方格quad1勺良,quad2
half2 quad1;
quad1.y = floor(floor(blueColor) / 8.0h);
quad1.x = floor(blueColor) - (quad1.y * 8.0h);
half2 quad2;
quad2.y = floor(ceil(blueColor) / 8.0h); //ceil 向下取整,ceil(12.6) = 13, 解決跨行時計算問題骄噪,比如blueColor = 7.6尚困,則取第7,8個方格链蕊,他們不在同一行
quad2.x = ceil(blueColor) - (quad2.y * 8.0h);
// 計算映射后顏色所在兩個方格的位置的歸一化紋理坐標(biāo)
float2 texPos1;
texPos1.x = (quad1.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * base.r);
texPos1.y = (quad1.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * base.g);
float2 texPos2;
texPos2.x = (quad2.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * base.r);
texPos2.y = (quad2.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * base.g);
// 取出對應(yīng)坐標(biāo)的顏色newColor1事甜,newColor2
constexpr sampler quadSampler3;
half4 newColor1 = inputTexture2.sample(quadSampler3, texPos1);
constexpr sampler quadSampler4;
half4 newColor2 = inputTexture2.sample(quadSampler4, texPos2);
// 混合顏色newColor1,newColor2滔韵,得到查找的顏色color_t
half4 newColor = mix(newColor1, newColor2, fract(blueColor));
// 調(diào)節(jié)強度時逻谦,將color_t和源色color_s進行混合
return half4(mix(base, half4(newColor.rgb, base.w), half(uniform.intensity)));
}
- 1.通過B分量確定兩個方格
half2 quad1;
quad1.y = floor(floor(blueColor) / 8.0h);
quad1.x = floor(blueColor) - (quad1.y * 8.0h);
half2 quad2;
quad2.y = floor(ceil(blueColor) / 8.0h);
quad2.x = ceil(blueColor) - (quad2.y * 8.0h);
比如 base(0.4,0.6陪蜻,0.2), 先確定第一個方格:
base.b = 0.2邦马,blueColor = 0.2 * 63 = 12.6,(即為第12個,第13個方格)宴卖,但是我們要計算它坐在行和列滋将, floor(12.6) = 12, floor(12 / 8.0h) = 1,即第一行;
floor(blueColor) - (quad1.y * 8.0h) = floor(12.6) - (1 * 8) = 4症昏,即第4列随闽;
同理可以算出第二個方格為第1行,第5列
//ceil 向下取整肝谭,ceil(12.6) = 13, 解決跨行時計算問題掘宪,比如blueColor = 7.6蛾扇,則取第7,8個方格添诉,他們不在同一行
- 2.確定方格映射后的坐標(biāo)
float2 texPos1;
texPos1.x = (quad1.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * base.r);
texPos1.y = (quad1.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * base.g);
float2 texPos2;
texPos2.x = (quad2.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * base.r);
texPos2.y = (quad2.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * base.g);
其中 (quad1.x * 0.125) 表示行歸一化的坐標(biāo)屁桑,(quad1.y * 0.125)表示列歸一化的坐標(biāo),一共8行栏赴,每一行的長度為1/8 = 0.125蘑斧,一共8列,每一列的長度為1/8 = 0.125须眷;
0.125 * base.r表示一個方格里紅色的位置竖瘾,因為一個方格長度為0.125,r從0~1花颗;綠色同理捕传;
需要留意的是這里有個0.5/512, 和 1.0/512.
0.5/512 是為了取點的中間值扩劝,一個點長度為1庸论,總長度512,取點的中間值棒呛,即為0.5/512聂示;
1.0/512, 是因為計算texPos2.x時簇秒,單獨對于一個方格來說鱼喉,是從0~63,所以為63/512趋观,即0.125 - 1.0/512扛禽;
- 3.取出兩個方格里對應(yīng)坐標(biāo)的顏色newColor1,newColor2
constexpr sampler quadSampler3;
half4 newColor1 = inputTexture2.sample(quadSampler3, texPos1);
constexpr sampler quadSampler4;
half4 newColor2 = inputTexture2.sample(quadSampler4, texPos2);
- 4.混合顏色newColor1皱坛,newColor2编曼,得到查找的顏色color_t
// 混合顏色newColor1,newColor2剩辟,得到查找的顏色color_t
half4 newColor = mix(newColor1, newColor2, fract(blueColor));
至此掐场,LUT特效濾鏡的實現(xiàn)原理,我們就明白了抹沪。
參考: