1. 程式人生 > Android開發 >OpenGL ES之LUT(濾鏡基準圖)

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)表示純紅色,同理RGB(0,255,0)表示純綠色,RGB(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,kCGImageAlphaPremultipliedLast);
CGContextDrawImage(contextRef,CGRectMake(0,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,height);
    
    CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
    void * imageData = malloc(width * height * 4);
    CGContextRef contextRef = CGBitmapContextCreate(imageData,4 * width,kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
    
    CGColorSpaceRelease(colorSpaceRef);
    
    if (needTransform) {
        CGContextTranslateCTM(contextRef,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,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_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);
// 正交投影矩陣
GLKMatrix4 project = GLKMatrix4MakeOrtho(-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

檢視完整程式碼