最終可以本身經過OpenGL或者藉助一些三方庫GPUImage寫一些簡單的濾鏡、特效shader,明白原理和整個流程git
OpenGL
是各個平臺的統稱,移動端的是OpenGL ES
,web端的是WebGL
github
(備註:下文將OpenGL ES將簡稱OpenGL
)web
爲何用OpenGL算法
CPU
和GPU
的數據交換定義了緩存(buffer),由於從一個內存區域複製到另外一個內存區域的速度是相對較慢的,而且在內存複製的過程當中,CPU 和 GPU 都不能處理這區域內存下圖是一個移動設備圖像渲染框架草圖: 編程
在進入主題以前,咱們再來了解下圖片渲染到屏幕的過程,這將有助於瞭解OpenGL在濾鏡特效中的做用 小程序
V-Sync
,GPU拿到位圖會作一些圖層的渲染、紋理合成等工做。再把結果放到幀緩衝區
中(Frame Buffer)正是由於有了OpenGL的存在,咱們才能夠對圖像、視頻作不少有意思的處理,而這一部分離不開OpenGL中的着色器——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 ES頂點座標 紋理座標
有了上面shader的鋪墊,那能夠思考下濾鏡和特效怎麼實現的了?
顏色濾鏡通常不須要用到頂點着色器
在 app 內利用各類圖形算法能夠對圖片進行一些變換,這樣的效果也稱爲「濾鏡」,濾鏡效果大體能夠分爲如下幾類:
最簡單的濾鏡就是第1點:獨立像素點變換,也能夠叫作顏色濾鏡,最主要的技術就是ColorLUT
下圖是一個標準的顏色查找表
在一張表中爲每種顏色(總共255 * 255 * 255)記錄一個對應的映射目標顏色。當用【顏色查找表】對一張照片作顏色映射時,只須要遍歷照片的每一個像素點,而後在表中找到該像素顏色對應的目標顏色,最後將該像素設置爲目標顏色便可
好比原始顏色是紅色(r:255,g:0,b:0),進行轉換後變爲綠色(r:0,g:255,b:0),之後全部是紅色的地方都會被自動轉換爲綠色。而顏色查找表就是將全部的顏色進行一次(矩陣)轉換,而不少的濾鏡功能就是提供了這麼一個轉換的矩陣,在原始色彩的基礎上進行顏色的轉換
左上角第一個像素表明位於(0,0,0)的點,第二個像素表明位於(85,0,0)的點
來看一個簡單的列子,灰度濾鏡的實現(不須要使用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);
}
複製代碼
若是是設計師給的顏色查找表.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));
}
複製代碼
這裏主要結合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];
}
}
複製代碼
學習資料: