OpenGL ES 之 LUT(濾鏡基準(zhǔn)圖)
前言
在調(diào)色領(lǐng)域中,稱為顏色查找表,查找表的分量為R、G、B,是一種降低GPU運(yùn)算量的技術(shù),通過將顏色值存儲(chǔ)在一張表中,在需要的時(shí)候通過索引在這張表上找到對(duì)應(yīng)的顏色值。
這是一種使用空間換時(shí)間的算法。
LUT分為1D和3D,本質(zhì)的區(qū)別在于索引的輸出所需要的索引數(shù)
從RGB色彩講起
我們知道光的三原色是紅(Red)、綠(Green)、藍(lán)(Blue),也就是RGB,我們將這三種光按不同比例混合就可以得到豐富的色彩。
我們規(guī)定R、G、B三者的取值范圍為[0, 255],0表示不發(fā)光,255表示發(fā)出最強(qiáng)的光,因此RGB(255, 0, 0)表示純紅色,同理RGB(0, 255, 0)表示純綠色,RGB(0, 0, 255)純藍(lán)色。
3D LUT
在濾鏡的LUT效果應(yīng)用中,通常是將用3D LUT預(yù)存效果的RGB值。
在 8bit 的 RGB 色域空間中,每個(gè)分量的取值范圍為[0, 255],一張完整的色域空間就為 256 * 256 * 256 = 16581375bit = 16M。
但實(shí)際上并不需要那么多顏色,通常會(huì)列舉節(jié)點(diǎn)來儲(chǔ)存,兩個(gè)節(jié)點(diǎn)之間的顏色通過線性插值得出。

1、表現(xiàn)形式
3D LUT的儲(chǔ)存就是一堆RGB數(shù)據(jù),它可以使用以下三種方式進(jìn)行表示:
1、三維數(shù)組
2、顏色方塊
3、顏色圖片
2、顏色圖片
顏色圖片的本質(zhì)就是將顏色方塊進(jìn)行二維化處理。上述顏色圖片分辨率為512512,里面有88的大格子,每個(gè)大格子中存有64*64個(gè)小格子,用來存儲(chǔ)色彩像素點(diǎn)。
每個(gè)小格子如下圖所示,X軸表示[0, 255]的R通道,Y軸表示[0, 255]的G通道。B通道分量放在了8*8的大格子中,從左到右從上到下,最后將RGB三個(gè)分量疊加
一張顏色圖片一功能儲(chǔ)存64 * 64 * 64 = 512 * 512 = 262144種色彩
3、輸出一張標(biāo)準(zhǔn)LUT圖片
1、創(chuàng)建RGBA原始數(shù)據(jù)
RGBA rgba[8 * 64][8 * 64];
for (int by = 0; by < 8; by++) {
for (int bx = 0; bx < 8; bx++) {
for (int g = 0; g < 64; g++) {
for (int r = 0; r < 64; r++) {
// 將RGB[0, 255]分成64份,每份相差4個(gè)單位, +0.5做四舍五入運(yùn)算
int rr = (int)(r * 255.0 / 63.0 + 0.5);
int gg = (int)(g * 255.0 / 63.0 + 0.5);
int bb = (int)((bx + by * 8) * 255.0 / 63.0 + 0.5);
int aa = 255.0;
int x = r + bx * 64;
int y = g + by * 64;
rgba[y][x] = (RGBA){rr, gg, bb, aa};
}
}
}
}
2、寫入數(shù)據(jù),生成圖片
int width = 8 * 64;
int height = 8 * 64;
size_t bufferLength = width * height * 4;
CGDataProviderRef dataProviderRef = CGDataProviderCreateWithData(NULL, &rgba, bufferLength, NULL);
CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault;
CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault;
CGImageRef imageRef = CGImageCreate(width, height, 8, 32, width * 4, colorSpaceRef, bitmapInfo, dataProviderRef, NULL, YES, renderingIntent);
uint32_t *pixels = (uint32_t *)malloc(bufferLength);
CGContextRef contextRef = CGBitmapContextCreate(pixels, width, height, 8, 32, colorSpaceRef, kCGImageAlphaPremultipliedLast);
CGContextDrawImage(contextRef, CGRectMake(0, 0, width, height), imageRef);
UIImage *image = nil;
if ([UIImage respondsToSelector:@selector(imageWithCGImage:scale:orientation:)]) {
float scale = [UIScreen mainScreen].scale;
image = [UIImage imageWithCGImage:imageRef scale:scale orientation:UIImageOrientationUp];
} else {
image = [UIImage imageWithCGImage:imageRef];
}
CGImageRelease(imageRef);
CGContextRelease(contextRef);
CGDataProviderRelease(dataProviderRef);
CGColorSpaceRelease(colorSpaceRef);
free(pixels);
使用 GLSL 實(shí)現(xiàn) LUT 濾鏡
制作濾鏡基準(zhǔn)圖
將上述的LUT圖片和需要上效果的圖片拖進(jìn)Photoshop

隱藏LUT圖片,進(jìn)行色彩調(diào)整,這里進(jìn)行了色相、自然飽和度的調(diào)整,并調(diào)整了曲線,拉高陰影處的曲線。

隱藏上效果圖片,導(dǎo)出LUT圖片

Coding
輸入雙紋理,InputImageTexture為輸入的需要應(yīng)用效果的圖片紋理,InputImageTexture2為基準(zhǔn)圖紋理。
// glsl.fsh
precision mediump float;
uniform sampler2D InputImageTexture2;
uniform sampler2D InputImageTexture;
uniform lowp float intensity;
varying vec2 TextureCoordinate;
void main() {
// 取出當(dāng)前像素的紋素
highp vec4 textureColor = texture2D(InputImageTexture, TextureCoordinate);
highp float blueColor = textureColor.b * 63.0;
// 計(jì)算B通道,看使用哪個(gè)像素色塊(這里分別對(duì)計(jì)算結(jié)果向上,向下取整,然后再對(duì)兩者進(jìn)行線性計(jì)算,減小誤差)
highp vec2 quad1;
quad1.y = floor(floor(blueColor) / 8.0);
quad1.x = floor(blueColor) - (quad1.y * 8.0);
highp vec2 quad2;
quad2.y = floor(ceil(blueColor) / 8.0);
quad2.x = ceil(blueColor) - (quad2.y * 8.0);
// 計(jì)算R、G通道
highp vec2 texPos1;
texPos1.x = (quad1.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r);
texPos1.y = (quad1.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g);
highp vec2 texPos2;
texPos2.x = (quad2.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r);
texPos2.y = (quad2.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g);
// 根據(jù)轉(zhuǎn)換后的紋理坐標(biāo),在基準(zhǔn)圖上取色
lowp vec4 newColor1 = texture2D(InputImageTexture2, texPos1);
lowp vec4 newColor2 = texture2D(InputImageTexture2, texPos2);
// 對(duì)計(jì)算出來的兩個(gè)色值,線性求平均(fract:取小數(shù)點(diǎn)后值)
lowp vec4 newColor = mix(newColor1, newColor2, fract(blueColor));
// intensity 按需計(jì)算濾鏡透明度,混合計(jì)算前后的色值
gl_FragColor = mix(textureColor, vec4(newColor.rgb, textureColor.w), intensity);
}
注意
坐標(biāo)系
UIKit坐標(biāo)系Y軸朝下,CoreGraphics、OpenGL坐標(biāo)系Y軸朝上,兩者顛倒,所以在做圖片生成紋理的時(shí)候要注意坐標(biāo)系變換。
為了正確渲染圖片到UIKit上,圖片生成紋理的時(shí)候在CoreGraphics做了Transform轉(zhuǎn)換操作。然而我們基準(zhǔn)圖在生成紋理的時(shí)候不需要做轉(zhuǎn)換操作,否則會(huì)導(dǎo)致基準(zhǔn)圖顛倒,色值查找錯(cuò)誤。
- (GLuint)texture:(BOOL)needTransform {
CGImageRef imageRef = self.CGImage;
GLint width = (GLint)CGImageGetWidth(imageRef);
GLint height = (GLint)CGImageGetHeight(imageRef);
CGRect rect = CGRectMake(0, 0, width, height);
CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
void * imageData = malloc(width * height * 4);
CGContextRef contextRef = CGBitmapContextCreate(imageData, width, height, 8, 4 * width, colorSpaceRef, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGColorSpaceRelease(colorSpaceRef);
if (needTransform) {
CGContextTranslateCTM(contextRef, 0, height);
CGContextScaleCTM(contextRef, 1.0, -1.0);
}
CGContextDrawImage(contextRef, rect, imageRef);
GLuint textureID;
glGenBuffers(1, &textureID);
glBindTexture(GL_TEXTURE_2D, textureID);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageData);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glBindTexture(GL_TEXTURE_2D, 0);
CGContextRelease(contextRef);
free(imageData);
return textureID;
}
為了解決渲染圖片方向問題,我們可以有兩種方式解決:
圖片紋理可以進(jìn)行Transform,基準(zhǔn)圖紋理不Transform
// glsl.vsh
attribute vec4 Position;
attribute vec2 InputTextureCoordinate;
varying vec2 TextureCoordinate;
uniform mat4 MVP;
void main (void) {
gl_Position = MVP * Position;
TextureCoordinate = InputTextureCoordinate;
}
圖片和基準(zhǔn)圖都不進(jìn)行Transform,使用轉(zhuǎn)換矩陣MVP
// 模型矩陣
GLKMatrix4 model = GLKMatrix4MakeScale(1.0, -1.0, 0.0);
// 觀察矩陣
GLKMatrix4 view = GLKMatrix4MakeLookAt(0.0, 0.0, 3.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
// 正交投影矩陣
GLKMatrix4 project = GLKMatrix4MakeOrtho(-1.0, 1.0, -1.0, 1.0, 0.1, 100);
GLKMatrix4 mvp = GLKMatrix4Identity;
mvp = GLKMatrix4Multiply(mvp, project);
mvp = GLKMatrix4Multiply(mvp, view);
mvp = GLKMatrix4Multiply(mvp, model);
glUniformMatrix4fv(_mvpUniform, 1, GL_FALSE, (GLfloat *)&mvp);
最終效果
源碼地址
https://github.com/dpplh/OpenGL_ES_DEMO
作者:dpplh
來源:https://juejin.im/post/5eeb83b76fb9a0584d4f24da

技術(shù)交流,歡迎加我微信:ezglumes ,拉你入技術(shù)交流群。
推薦閱讀:
推薦幾個(gè)堪稱教科書級(jí)別的 Android 音視頻入門項(xiàng)目
覺得不錯(cuò),點(diǎn)個(gè)在看唄~

