基於openGL中shader聊濾鏡特效的原理和實際應用

目標

最終可以本身經過OpenGL或者藉助一些三方庫GPUImage寫一些簡單的濾鏡、特效shader,明白原理和整個流程git

OpenGL簡介

OpenGL是各個平臺的統稱,移動端的是OpenGL ES,web端的是WebGLgithub

(備註:下文將OpenGL ES將簡稱OpenGL)web

爲何用OpenGL算法

  • 定義了一套平臺無關的圖形操做API,提供了訪問GPU的能力。
  • CPUGPU的數據交換定義了緩存(buffer),由於從一個內存區域複製到另外一個內存區域的速度是相對較慢的,而且在內存複製的過程當中,CPU 和 GPU 都不能處理這區域內存

下圖是一個移動設備圖像渲染框架草圖: 編程

在進入主題以前,咱們再來了解下圖片渲染到屏幕的過程,這將有助於瞭解OpenGL在濾鏡特效中的做用 小程序

  • CPU:繪製紋理圖片而後交給GPU渲染,也就是位圖
  • GPU: 等待垂直同步信號V-Sync,GPU拿到位圖會作一些圖層的渲染、紋理合成等工做。再把結果放到幀緩衝區中(Frame Buffer)
  • 視頻控制器: 根據V-Sync信號,在指定時間以前,提取幀緩衝區的屏幕顯示內容,最終顯示到顯示器。

正是由於有了OpenGL的存在,咱們才能夠對圖像、視頻作不少有意思的處理,而這一部分離不開OpenGL中的着色器——Shader,下面就來看看數組

什麼是Shader

OpenGL中,任何事物都在3D空間中,而屏幕和窗口倒是2D像素數組,這致使OpenGL的大部分工做都是關於把3D座標轉變爲適應你屏幕的2D像素。緩存

3D座標轉爲2D座標的處理過程是由OpenGL的圖形渲染管線管理的,圖形渲染管線能夠被劃分爲幾個階段,每一個階段將會把前一個階段的輸出做爲輸入 app

OpenGL圖形渲染管線的每個階段運行着各自的小程序,這些小程序叫作 着色器(Shader)。通常以字符串的方式在代碼中使用,目前OpenGL中只有 vertex shader(頂點着色器)fragment shader(片斷着色器)是可編程的。GLSL是是OpenGL用來編寫着色器(shader)的高級語言,它不是運行在CPU而是GPU

下面來看看OpenGL世界中的"Hello world"(三角形)怎麼實現.框架

這裏主要說下頂點着色器和片斷着色器

  • 頂點着色器

若是須要對圖像進行縮放變化,好比放大,縮小,移動效果,則須要對頂點着色器從新編程,默認頂點着色器代碼以下

attribute vec4 Position; // 頂點座標
attribute vec2 TextureCoords; // 紋理座標
varying vec2 TextureCoordsVarying;//片斷着色器的輸入(紋理座標)

void main (void) {
    gl_Position = Position;
    TextureCoordsVarying = TextureCoords;
}

複製代碼
  • 片斷着色器

若是須要對原始圖像最終輸出的顏色進行調整,則須要對片斷着色器從新編程

precision mediump float;

uniform sampler2D Texture;//紋理採樣器
varying vec2 TextureCoordsVarying; //頂點着色器傳過來的紋理座標

void main (void) {
    //圖元的每一個頂點各自對應紋理座標,用來標明該從紋理圖像的哪一個部分採樣
    vec4 mask = texture2D(Texture, TextureCoordsVarying);//得到紋理座標相應位置的顏色
    gl_FragColor = vec4(mask.rgb, 1.0);
}


複製代碼

用頂點着色器和片斷着色器能夠寫出各類各類樣的opengl程序

  • OpenGL中座標系(可選)

瞭解OpenGL中的座標系,有助於更好的瞭解openGL渲染管線和做業的流程:

友情連接:OpenGL ES頂點座標 紋理座標

有了上面shader的鋪墊,那能夠思考下濾鏡和特效怎麼實現的了?

濾鏡的原理和實現

顏色濾鏡通常不須要用到頂點着色器

什麼是濾鏡

在 app 內利用各類圖形算法能夠對圖片進行一些變換,這樣的效果也稱爲「濾鏡」,濾鏡效果大體能夠分爲如下幾類:

  1. 獨立像素點變換:包括亮度、對比、飽和度、色調、灰色化、分離RGB通道等
  2. 像素卷積變換:包括邊緣檢測、浮雕化、模糊、銳化(好比美顏)
  3. 仿射矩陣變換: 包括縮放、旋轉、傾斜、扭曲、液化等()
  4. 多圖像合成 其中最簡單的就是進行獨立像素點變換,

最簡單的濾鏡就是第1點:獨立像素點變換,也能夠叫作顏色濾鏡,最主要的技術就是ColorLUT

ColorLUT

將左邊狗子🐶圖變爲右邊效果圖,大概的作法就是把左圖的每一個像素點根據相應的計算公式計算獲得一個新的像素點便可,那麼如何定義這個計算公式呢? 顏色查找表(簡稱ColorLUT)就是幹這事兒的,它是實現濾鏡最主要的技術

下圖是一個標準的顏色查找表

實現濾鏡的原理:

在一張表中爲每種顏色(總共255 * 255 * 255)記錄一個對應的映射目標顏色。當用【顏色查找表】對一張照片作顏色映射時,只須要遍歷照片的每一個像素點,而後在表中找到該像素顏色對應的目標顏色,最後將該像素設置爲目標顏色便可

好比原始顏色是紅色(r:255,g:0,b:0),進行轉換後變爲綠色(r:0,g:255,b:0),之後全部是紅色的地方都會被自動轉換爲綠色。而顏色查找表就是將全部的顏色進行一次(矩陣)轉換,而不少的濾鏡功能就是提供了這麼一個轉換的矩陣,在原始色彩的基礎上進行顏色的轉換

如何生成顏色查找表

  1. 用PS或其它濾鏡軟件打開原圖。
  2. 對原圖作曲線,色彩平衡、色調等調整。
  3. 調整適合後,對Lookup Table作相同的操做。
  4. 調整好Lookup Table後,導出新的Lookup Table,而後在程序中使用。

如何查找顏色表

每一個像素的色彩都是由RGB三種顏色組成,若是以三維陣列來存儲,咱們很很差處理,因此要把Z軸單獨拎出來,拼接成爲一個8x8的二緯圖片,相近的顏色採用一條記錄存儲

左上角第一個像素表明位於(0,0,0)的點,第二個像素表明位於(85,0,0)的點

具體的實現

  • demo1:

來看一個簡單的列子,灰度濾鏡的實現(不須要使用ColorLUT):

圖片的顯示由三個顏色通道(rgb)決定,而灰度濾鏡全部通道的值相同

precision highp float;
uniform sampler2D Texture;
varying vec2 TextureCoordsVarying;
//灰度計算比率 (借用GPUImage的值)
const highp vec3 ratio = vec3(0.2125, 0.7154, 0.0721);
void main (void) {
    vec4 mask = texture2D(Texture, TextureCoordsVarying);
    // Gray值
    float luminance = dot(mask.rgb, ratio);
    gl_FragColor = vec4(vec3(luminance), 1.0);
}

複製代碼
  • demo2:

若是是設計師給的顏色查找表.png怎麼用, 這裏經過GPUImage中的顏色查找濾鏡GPUImageLookupFilter(就是用來處理顏色查找表和原圖的)來看下這部分着色器處理的代碼

NSString *const kGPUImageLookupFragmentShaderString = SHADER_STRING
(
 varying highp vec2 textureCoordinate;

 uniform sampler2D inputImageTexture;
 uniform sampler2D inputImageTexture2; // lookup texture
 
 uniform lowp float intensity;

 void main()
 {
     highp vec4 textureColor = texture2D(inputImageTexture, textureCoordinate);
     //藍色通道,textureColor.b的範圍爲(0,1),blueColor範圍爲(0,63)
     highp float blueColor = textureColor.b * 63.0;

     //根據B通道獲取小正方形格子(64x64格子)
     highp vec2 quad1;  
     quad1.y = floor(floor(blueColor) / 8.0);
     quad1.x = floor(blueColor) - (quad1.y * 8.0);
     
     //quad2爲大於且最靠近要查找顏色所在位置的小正方形
     highp vec2 quad2;
     quad2.y = floor(ceil(blueColor) / 8.0);
     quad2.x = ceil(blueColor) - (quad2.y * 8.0);
     
     highp vec2 texPos1;

     //由於一行有8個小正方形,因此小正方形的邊長,轉換爲紋理座標時,就是0.125。quad1的位置就是quad1.x * 0.125和quad1.y * 0.125。

     //根據小正方形格子和RG通道,獲取紋理座標,每一個大格子的大小:1/8=0.125,每一個小格子的大小:1/512
     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;
     //quad2和quad1差很少
     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); 
     
      //真正的顏色在newColor1和newColor2之間。fract是取分數部分。
     lowp vec4 newColor = mix(newColor1, newColor2, fract(blueColor));
     gl_FragColor = mix(textureColor, vec4(newColor.rgb, textureColor.w), intensity);
 }
);
複製代碼

僞代碼

precision mediump float;
 varying highp vec2 textureCoordinate;
 uniform sampler2D inputImageTexture; //原圖紋理
 uniform sampler2D inputImageTexture2; // lookup texture
 
void main (void) {
    //圖元的每一個頂點各自對應紋理座標,用來標明該從紋理圖像的哪一個部分採樣
    lowp vec4 newColor1 = texture2D(inputImageTexture2, texPos1);
     lowp vec4 newColor2 = texture2D(inputImageTexture2, texPos2); 
      //真正的顏色在兩個顏色之間(按照必定權重進行混合)
     lowp vec4 newColor = mix(orginColor(原圖紋理顏色), LUTColor(顏色查找表), 權重);
     gl_FragColor = mix(textureColor, vec4(newColor.rgb, textureColor.w), intensity);
}

複製代碼

特效的原理和實現

特效的實現其實也是用頂點着色器和片斷着色器,只不過從複雜度上來講比濾鏡相對複雜些,能夠用到更多驚豔的效果,顏色濾鏡以外的均可以算做特效

好比來看一個比較簡單的【分屏

原理其實就是去改變紋理座標在y軸的偏移位置

// 精度
precision highp float;
// 經過uniform傳遞過來的紋理
uniform sampler2D Texture;
// 紋理座標
varying highp vec2 varyTextureCoord;

void main() {
    
    vec2 uv = varyTextureCoord.xy;
    float y;
    // 0.0~0.5 範圍內顯示0.25~0.75範圍的像素
    if (uv.y >= 0.0 && uv.y <= 0.5) {
        y = uv.y + 0.25;
    }else {
        // 0.5~1.0範圍內顯示 0.25~0.75範圍的像素
        y = uv.y - 0.25;
    }
    
    // 獲取紋理像素,用於顯示
    gl_FragColor = texture2D(Texture, vec2(uv.x, y));
}

複製代碼

在iOS中的應用

這裏主要結合GPUImage來簡單講濾鏡、特效在音視頻應用中的實際使用,因爲GPUImage不是重點,因此有個簡單瞭解就能夠了

整個庫的總體目錄分層結構以下:

以抖音爲列,咱們來看看濾鏡、特效在音視頻應用中的使用方案

  • 相機拍攝界面

若是添加了濾鏡,本質其實就是對採集到的每一幀CMSampleBufferRef運用ColorLUT

  • 特效處理界面

這裏主要講講特效部分的預覽和合成

視屏最終合成

關於特效處理部分僞代碼以下

- (void)setUpExportEnvironment {
    self.exportRenderer = [[GPUImageMovie alloc] initWithURL:_exportModel.localVideoURL];
    //鏈式疊加
    self.moveWriter = [[GPUImageMovieWriter alloc] initWithMovieURL: '原視頻文件路徑'] 
    
    self.passFilter = [[GPUImageFilter alloc] init];
    [self.exportRenderer enableSynchronizedEncodingUsingMovieWriter:self.moveWriter];
    [self.exportRenderer addTarget:self.passFilter];
   //當前處理時間
    [self.passFilter setFrameProcessingCompletionBlock:^(GPUImageOutput *output, CMTime time) {
        [weakSelf mixEffectAt:time];
    }];
    
- (void)mixEffectAt:(CMTime)time {
       [self.moveWriter setPaused:YES];
        curActiveEffectModel.isUsing = YES;
        //選擇對應的特效
        [_effect switch_effect:curActiveEffectModel.pictureEffectType];
        [self.moveWriter setPaused:NO];
    }
}

複製代碼

其餘

  • 貼紙
  • 美顏

學習資料:

相關文章
相關標籤/搜索