前言
Metal入門教程總結(jié)
Metal圖像處理——直方圖均衡化
Metal視頻處理——綠幕視頻合成
本文介紹Metal下的顏色查找表(Color Lookup Table)撬槽。
正文
一張1024x1024的普通圖片,是由1024 * 1024=1048576個(gè)像素點(diǎn)組成厚骗,每個(gè)像素點(diǎn)包括RGBA共32bit椅寺,常見的圖像處理是對(duì)相鄰像素點(diǎn)顏色浑槽、像素點(diǎn)本身顏色做處理。
在對(duì)像素點(diǎn)本身顏色做處理的情況下返帕,需要把某個(gè)顏色映射成另外一個(gè)顏色桐玻,比如說(shuō)把顏色rgb(0.2, 0.3, 0.4) * colorMatrix = rgb(0.1, 0.2, 0.3),可以使用shader實(shí)現(xiàn)這個(gè)顏色轉(zhuǎn)變對(duì)圖片進(jìn)行處理荆萤。但實(shí)際過(guò)程中的顏色映射計(jì)算過(guò)程可能會(huì)更加復(fù)雜镊靴,并且會(huì)有很多冗余運(yùn)算(比如我們對(duì)相同的顏色會(huì)有重復(fù)的運(yùn)算),我們希望用空間換取時(shí)間链韭,把相同顏色值的運(yùn)算結(jié)果緩存下來(lái)偏竟。
如何避免冗余運(yùn)算?
假如我們用一個(gè)三維數(shù)組colorConvert來(lái)緩存這個(gè)結(jié)果敞峭,那么rgb(0.2, 0.3, 0.4) * colorMatrix處理就變成數(shù)組訪問(wèn)操作rgb(0.2, 0.3, 0.4) =colorConvert[0.2 * 255][0.3 * 255][0.4 * 255]=rgb(0.1, 0.2, 0.3)踊谋,運(yùn)算效率會(huì)有較高的提升。
但是數(shù)組長(zhǎng)度有512* 512 * 512= 134 217 728旋讹,太占用內(nèi)存殖蚕!我們可以減少數(shù)組每一維的大小轿衔,把512種可能減少為64種。同時(shí)為了有更好的過(guò)渡效果睦疫,每次計(jì)算的時(shí)候我們可以用相鄰的結(jié)果進(jìn)行線性結(jié)合呀枢。
我們以一維的情況為例,用數(shù)組a[64]來(lái)緩存512種顏色的映射結(jié)果笼痛。假如某個(gè)點(diǎn)的值是102,那么有102/4=25.5琅拌,映射結(jié)果為a[25] * 0.5+a[26] * 0.5缨伊,即兩邊各取一半;假如某個(gè)點(diǎn)的值是101进宝,那么有101/4=25.25刻坊,映射結(jié)果為a[25] * 0.25 + a[26] * 0.75,按照小數(shù)點(diǎn)進(jìn)行分配党晋。
這樣可以用合理的數(shù)組大小緩存運(yùn)算結(jié)果谭胚,并且可以在PC端提前計(jì)算出映射的數(shù)組。
接下來(lái)的問(wèn)題是:
如何把映射數(shù)組傳遞給shader未玻?
直接的方案是使用文本記錄映射結(jié)果灾而,然后把移動(dòng)端加載文本,讀取結(jié)果后存入內(nèi)存的數(shù)組buffer扳剿,再把buffer作為shader的一個(gè)參數(shù)旁趟。
這里我們肯定不采用這種辦法,而是采用顏色查找表(Color Lookup Table)庇绽。
我們的映射數(shù)組是colorConvert3[64][64][64]锡搜,相當(dāng)于64個(gè)二維數(shù)組colorConvert2[64][64]。如果我們colorConvert2[i][j]的結(jié)果寫入一張64 * 64的圖片第(i, j)個(gè)像素點(diǎn)瞧掺,即用一張64 * 64的圖片來(lái)緩存這個(gè)結(jié)果耕餐,如下:
對(duì)于colorConvert3[64][64][64],可以采用把64張圖片拼成一個(gè)8 * 8個(gè)小圖組成的大圖辟狈,如下:
最后肠缔,問(wèn)題只有:
如何從圖片讀取對(duì)應(yīng)運(yùn)算結(jié)果?
圖片有64個(gè)正方形上陕,每個(gè)小正方存著64 * 64的運(yùn)算結(jié)果桩砰。對(duì)于顏色rgb(x, y, z),我們先用z值算出正方形的位置释簿,再用(x亚隅,y)讀取對(duì)應(yīng)結(jié)果。
整個(gè)過(guò)程如下:(shader中的顏色值都是歸一化后的結(jié)果庶溶,區(qū)間為[0, 1])
- 1煮纵、用藍(lán)色值計(jì)算正方形的位置懂鸵,得到quad1和quad2;
- 2行疏、根據(jù)紅色值和綠色值計(jì)算對(duì)應(yīng)位置在整個(gè)紋理的坐標(biāo)匆光,得到texPos1和texPos2;
- 3酿联、根據(jù)texPos1和texPos2讀取映射結(jié)果终息,再用藍(lán)色值的小數(shù)部分進(jìn)行mix操作;
整個(gè)shader如下:
constant float SquareSize = 63.0 / 512.0;
constant float stepSize = 0.0; //0.5 / 512.0;
fragment float4
samplingShader(RasterizerData input [[stage_in]], // stage_in表示這個(gè)數(shù)據(jù)來(lái)自光柵化贞让。(光柵化是頂點(diǎn)處理之后的步驟周崭,業(yè)務(wù)層無(wú)法修改)
texture2d<float> normalTexture [[ texture(LYFragmentTextureIndexNormal) ]], // texture表明是紋理數(shù)據(jù),LYFragmentTextureIndexNormal是索引
texture2d<float> lookupTableTexture [[ texture(LYFragmentTextureIndexLookupTable) ]]) // texture表明
{
constexpr sampler textureSampler (mag_filter::linear,
min_filter::linear); // sampler是采樣器
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);
float2 texPos1; // 計(jì)算顏色(r,b,g)在第一個(gè)正方形中對(duì)應(yīng)位置
/*
quad1是正方形的坐標(biāo)逢净,每個(gè)正方形占紋理大小的1/8,即是0.125歼指,所以quad1.x * 0.125是算出正方形的左下角x坐標(biāo)
stepSize這里設(shè)置為0爹土,可以忽略;
SquareSize是63/512踩身,一個(gè)正方形小格子在整個(gè)圖片的紋理寬度
*/
texPos1.x = (quad1.x * 0.125) + stepSize + (SquareSize * textureColor.r);
texPos1.y = (quad1.y * 0.125) + stepSize + (SquareSize * textureColor.g);
float2 texPos2; // 同上
texPos2.x = (quad2.x * 0.125) + stepSize + (SquareSize * textureColor.r);
texPos2.y = (quad2.y * 0.125) + stepSize + (SquareSize * textureColor.g);
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
return float4(newColor.rgb, textureColor.w); //不修改alpha值
}
總結(jié)
顏色轉(zhuǎn)換表是在網(wǎng)上找了一張胀茵,特此感謝——LUT(顏色查找表)的來(lái)源;
Shader部分參考自GPUImageLookupFilter挟阻,demo的地址在這里琼娘。