[toc]
前言
通过自定义着色器实现分屏滤镜功能.
准备工作
- 导入相关头文件
#import "FilterBar.h"
#import <GLKit/GLKit.h>
FilterBar
为一个选择Bar
,用来选择不同的分屏.
- 定义顶点的结构体和常用的属性
typedef struct {
GLKVector4 positionCoord;
GLKMatrix2 textureCoord;
}FilterVertex;
@interface ViewController ()<FilterBarDelegate>
@property(nonatomic, assign) FilterVertex *vertexs;
@property(nonatomic, strong) EAGLContext *context;//上下文
@property(nonatomic, strong) CADisplayLink *displayLink;//用于刷新屏幕
@property(nonatomic, assign) NSTimeInterval timeInterval;//开始的时间戳
@property(nonatomic, assign) GLuint vertexBuffer;//顶点缓存
@property(nonatomic, assign) GLuint textureID;//顶点ID
@property (nonatomic, assign) GLuint program;//着色器程序
FilterBarDelegate
代理用来实现点击不同分屏的回调.
FilterVertex
用来保存顶点坐标和纹理坐标.
- 初始化``用来选择不同的分屏功能
- (void)setUpFilterBar{
CGFloat filterBarWidth = [UIScreen mainScreen].bounds.size.width;
CGFloat filterBarHeight = 100;
CGFloat filterBarY = [UIScreen mainScreen].bounds.size.height - filterBarHeight;
FilterBar *filerBar = [[FilterBar alloc] initWithFrame:CGRectMake(0, filterBarY, filterBarWidth, filterBarHeight)];
filerBar.delegate = self;
[self.view addSubview:filerBar];
NSArray *dataSource = @[@"无",@"分屏_2",@"分屏_3",@"分屏_4",@"分屏_6_横",@"分屏_6_竖",@"分屏_9"];
filerBar.itemList = dataSource;
}
``
* 初始化滤镜相关信息
```c
- (void)filterInit{
//初始化上下文
self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
[EAGLContext setCurrentContext:self.context];
// 设置layer
CAEAGLLayer *layer = [[CAEAGLLayer alloc] init];
layer.frame = CGRectMake(0, 50, self.view.frame.size.width, self.view.frame.size.height-150);
layer.contentsScale = [[UIScreen mainScreen] scale];
[self.view.layer addSublayer:layer];
//绑定到渲染缓冲区
[self binrenderlayer:layer];
//初始化顶点和纹理坐标
self.vertexs = (FilterVertex *)malloc(sizeof(FilterVertex) * 4);
self.vertexs[0] = (FilterVertex){{-1, 1, 0}, {0, 1}};
self.vertexs[1] = (FilterVertex){{-1, -1, 0}, {0, 0}};
self.vertexs[2] = (FilterVertex){{1, 1, 0}, {1, 1}};
self.vertexs[3] = (FilterVertex){{1, -1, 0}, {1, 0}};
//设置顶点缓冲区
GLuint vertextBuffer;
glGenBuffers(1, &vertextBuffer);
glBindBuffer(GL_ARRAY_BUFFER, vertextBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(FilterVertex)*4, self.vertexs, GL_STATIC_DRAW);
self.vertexBuffer = vertextBuffer;
//获取处理图片的路径
NSString *imagePath = [[NSBundle mainBundle] pathForResource:@"IU1" ofType:@"JPG"];
//得到纹理
GLuint texture2D = [self createTextureWithImageFilePath:imagePath];
// 设置纹理ID
self.textureID = texture2D;
//设置默认着色器
[self setupNormalShaderProgram];
//设置视口
glViewport(0, 0, [self drawableWidth], [self drawableHeight]);
}
- 初始化上下文,选择
kEAGLRenderingAPIOpenGLES2
- 设置layer的大小和
frame
- 绑定到渲染缓冲区
- 初始化顶点和纹理坐标
- 设置顶点缓冲区
- 获取处理图片的路径
- 得到纹理,并用全局变量保存
- 设置默认着色器
- 设置视口
binrenderlayer
- 并设置
renderbuffer
和framebuffer
-
layer
遵守<EAGLDrawable>
代理协议
(void)binrenderlayer:(CAEAGLLayer *)layer{
//创建渲染缓冲区 帧缓冲区
GLuint renderBuffer;
GLuint frameBuffer;
//获取帧渲染缓存区名称,绑定渲染缓存区以及将渲染缓存区与layer建立连接
glGenRenderbuffers(1, &renderBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, renderBuffer);
[self.context renderbufferStorage:GL_RENDERBUFFER fromDrawable:layer];
//获取帧缓存区名称,绑定帧缓存区以及将渲染缓存区附着到帧缓存区上
glGenFramebuffers(1, &frameBuffer);
glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderBuffer);
}
createTextureWithImageFilePath
//从图片中加载纹理
- (GLuint)createTextureWithImageFilePath:(NSString *)filePath{
//加载图片
UIImage *image = [UIImage imageWithContentsOfFile:filePath];
//1、将 UIImage 转换为 CGImageRef
CGImageRef cgImageRef = [image CGImage];
//判断图片是否获取成功
if (!cgImageRef) {
NSLog(@"Failed to load image");
exit(1);
}
//2、读取图片的大小,宽和高
GLuint width = (GLuint)CGImageGetWidth(cgImageRef);
GLuint height = (GLuint)CGImageGetHeight(cgImageRef);
//获取图片的rect
CGRect rect = CGRectMake(0, 0, width, height);
//获取图片的颜色空间
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
//3.获取图片字节数 宽*高*4(RGBA)
void *imageData = malloc(width * height * 4);
//4.创建上下文
/*
参数1:data,指向要渲染的绘制图像的内存地址
参数2:width,bitmap的宽度,单位为像素
参数3:height,bitmap的高度,单位为像素
参数4:bitPerComponent,内存中像素的每个组件的位数,比如32位RGBA,就设置为8
参数5:bytesPerRow,bitmap的没一行的内存所占的比特数
参数6:colorSpace,bitmap上使用的颜色空间 kCGImageAlphaPremultipliedLast:RGBA
*/
CGContextRef context = CGBitmapContextCreate(imageData, width, height, 8, width * 4, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
//将图片翻转过来(图片默认是倒置的)
CGContextTranslateCTM(context, 0, height);
CGContextScaleCTM(context, 1.0f, -1.0f);
CGColorSpaceRelease(colorSpace);
CGContextClearRect(context, rect);
//对图片进行重新绘制,得到一张新的解压缩后的位图
CGContextDrawImage(context, rect, cgImageRef);
//设置图片纹理属性
//5. 获取纹理ID
GLuint textureID;
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_2D, textureID);
//6.载入纹理2D数据
/*
参数1:纹理模式,GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D
参数2:加载的层次,一般设置为0
参数3:纹理的颜色值GL_RGBA
参数4:宽
参数5:高
参数6:border,边界宽度
参数7:format
参数8:type
参数9:纹理数据
*/
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageData);
//7.设置纹理属性
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
//8.绑定纹理
/*
参数1:纹理维度
参数2:纹理ID,因为只有一个纹理,给0就可以了。
*/
glBindTexture(GL_TEXTURE_2D, 0);
//9.释放context,imageData
CGContextRelease(context);
free(imageData);
//10.返回纹理ID
return textureID;
}
setupShaderProgramWithName
将
shader
附着到着色程->链接->最后use
// 初始化着色器程序
- (void)setupShaderProgramWithName:(NSString *)name {
//1. 获取着色器program
GLuint program = [self programWithShaderName:name];
//2. use Program
glUseProgram(program);
//3. 获取Position,Texture,TextureCoords 的索引位置
GLuint positionSlot = glGetAttribLocation(program, "Position");
GLuint textureSlot = glGetUniformLocation(program, "Texture");
GLuint textureCoordsSlot = glGetAttribLocation(program, "TextureCoords");
//4.激活纹理,绑定纹理ID
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, self.textureID);
//5.纹理sample
glUniform1i(textureSlot, 0);
//6.打开positionSlot 属性并且传递数据到positionSlot中(顶点坐标)
glEnableVertexAttribArray(positionSlot);
glVertexAttribPointer(positionSlot, 3, GL_FLOAT, GL_FALSE, sizeof(FilterVertex), NULL + offsetof(FilterVertex, positionCoord));
//7.打开textureCoordsSlot 属性并传递数据到textureCoordsSlot(纹理坐标)
glEnableVertexAttribArray(textureCoordsSlot);
glVertexAttribPointer(textureCoordsSlot, 2, GL_FLOAT, GL_FALSE, sizeof(FilterVertex), NULL + offsetof(FilterVertex, textureCoord));
//8.保存program,界面销毁则释放
self.program = program;
}
programWithShaderName
获取路径 ->字符串->附着到
shaer
->编译
#pragma mark -shader compile and link
//将shader附着到着色程->链接->最后use
- (GLuint)programWithShaderName:(NSString *)shaderName {
//1. 编译顶点着色器/片元着色器
GLuint vertexShader = [self compileShaderWithName:shaderName type:GL_VERTEX_SHADER];
GLuint fragmentShader = [self compileShaderWithName:shaderName type:GL_FRAGMENT_SHADER];
//2. 将顶点/片元附着到program
GLuint program = glCreateProgram();
glAttachShader(program, vertexShader);
glAttachShader(program, fragmentShader);
//3.linkProgram
glLinkProgram(program);
//4.检查是否link成功
GLint linkSuccess;
glGetProgramiv(program, GL_LINK_STATUS, &linkSuccess);
if (linkSuccess == GL_FALSE) {
GLchar messages[256];
glGetProgramInfoLog(program, sizeof(messages), 0, &messages[0]);
NSString *messageString = [NSString stringWithUTF8String:messages];
NSAssert(NO, @"program链接失败:%@", messageString);
exit(1);
}
//5.返回program
return program;
}
compileShaderWithName
//编译shader代码
//获取路径 ->字符串->附着到shaer->编译
- (GLuint)compileShaderWithName:(NSString *)name type:(GLenum)shaderType {
//1.获取shader 路径
NSString *shaderPath = [[NSBundle mainBundle] pathForResource:name ofType:shaderType == GL_VERTEX_SHADER ? @"vsh" : @"fsh"];
NSError *error;
NSString *shaderString = [NSString stringWithContentsOfFile:shaderPath encoding:NSUTF8StringEncoding error:&error];
if (!shaderString) {
NSAssert(NO, @"读取shader失败");
exit(1);
}
//2. 创建shader->根据shaderType
GLuint shader = glCreateShader(shaderType);
//3.获取shader source
const char *shaderStringUTF8 = [shaderString UTF8String];
int shaderStringLength = (int)[shaderString length];
glShaderSource(shader, 1, &shaderStringUTF8, &shaderStringLength);
//4.编译shader
glCompileShader(shader);
//5.查看编译是否成功
GLint compileSuccess;
glGetShaderiv(shader, GL_COMPILE_STATUS, &compileSuccess);
if (compileSuccess == GL_FALSE) {
GLchar messages[256];
glGetShaderInfoLog(shader, sizeof(messages), 0, &messages[0]);
NSString *messageString = [NSString stringWithUTF8String:messages];
NSAssert(NO, @"shader编译失败:%@", messageString);
exit(1);
}
//6.返回shader
return shader;
}
drawableWidth&&drawableHeight
//获取渲染缓存区的宽
- (GLint)drawableWidth {
GLint backingWidth;
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &backingWidth);
return backingWidth;
}
//获取渲染缓存区的高
- (GLint)drawableHeight {
GLint backingHeight;
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &backingHeight);
return backingHeight;
}
FilterBarDelegate
通过重新加载自定义着色文件,重新编译链接.
调用startFilerAnimation
进行重绘
#pragma mark - FilterBarDelegate
- (void)filterBar:(FilterBar *)filterBar didScrollToIndex:(NSUInteger)index{
if (index == 0) {
[self setupNormalShaderProgram];
}else if (index == 1){
[self setupShaderProgramWithName:@"normal2"];
}else if (index == 2){
[self setupShaderProgramWithName:@"normal3"];
}else if (index==3){
[self setupShaderProgramWithName:@"normal4"];
}else if (index==4){
[self setupShaderProgramWithName:@"normal61"];
}else if (index==5){
[self setupShaderProgramWithName:@"normal6"];
}else if (index==6){
[self setupShaderProgramWithName:@"normal9"];
}
[self startFilerAnimation];
}
开启滤镜动画
- (void)startFilerAnimation{
if(self.displayLink){
[self.displayLink invalidate];
self.displayLink = nil;
}
self.timeInterval = 0;
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(timeAction)];
[self.displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}
执行动画刷新
//动画
- (void)timeAction{
//DisplayLink 的当前时间撮
if (self.timeInterval == 0) {
self.timeInterval = self.displayLink.timestamp;
}
//使用program
glUseProgram(self.program);
//绑定buffer
glBindBuffer(GL_ARRAY_BUFFER, self.vertexBuffer);
// 传入时间
CGFloat currentTime = self.displayLink.timestamp - self.timeInterval;
GLuint time = glGetUniformLocation(self.program, "Time");
glUniform1f(time, currentTime);
// 清除画布
glClear(GL_COLOR_BUFFER_BIT);
glClearColor(1, 1, 1, 1);
// 重绘
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
//渲染到屏幕上
[self.context presentRenderbuffer:GL_RENDERBUFFER];
}
释放
- (void)dealloc {
//1.上下文释放
if ([EAGLContext currentContext] == self.context) {
[EAGLContext setCurrentContext:nil];
}
//顶点缓存区释放
if (_vertexBuffer) {
glDeleteBuffers(1, &_vertexBuffer);
_vertexBuffer = 0;
}
//顶点数组释放
if (_vertexs) {
free(_vertexs);
_vertexs = nil;
}
}
- (void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
if (self.displayLink){
[self.displayLink invalidate];
self.displayLink = nil;
}
}
上面代码执行完,我们得到一个没有分屏正在显示的图片.
效果图:
二分屏
- 如果是横着切成一半,显示内容受
y
轴影响.
显示和纹理坐标是一比一
映射的
设置当
y
值小于0.5
,或者大于0.5
从0.25
开始显示到->0.75
void main (void) {
// vec4 mask = texture2D(Texture, TextureCoordsVarying);
// gl_FragColor = vec4(mask.rgb, 1.0);
vec2 uv = TextureCoordsVarying.xy;
float y;
if (uv.y >= 0.0 && uv.y <= 0.5) {
y = uv.y + 0.25;
} else {
y = uv.y - 0.25;
}
gl_FragColor = texture2D(Texture, vec2(uv.x,y));
}
效果图:
三分屏
三分屏类似于两分屏,设置读取1/3
到2/3
的内容.
void main (void) {
// vec4 mask = texture2D(Texture, TextureCoordsVarying);
// gl_FragColor = vec4(mask.rgb, 1.0);
vec2 uv = TextureCoordsVarying.xy;
float y;
if (uv.y>=0.0 && uv.y<=1.0/3.0){
y = uv.y + 1.0/3.0;
}else if(uv.y>=2.0/3.0 ){
y = uv.y - 1.0/3.0;
}else{
y = uv.y;
}
gl_FragColor = texture2D(Texture, vec2(uv.x,y));
}
效果图:
四分屏
四分屏和下面的六分屏,九分屏原理一样,
当四分屏读取0-0.5
如果乘以2
,对应纹理0-1,,其他几个位置依次类推.
void main (void) {
vec2 uv = TextureCoordsVarying.xy;
float x;
float y;
if (uv.y<=0.5){
y = uv.y *2.0;
}else{
y = (uv.y - 0.5) * 2.0;
}
if (uv.x<=0.5 ){
x = uv.x * 2.0;
}else{
x = (uv.x -0.5) * 2.0;
}
gl_FragColor = texture2D(Texture, vec2(x,y));
}
效果图:
六分屏
六分屏有2x3和3x2 分别对应x
和y
的分割.
也是按照4分屏一样,是的范围内的坐标
*一个
因数等于
1`
- 横
void main (void) {
vec2 uv = TextureCoordsVarying.xy;
float x;
float y;
if (uv.y<=1.0/3.0){
y = uv.y *3.0;
}else if (uv.y<=2.0/3.0){
y = (uv.y - 1.0/3.0) * 3.0;
}else{
y = (uv.y - 2.0/3.0) * 3.0;
}
if (uv.x<=0.5 ){
x = uv.x * 2.0;
}else{
x = (uv.x -0.5) * 2.0;
}
gl_FragColor = texture2D(Texture, vec2(x,y));
}
- 竖
void main (void) {
vec2 uv = TextureCoordsVarying.xy;
float x;
float y;
if (uv.x<=1.0/3.0){
x = uv.x *3.0;
}else if (uv.x<=2.0/3.0){
x = (uv.x - 1.0/3.0) * 3.0;
}else{
x= (uv.x - 2.0/3.0) * 3.0;
}
if (uv.y<=0.5 ){
y = uv.y * 2.0;
}else{
y = (uv.y -0.5) * 2.0;
}
gl_FragColor = texture2D(Texture, vec2(x,y));
}
-
效果图
九分屏
和上面一样的逻辑,乘以一个因数3,是每次读取是以0-1范围内的坐标.
void main (void) {
vec2 uv = TextureCoordsVarying.xy;
float x;
float y;
if (uv.y<=1.0/3.0){
y = uv.y *3.0;
}else if (uv.y<=2.0/3.0){
y = (uv.y - 1.0/3.0) * 3.0;
}else{
y = (uv.y - 2.0/3.0) * 3.0;
}
if (uv.x<=1.0/3.0){
x = uv.x *3.0;
}else if (uv.x<=2.0/3.0){
x = (uv.x - 1.0/3.0) * 3.0;
}else{
x = (uv.x - 2.0/3.0) * 3.0;
}
gl_FragColor = texture2D(Texture, vec2(x,y));
}
效果图: