iOS 着色器滤镜
作为智能机照相和摄像功能无疑是其重要的功能,从比较早期使用实时滤镜的path到后期迎头赶上的instagram,他们出彩了滤镜效果和流畅的性能无疑给软件增色不少,而其中最大的功臣就要归功于opengles 2.x之后引入的着色器了。
滤镜原理 其实滤镜归根到底就是在照相时,取得每一帧,将取到的这一帧数据的首地址取出,我们就获得了访问每一个像素点的能力,然后在对每个点根据相应的算法来进行变化,就可以得到我们想要的一些效果
如何取得视频数据? 取得照相时的图片数据 加入Framework AVFoundation _session = [[AVCaptureSession alloc] init];
_videoOutput = [[[AVCaptureVideoDataOutput alloc] init] autorelease];
if ([_session canAddOutput:_videoOutput]) {
[_session addOutput:_videoOutput];
}
dispatch_queue_t videoCaptureQueue = dispatch_queue_create("video_capture_queue_",DISPATCH_QUEUE_SERIAL);
[_videoOutput setSampleBufferDelegate:self queue:videoCaptureQueue];
[_session startRunning];
在session start之后我们就可以通过回调 来得到 sbuf
- (void)captureOutput:(AVCaptureOutput *)captureOutput
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
fromConnection:(AVCaptureConnection *)connection{
...
OSStatus err = CMBufferQueueEnqueue(previewBufferQueue, sampleBuffer);
if ( !err ) {
dispatch_async(dispatch_get_main_queue(), ^{
CMSampleBufferRef sbuf = (CMSampleBufferRef)CMBufferQueueDequeueAndRetain(previewBufferQueue);
});
}
...
}
拿到sbuf之后 我们就可以通过以下的方法来获取图片的首地址
CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
CVPixelBufferLockBaseAddress( pixelBuffer, 0 );
int len = CVPixelBufferGetDataSize(pixelBuffer);
UInt8 *pixel = (UInt8 *)CVPixelBufferGetBaseAddress(pixelBuffer);
_render(pixel,len) //滤镜函数
这样其实就可以简单的实现照相时候的实时滤镜,当然作为高性能的标题,肯定实现不会这么简单,大家可以尝试一下这个方法的效果,我之前尝试的时候原本60帧左右的摄像,对rgb某个单通道操作影响不大,但是一旦操作到三个通道,降帧就很明显,更不用说在摄像时同时还要录音了。这时我们要解决这个问题就需要我们下一节来认识着色器。
认识着色器
用过opengl的同学对着色器 应该不会陌生,但对与iOS终端设备的开发者,对于图形图像有着深入了解的人不多,本人也只是个初学者,对于opengl es 2.x才引入的着色器也是初步认识,WWDC的专题讲座有一节就是讲opengl es的新特性的 结合AVFoundation那一节 我们还是可以学到不少东西的。
opengl es的着色器 有.fsh和.vsh两个文件 这两个文件在被编译和链接后就可以产生可执行程序 与GPU交互 .vsh 是顶点shader 用与顶点计算 可以理解控制顶点的位置 在这个文件中我们通常会传入当前顶点的位置,和纹理的坐标
例如 attribute vec4 position;
attribute vec4 inputTextureCoordinate;
varying vec2 textureCoordinate;
precision mediump float;
uniform float overTurn;
void main()
{
gl_Position = position;
if (overTurn>0.0) {
textureCoordinate = vec2(inputTextureCoordinate.x,overTurn-inputTextureCoordinate.y);
}
else
textureCoordinate = vec2(inputTextureCoordinate.x,inputTextureCoordinate.y);
}
attribute 外部传入vsh文件的变量 每帧的渲染的可变参数 变化率高 用于定义每个点
varying 用于 vsh和fsh之间相互传递的参数
precision mediump float 定义中等精度的浮点数
uniform 外部传入vsh文件的变量 变化率较低 对于可能在整个渲染过程没有改变 只是个常量
在main()当overTurn大于0的时候 函数里面做的事情就是将纹理y轴反转
.fsh 是片段shader 在这里面我可以对于每一个像素点进行重新计算 从而达到我们滤镜效果
varying highp vec2 textureCoordinate;
precision mediump float;
uniform sampler2D videoFrame;
vec4 memoryRender(vec4 color)
{
float gray;
gray = color.r*0.3+color.g*0.59+color.b*0.11;
color.r = gray;
color.g = gray;
color.b = gray;
color.r += color.r*1.5;
color.g = color.g*2.0;
if(color.r > 255.0)
color.r = 255.0;
if(color.g > 255.0)
color.g = 255.0;
return color;
}
void main()
{
vec4 pixelColor;
pixelColor = texture2D(videoFrame, textureCoordinate);
gl_FragColor = memoryRender(pixelColor);
}
varying highp vec2 textureCoordinate 就是从vsh中传过来了纹理坐标
uniform sampler2D videoFrame 是我们真正的纹理贴图
texture2D(videoFrame, textureCoordinate) 将纹理中的每个像素点颜色取出到pixelColor
我们可以用memoryRender(pixelColor)将我们取到像素点重新加工
总结:
对于着色器的编程并不是很困难,完全是C的语法 但是debug比较麻烦,对于类型的约束也很严格 必须是相同类型的才能进行算术运算。我们理解了这两个文件之后 就可以发挥我们的想象对纹理图片做出各种的处理。 了解如何编写着色器之后 我们就要在下篇开始 了解如何把AVFoundation中取到的图片帧来与我们的着色器结合,从来绘制出高性能的滤镜。
【iOS 着色器滤镜】
推荐阅读
- Docker应用:容器间通信与Mariadb数据库主从复制
- 人生感悟记#环境仪器宋庆国成长记#072
- 2020-04-07vue中Axios的封装和API接口的管理
- 标签、语法规范、内联框架、超链接、CSS的编写位置、CSS语法、开发工具、块和内联、常用选择器、后代元素选择器、伪类、伪元素。
- iOS中的Block
- 视频转换器哪种好用()
- NeuVector 会是下一个爆款云原生安全神器吗()
- 操作系统|[译]从内部了解现代浏览器(1)
- 探索免费开源服务器tomcat的魅力
- 记录iOS生成分享图片的一些问题,根据UIView生成固定尺寸的分享图片