1.以下是關(guān)于Metal或者OpenGL ES顏色查找表LUT的理解萨咳,具體博客見落影大神落影l(fā)oyinglin
2.常見的圖像處理是對(duì)相鄰像素點(diǎn)顏色、像素點(diǎn)本身顏色做處理疫稿。
1.在對(duì)像素點(diǎn)本身顏色做處理的情況下培他,需要把某個(gè)顏色映射成另外一個(gè)顏色,LUT實(shí)際上就是一種顏色映射成另外一種我們想要的顏色的一種手段遗座。
2.LUT核心思路就是通過RGB三個(gè)顏色分量舀凛,映射為L(zhǎng)UT紋理的紋理坐標(biāo),再通過這個(gè)坐標(biāo)獲取到LUT紋理上對(duì)應(yīng)的顏色重新作為該RGB的顏色輸出途蒋,這樣就達(dá)到了一種顏色映射為另外一種顏色猛遍,其余不需要改變的顏色映射出來的還是原來的顏色,而且是可控的号坡,根據(jù)需求定制的懊烤。
3.那么人臉美膚(變白),紅唇等都是可以通過LUT實(shí)現(xiàn)的宽堆,比如人臉顏色腌紧,可能有幾種常見的顏色值,那么如果要美白的話畜隶,顏色查找表應(yīng)該有幾張與之對(duì)應(yīng)的LUT壁肋,根據(jù)檢測(cè)出來的膚色傳入不同的LUT紋理到Fragment Shader里面,達(dá)到不同膚色都可以達(dá)到相同的美白程度和效果籽慢。
3.對(duì)轉(zhuǎn)換流程的理解如下RGB->xyz(紋理坐標(biāo))->LUT(xy)->RGB
1.假如某個(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)行分配驾茴。
2.一個(gè)64 * 64像素點(diǎn)的紋理圖如下
我們的映射數(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的圖片來緩存這個(gè)結(jié)果晨缴,如下
3.一個(gè)64 * 64 * 64像素點(diǎn)的紋理圖如下
64 * 64 * 64的LUT只是一種顏色劃分顆粒的方式译秦,可以是別的數(shù)值,但是击碗,涉及到GPU的計(jì)算量和有沒有太大的必要性筑悴,64應(yīng)該是比較合適的值,也就是說把RGB每一個(gè)分量分成64份稍途,這樣的顆粒度阁吝, 顏色值是0.0-1.0,與之對(duì)應(yīng)的就是0.0 - 63
4.同時(shí)為了有更好的過渡效果械拍,每次計(jì)算的時(shí)候我們可以用相鄰的結(jié)果進(jìn)行線性結(jié)合突勇。可能是相鄰的坷虑,也可能不是甲馋,但是顏色值相差不大。
映射關(guān)系計(jì)算原理迄损,通過RGB的b值定躏,再結(jié)合floor (向下取整,例如floor(25.9) = 25)和ceil (向上取整比如 (ceil(25.1) = 26)芹敌,獲取到相鄰或者不相鄰的LUT里面64個(gè)里面的其中2個(gè)顏色塊痊远,這一步獲取到了兩個(gè)顏色塊的具體位置
4. RGB中的B顏色分量在LUT64個(gè)顏色塊中確定兩個(gè)顏色塊
第1個(gè)顏色塊
比如b-> blueColor是22.5,那么第一個(gè)顏色塊為第2行氏捞,第6列的正方形
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);
第2個(gè)顏色塊
比如b-> blueColor是22.5豁护,那么第二個(gè)顏色塊為第2行哼凯,第7列的正方形
blueColor=22.5,則y=22/8=2楚里,x=23-8*2=6断部,即是第2行,第7個(gè)正方形班缎;(因?yàn)閥是縱坐標(biāo))
quad2.y = floor(ceil(blueColor) * 0.125);
quad2.x = ceil(blueColor) - (quad2.y * 8.0);
以上是相鄰的情況蝴光,向上向下取整與8的整數(shù)倍有跨越的時(shí)候她渴,不相鄰的情況就出現(xiàn)了
第1個(gè)顏色塊
比如b-> blueColor是23.5,那么第一個(gè)顏色塊為第2行蔑祟,第7列的正方形
blueColor=23.5趁耗,則y=23/8=2,x=23-8*2=7疆虚,即是第2行苛败,第7個(gè)正方形;(因?yàn)閥是縱坐標(biāo))
quad1.y = floor(floor(blueColor) * 0.125);
quad1.x = floor(blueColor) - (quad1.y * 8.0);
第2個(gè)顏色塊
比如b-> blueColor是22.5径簿,那么第二個(gè)顏色塊為第3行罢屈,第0列的正方形
blueColor=23.5,則y=24/8=3篇亭,x=24-8*3=0缠捌,即是第3行,第0個(gè)正方形译蒂;(因?yàn)閥是縱坐標(biāo))
quad2.y = floor(ceil(blueColor) * 0.125);
quad2.x = ceil(blueColor) - (quad2.y * 8.0);
總結(jié):通過以上b分量的的映射關(guān)系曼月,可以確定兩個(gè)顏色塊。
5.通過RG兩個(gè)歸一化的顏色分量蹂随,計(jì)算每一顏色塊中xy=RG的值相對(duì)于整個(gè)LUT的紋理坐標(biāo)十嘿,即塊內(nèi)紋理坐標(biāo)相對(duì)于LUT的紋理坐標(biāo)計(jì)算
意思就是RG兩個(gè)值作為xy值是上面確定兩個(gè)顏色塊的紋理坐標(biāo),在結(jié)合對(duì)應(yīng)的百分比計(jì)算出岳锁,兩個(gè)顏色塊中的兩個(gè)點(diǎn)對(duì)應(yīng)到整個(gè)LUT紋理的紋理坐標(biāo)
constant float SquareSize = 63.0 / 512.0;
constant float stepSize = 0.0; //0.5 / 512.0;
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);
總結(jié):通過RG兩個(gè)顏色分量確定了绩衷,具體的兩個(gè)點(diǎn)在LUT中的紋理坐標(biāo)
6.根據(jù)紋理坐標(biāo)獲取兩個(gè)新新的顏色值,再混合激率,不同的 LUT表咳燕,顏色值可以自定義
兩個(gè)內(nèi)建函數(shù)解析
// genType mix (genType x, genType y, genType a)、genType mix (genType x, genType y, float a)
//返回線性混合的x和y乒躺,如:x?(1?a)+y?a
// fract(x) 返回x-floor(x)招盲,即返回x的小數(shù)部分
獲取混合后的新的顏色值返回
進(jìn)行顏色混合,常用的顏色混合公式 AColor * (1.0 - alpha) + BColor * alpha
// 正方形1的顏色值
float4 newColor1 = lookupTableTexture.sample(textureSampler, texPos1);
// 正方形2的顏色值
float4 newColor2 = lookupTableTexture.sample(textureSampler, texPos2);
// 根據(jù)小數(shù)點(diǎn)的部分進(jìn)行mix
float4 newColor = mix(newColor1, newColor2, fract(blueColor));
return float4(newColor.rgb, textureColor.w); //不修改alpha值
7.完整的vertex和fragment函數(shù)
#include <metal_stdlib>
#import "LYShaderTypes.h"
using namespace metal;
typedef struct
{
float4 clipSpacePosition [[position]]; // position的修飾符表示這個(gè)是頂點(diǎn)
float2 textureCoordinate; // 紋理坐標(biāo)嘉冒,會(huì)做插值處理
} RasterizerData;
vertex RasterizerData // 返回給片元著色器的結(jié)構(gòu)體
vertexShader(uint vertexID [[ vertex_id ]], // vertex_id是頂點(diǎn)shader每次處理的index曹货,用于定位當(dāng)前的頂點(diǎn)
constant LYVertex *vertexArray [[ buffer(LYVertexInputIndexVertices) ]]) { // buffer表明是緩存數(shù)據(jù),0是索引
RasterizerData out;
out.clipSpacePosition = vertexArray[vertexID].position;
out.textureCoordinate = vertexArray[vertexID].textureCoordinate;
return out;
}
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ù)來自光柵化讳推。(光柵化是頂點(diǎn)處理之后的步驟顶籽,業(yè)務(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
// genType mix (genType x, genType y, genType a)芝雪、genType mix (genType x, genType y, float a)
//返回線性混合的x和y减余,如:x?(1?a)+y?a
// fract(x) 返回x-floor(x),即返回x的小數(shù)部分
return float4(newColor.rgb, textureColor.w); //不修改alpha值
}
8.總結(jié)
上面是一個(gè)正常的顏色查找表LUT惩系,所以位岔,渲染出來的效果更清晰,如果想要不同的顏色效果只需要 原RGB->LUT(xyz)->LUT(RGB)對(duì)應(yīng)不同的LUT即可堡牡。靈活度很高的自定義像素顏色的一種手段抒抬,以上是Metal的代碼,OpenGL ES與之同理晤柄,再次感謝大神 落影l(fā)oyinglin擦剑!