OpenGL ES之LUT(滤镜基准图)

未标题-2

前言

Look Up Table(简称LUT,查找表)。输入一个值,然后通过查找表来得到一个输出值。在调色领域中,称为颜色查找表,查找表的分量为R、G、B,是一种降低GPU运算量的技术,通过将颜色值存储在一张表中,在需要的时候通过索引在这张表上找到对应的颜色值。这是一种使用空间换时间的算法。

LUT分为1D和3D,本质的区别在于索引的输出所需要的索引数

从RGB色彩讲起

我们知道光的三原色是红(Red)、绿(Green)、蓝(Blue),也就是RGB,我们将这三种光按不同比例混合就可以得到丰富的色彩。我们规定R、G、B三者的取值范围为[0, 255],0表示不发光,255表示发出最强的光,因此RGB(255, 0, 0)表示纯红色,同理RGB(0, 255, 0)表示纯绿色,RGB(0, 0, 255)纯蓝色。

3D LUT

在滤镜的LUT效果应用中,通常是将用3D LUT预存效果的RGB值。在8bit的RGB色域空间中,每个分量的取值范围为[0, 255],一张完整的色域空间就为256 * 256 * 256 = 16581375bit = 16M。但实际上并不需要那么多颜色,通常会列举节点来储存,两个节点之间的颜色通过线性插值得出。

LUT3D-s

1、表现形式

3D LUT的储存就是一堆RGB数据,它可以使用以下三种方式进行表示:

  • 1、三维数组
  • 2、颜色方块
LUT-3D3
  • 3、颜色图片
LUT 1D

2、颜色图片

颜色图片的本质就是将颜色方块进行二维化处理。上述颜色图片分辨率为512*512,里面有8*8的大格子,每个大格子中存有64*64个小格子,用来存储色彩像素点。

每个小格子如下图所示,X轴表示[0, 255]的R通道,Y轴表示[0, 255]的G通道。B通道分量放在了8*8的大格子中,从左到右从上到下,最后将RGB三个分量叠加

一张颜色图片一功能储存64 * 64 * 64 = 512 * 512 = 262144种色彩

3、输出一张标准LUT图片

  • 1、创建RGBA原始数据
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个单位, +0.5做四舍五入运算
                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、写入数据,生成图片
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实现LUT滤镜

制作滤镜基准图

  • 1、 将上述的LUT图片和需要上效果的图片拖进Photoshop
ps1
  • 2、隐藏LUT图片,进行色彩调整,这里进行了色相、自然饱和度的调整,并调整了曲线,拉高阴影处的曲线。
ps2
  • 3、隐藏上效果图片,导出LUT图片
LUT

Coding

输入双纹理(如果对于纹理输入绘制不清楚的可以看OpenGL ES之纹理渲染),InputImageTexture为输入的需要应用效果的图片纹理,InputImageTexture2为基准图纹理。

// glsl.fsh
precision mediump float;

uniform sampler2D InputImageTexture2;
uniform sampler2D InputImageTexture;
uniform lowp float intensity;

varying vec2 TextureCoordinate;

void main() {
    // 取出当前像素的纹素
    highp vec4 textureColor = texture2D(InputImageTexture, TextureCoordinate);
    
    highp float blueColor = textureColor.b * 63.0;
    
    // 计算B通道,看使用哪个像素色块(这里分别对计算结果向上,向下取整,然后再对两者进行线性计算,减小误差)
    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);
    
    // 计算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);
    
    // 根据转换后的纹理坐标,在基准图上取色
    lowp vec4 newColor1 = texture2D(InputImageTexture2, texPos1);
    lowp vec4 newColor2 = texture2D(InputImageTexture2, texPos2);
    
    // 对计算出来的两个色值,线性求平均(fract:取小数点后值)
    lowp vec4 newColor = mix(newColor1, newColor2, fract(blueColor));
    
    // intensity 按需计算滤镜透明度,混合计算前后的色值
    gl_FragColor = mix(textureColor, vec4(newColor.rgb, textureColor.w), intensity);
}

注意

1、坐标系

UIKit坐标系Y轴朝下,CoreGraphics、OpenGL坐标系Y轴朝上,两者颠倒,所以在做图片生成纹理的时候要注意坐标系变换

坐标系

OpenGL ES之纹理渲染文章中,为了正确渲染图片到UIKit上,图片生成纹理的时候在CoreGraphics做了Transform转换操作。然而我们基准图在生成纹理的时候不需要做转换操作,否则会导致基准图颠倒,色值查找错误

- (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;
}

为了解决渲染图片方向问题,我们可以有两种方式解决:

  • 1、图片纹理可以进行Transform,基准图纹理不Transform
  • 2、图片和基准图都不进行Transform,使用转换矩阵MVP
// glsl.vsh
attribute vec4 Position;
attribute vec2 InputTextureCoordinate;

varying vec2 TextureCoordinate;
uniform mat4 MVP;

void main (void) {
    gl_Position = MVP * Position;
    TextureCoordinate = InputTextureCoordinate;
}
// 模型矩阵
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);

最终效果

未标题-1

查看完整代码

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342