First, the effect drawing

Ii. Flow chart

The main differences in shaders

There is no need to change the vertex shader, just change the pixel drawing point in the slice shader

1. Vertex shaders

attribute vec4 Position;
attribute vec2 TextureCoords;
varying vec2 TextureCoordsVarying;

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

Below is the fragment shader code for the different filters

2. No screen

precision highp float; uniform sampler2D Texture; varying vec2 TextureCoordsVarying; void main (void) { vec4 mask = texture2D(Texture, TextureCoordsVarying); Gl_FragColor = vec4 (mask. RGB, 1.0); }Copy the code

3, 2 split screen

There is no need to change the X direction. Let’s look at the sampling range of Y direction:

  • When y[0, 0.5]Range, the screen’s (0,0) coordinates need the corresponding image’s (0,0.25), so y = y+0.25
  • When y[0.5, 1]Range, the screen’s (0,0.5) coordinates need the corresponding image’s (0,0.25), so y = y-0.25
precision highp float; uniform sampler2D Texture; varying highp vec2 TextureCoordsVarying; void main() { vec2 uv = TextureCoordsVarying.xy; float y; If (uv) > = 0.0 && y uv) < = 0.5) {y y = uv. Y + 0.25; } else {y = uv. y-0.25; } gl_FragColor = texture2D(Texture, vec2(uv.x, y)); }Copy the code

4, 3 split screen

There is no need to change the X direction. Let’s look at the sampling range of Y direction:

  • When y[0, a third]Range, the screen’s (0,0) coordinates need to correspond to the image’s (0, 1/3), so y = y+1/3
  • When y[1/3, 2/3]Range, the screen’s (0, 1/3) coordinates need to correspond to the image’s (0, 1/3), so y stays the same
  • When y[2/3, 1]Range, the screen’s (0, 2/3) coordinates need to correspond to the image’s (0, 1/3), so y = y-1/3
precision highp float; uniform sampler2D Texture; varying highp vec2 TextureCoordsVarying; void main() { vec2 uv = TextureCoordsVarying.xy; If (uv.y < 1.0/3.0) {uv.y = uv.y + 1.0/3.0; } else if (uv) y > 2.0/3.0) {uv. Y = uv. Y - 1.0/3.0. } gl_FragColor = texture2D(Texture, uv); }Copy the code

5, 4 split screen

Both X and Y need to change:

  • When x is in the range [0, 0.5], x = x * 2

  • When x is in the range of [0.5, 1], x = (x-0.5) * 2

  • When y is in the range [0, 0.5], y = y * 2

  • When y is in the range of [0.5, 1], y = (y-0.5) * 2

precision highp float; uniform sampler2D Texture; varying highp vec2 TextureCoordsVarying; void main() { vec2 uv = TextureCoordsVarying.xy; If (uv.x <= 0.5){uv.x = uv.x * 2.0; }else{uv.x = (uv.x -0.5) * 2.0; } if (uv.y<= 0.5) {uv.y = uv.y * 2.0; }else{uv.y = (uV. y-0.5) * 2.0; } gl_FragColor = texture2D(Texture, uv); }Copy the code

6, 6 split screen

Most split screens have two rows and three columns. The X direction is divided into three parts, and the Y direction is divided into two parts. In fact, the Y direction is divided into two parts, and the X direction is divided into three parts.

  • When x[0, a third]In the range, x is equal to x plus 1/3
  • When x[1/3, 2/3]Delta x doesn’t change
  • When x[2/3, 1]In the range, x is equal to x minus 1/3
  • When y[0, 0.5]Range, y = y+0.25
  • When y[0.5, 1]In the range, y = y-0.24
precision highp float; uniform sampler2D Texture; varying highp vec2 TextureCoordsVarying; void main() { vec2 uv = TextureCoordsVarying.xy; If (uv.x <= 1.0/3.0){uv.x = uv.x + 1.0/3.0; } else if (uv) x > = 2.0/3.0) {uv. X = uv. X - 1.0/3.0. } if(uv.y <= 0.5){uv.y = uv.y + 0.25; }else {uv.y = uv.y -0.25; } gl_FragColor = texture2D(Texture, uv); }Copy the code

7, 9 split screen

Similar to 4-split screen, X and Y directions are divided in 3 equal parts:

  • When x is in the range [0, 1/3], x is equal to x times 3

  • When x is in the range 1/3, 2/3, x is equal to x minus 1/3 times 3

  • When x is in the [2/3, 1] range, x is equal to x minus 2/3 times 3

  • When y is in the range 0, 1/3, y is equal to y times 3

  • When y is in the 1/3, 2/3 range, y is equal to y minus 1/3 times 3

  • When y is in the [2/3, 1] range, y is equal to y minus 2/3 times 3

precision highp float; uniform sampler2D Texture; varying highp vec2 TextureCoordsVarying; void main() { vec2 uv = TextureCoordsVarying.xy; If (uv.x < 1.0/3.0) {uv.x = uv.x * 3.0; } else if (uv) x < 2.0/3.0) {uv. X = (uv) x - 1.0/3.0) * 3.0; } else {uv.x = (uv.x-2.0/3.0) * 3.0; } if (uv.y <= 1.0/3.0) {uv.y = uv.y * 3.0; } else if (uv) y < 2.0/3.0) {uv. Y = (uv) y - 1.0/3.0) * 3.0; } else {uv.y = (uv.y-2.0/3.0) * 3.0; } gl_FragColor = texture2D(Texture, uv); }Copy the code

Four, the code part

The comments are very detailed, so I won’t go into them

//
//  ViewController.m
//  SplitScreen
//
//  Created by jpy on 2020/8/11.
//  Copyright © 2020 jpy. All rights reserved.
//

#import "ViewController.h"
#import <GLKit/GLKit.h>
#import "FilterBar.h"

//之前也提到过,c语言结构体,存放顶点数据
typedef struct {
    GLKVector3 positionCoord; // (X, Y, Z)
    GLKVector2 textureCoord; // (U, V)
} SenceVertex;


@interface ViewController ()<FilterBarDelegate>
// 顶点数组
@property (nonatomic, assign) SenceVertex *vertices;
// 上下文
@property (nonatomic, strong) EAGLContext *context;
// 用于刷新屏幕的专属定时器(相比timer,它可以和屏幕刷新同频)
@property (nonatomic, strong) CADisplayLink *displayLink;
// 着色器程序
@property (nonatomic, assign) GLuint program;
// 顶点缓冲区id
@property (nonatomic, assign) GLuint vertexBuffer;
// 纹理的id
@property (nonatomic, assign) GLuint textureID;

@end

@implementation ViewController

//释放部分
- (void)dealloc {
    //上下文释放
    if ([EAGLContext currentContext] == self.context) {
        [EAGLContext setCurrentContext:nil];
    }
    //顶点缓存区释放
    if (_vertexBuffer) {
        glDeleteBuffers(1, &_vertexBuffer);
        _vertexBuffer = 0;
    }
    //顶点数组释放
    if (_vertices) {
        free(_vertices);
        _vertices = nil;
    }
}

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    
    // 移除 displayLink
    if (self.displayLink) {
        [self.displayLink invalidate];
        self.displayLink = nil;
    }
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.view.backgroundColor = [UIColor blackColor];
    
    /*
     整体思路:
     和GLSL加载图片的流程一样
     不同分屏滤镜效果,主要是在着色器里去计算的
     这里加一个计时器的关键点在于,让屏幕保持一直刷新渲染,方便切换滤镜及时刷新。更多是用于有动效的滤镜
     */
    //1、创建底部切换bar
    [self setupFilterBar];
    
    //2、GLSL加载图片流程
    [self loaderImage];

    //3、启动定时器,刷新屏幕
    [self startRender];
}

#pragma mark - 1
- (void)setupFilterBar {
    
    CGFloat filterBarWidth = [UIScreen mainScreen].bounds.size.width;
    CGFloat filterBarHeight = 100;
    CGFloat filterBarY = [UIScreen mainScreen].bounds.size.height - filterBarHeight;
    NSArray *dataSource = @[@"无",@"分屏_2",@"分屏_3",@"分屏_4",@"分屏_6",@"分屏_9"];
    
    FilterBar *filerBar = [[FilterBar alloc] initWithFrame:CGRectMake(0, filterBarY, filterBarWidth, filterBarHeight)];
    filerBar.itemList = dataSource;
    filerBar.delegate = self;
    [self.view addSubview:filerBar];
     
}


- (void)filterBar:(FilterBar *)filterBar didScrollToIndex:(NSUInteger)index {
    //1. 选择默认shader
    if (index == 0) {
        [self setUpDrawShaderWith:@"shader_n"];
    }else if(index == 1)
    {
        [self setUpDrawShaderWith:@"shader_2"];
    }else if(index == 2)
    {
        [self setUpDrawShaderWith:@"shader_3"];
    }else if(index == 3)
    {
        [self setUpDrawShaderWith:@"shader_4"];
    }else if(index == 4)
    {
        [self setUpDrawShaderWith:@"shader_6"];
    }else if(index == 5)
    {
        [self setUpDrawShaderWith:@"shader_9"];
    }
    // 重新开始滤镜动画
    [self startRender];
}

#pragma mark - 2
- (void)loaderImage {

    //1、准备工作
    /*
     把一些会重复用到的地方,封装起来,然后剩下的不变的,放在这个方法里面。
     上下文&设置当前
     设置图层
     设置缓冲区
     设置视口
     设置顶点数据
     设置顶点缓冲区
     解压图片,拿到纹理id(因为这里面只有一个纹理,如果有多个也要拆分出去,方便复用)
     */
    [self setUpConfig];
    
    //2、绘制每一个着色器都需要调用的方法。第一次加载,肯定使用默认着色器
    /*
     1、加载、编译shader
        1)拿到shader路径,转成c字符串
        2)创建shader对象
        3)把着色器字符串 附着到shader对象上
        4)编译shader对象&检验
     2、附着、连接program
        1)创建一个program对象
        2)把顶点、片元shader 附着上
        3)链接program&检验
     3、use program
     4、传递数据
        1)顶点坐标数据
        2)纹理坐标数据
        3)采样器传递纹理id(纹理id在准备工作就拿到了,不放这里是防止重复操作)
     */
    [self setUpDrawShaderWith:@"shader_n"];
    
     
}
#pragma mark - 2.1
- (void)setUpConfig {
    
    //1.上下文
    self.context = [[EAGLContext alloc] initWithAPI: kEAGLRenderingAPIOpenGLES2];
    [EAGLContext setCurrentContext:self.context];
    
    
    //2、图层-设置一个正方形
    CAEAGLLayer *layer = [[CAEAGLLayer alloc] init];
    layer.frame = CGRectMake(0, 100, self.view.frame.size.width, self.view.frame.size.width);
    layer.contentsScale = [[UIScreen mainScreen] scale];
    [self.view.layer addSublayer:layer];
    
    
    //3、缓冲区
    //1)渲染缓冲区
    GLuint rBuffer,fBuffer;
    glGenRenderbuffers(1, &rBuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, rBuffer);
    //把layer的存储绑定到渲染缓冲区
    [self.context renderbufferStorage:GL_RENDERBUFFER fromDrawable:layer];
    //2)帧缓冲区
    glGenFramebuffers(1, &fBuffer);
    glBindFramebuffer(GL_FRAMEBUFFER, fBuffer);
    //把renderBuffer绑定到ATTACHMENT0上
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rBuffer);

    
    //4、视口
    glViewport(0, 0, self.drawableWidth, self.drawableHeight);


    //5、顶点数据
    //1)开辟顶点数组内存空间
    self.vertices = malloc(sizeof(SenceVertex) * 4);
    //2)
    self.vertices[0] = (SenceVertex){{-1, 1, 0}, {0, 1}};
    self.vertices[1] = (SenceVertex){{-1, -1, 0}, {0, 0}};
    self.vertices[2] = (SenceVertex){{1, 1, 0}, {1, 1}};
    self.vertices[3] = (SenceVertex){{1, -1, 0}, {1, 0}};


    //6、顶点缓冲区
    GLuint vBuffer;
    glGenBuffers(1, &vBuffer);
    glBindBuffer(GL_ARRAY_BUFFER, vBuffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(SenceVertex) * 4, self.vertices, GL_STATIC_DRAW);
    //保存,退出的时候才释放
    self.vertexBuffer = vBuffer;


    //7、解压图片,获取纹理id
    
    GLuint textureID = [self createTextureWithImageName:@"mark.jpeg"];
    //设置纹理ID
    self.textureID = textureID;
    
    
    
}
- (GLuint)createTextureWithImageName:(NSString *)imageName{
    
    
    //1、拿到图片路径
    //这么写的好处是,图片不做缓存处理
    NSString *imagePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:imageName];
   
    UIImage *image = [UIImage imageWithContentsOfFile:imagePath];
    
    
    //2、解压图片
    CGImageRef imageRef = [image CGImage];
    
    //3、判断图片有没有拿到
    if (!imageRef) {
        NSLog(@"load image faile");
        exit(1);
    }
    
    //4、创建上下文
    //1)获取宽高
    GLuint width = (GLuint)CGImageGetWidth(imageRef);
    GLuint height = (GLuint)CGImageGetHeight(imageRef);
    //2)拿到图片大小
    void *imageData = malloc(width * height * 4);
    //3)拿到图片的颜色
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    //3)上下文
    /*
    参数1:data,指向要渲染的绘制图像的内存地址
    参数2:width,bitmap的宽度,单位为像素
    参数3:height,bitmap的高度,单位为像素
    参数4:bitPerComponent,内存中像素的每个组件的位数,比如32位RGBA,就设置为8
    参数5:bytesPerRow,bitmap的没一行的内存所占的比特数
    参数6:colorSpace,bitmap上使用的颜色空间  kCGImageAlphaPremultipliedLast:RGBA
    */
    CGContextRef imageContext = CGBitmapContextCreate(imageData, width, height, 8, width * 4, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);

    //5、重新绘制
    CGRect rect = CGRectMake(0, 0, width, height);
    //1)翻转策略
    CGContextTranslateCTM(imageContext, 0, height);
    CGContextScaleCTM(imageContext, 1.0f, -1.0f);
    
    //2)对图片重新绘制,得到一张新的解压后的位图
    CGContextDrawImage(imageContext, rect, imageRef);
    
    //3)用完之后释放
    CGColorSpaceRelease(colorSpace);
    CGContextRelease(imageContext);
    
    //6、设置纹理 (因为这个方法需要我们返回一个id,就不穿默认0了,还是写一遍代码吧)
    GLuint textureId;
    glGenTextures(1, &textureId);
    glBindTexture(GL_TEXTURE_2D, textureId);
    
    //7、载入纹理数据
    /*
    参数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);

    
    //8、设置纹理属性
    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);
    
    //9、重新绑定一下(用的时候就绑定准没错)
    glBindTexture(GL_TEXTURE_2D, textureId);
    //10、释放
    free(imageData);
        
    return textureId;
}

#pragma mark - 2.2
- (void)setUpDrawShaderWith:(NSString *)shaderName{
    
    //1. 编译顶点着色器/片元着色器
    GLuint vertexShader = [self compileShaderWithName:shaderName type:GL_VERTEX_SHADER];
    GLuint fragmentShader = [self compileShaderWithName:shaderName type:GL_FRAGMENT_SHADER];
    
    
    //2、
    //1)创建一个program
    GLuint program = glCreateProgram();
    
    //2)附着
    glAttachShader(program, vertexShader);
    glAttachShader(program, fragmentShader);
    
//    glDeleteShader(vertexShader);
//    glDeleteShader(fragmentShader);
    
    //3)link
    glLinkProgram(program);
    
    //4)检查
    GLint linkStatus;
    glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
    if (linkStatus == GL_FALSE) {
        GLchar messages[256];
        glGetProgramInfoLog(program, sizeof(messages), 0, &messages[0]);
        NSString *messageString = [NSString stringWithUTF8String:messages];
        NSLog(@"program链接失败:%@", messageString);
        exit(1);
    }
 
    
    //3、use
    glUseProgram(program);
    
    //4、传递数据
    //1)先拿到通道名
    //顶点坐标
    GLuint positionSlot = glGetAttribLocation(program, "Position");
    //纹理坐标
    GLuint textureCoordsSlot = glGetAttribLocation(program, "TextureCoords");
    //纹理
    GLuint textureSlot = glGetUniformLocation(program, "Texture");
 
    
    //2)传顶点坐标
    glEnableVertexAttribArray(positionSlot);
    glVertexAttribPointer(positionSlot, 3, GL_FLOAT, GL_FALSE, sizeof(SenceVertex), NULL + offsetof(SenceVertex, positionCoord));
    
    //3)传纹理坐标
    glEnableVertexAttribArray(textureCoordsSlot);
    glVertexAttribPointer(textureCoordsSlot, 2, GL_FLOAT, GL_FALSE, sizeof(SenceVertex), NULL + offsetof(SenceVertex, textureCoord));
    
    //4) 传纹理
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, self.textureID);
    
    glUniform1i(textureSlot, 0);
    
    
    //5.保存program,界面销毁则释放
    self.program = program;
}

//编译shader代码
- (GLuint)compileShaderWithName:(NSString *)name type:(GLenum)shaderType {
    
    //1、获得shader路径
    NSString *shaderPath = [[NSBundle mainBundle] pathForResource:name ofType:shaderType == GL_VERTEX_SHADER ? @"vsh" : @"fsh"];
    
    //2、转换成c语言字符串
    NSError *error;
    NSString *shaderString = [NSString stringWithContentsOfFile:shaderPath encoding:NSUTF8StringEncoding error:&error];
    if (!shaderString) {
        NSLog( @"读取shader失败");
        exit(1);
    }
    
//    const GLchar* source = (GLchar*)[pathString UTF8String];
    
    const char *shaderStringUTF8 = [shaderString UTF8String];
    int shaderStringLength = (int)[shaderString length];
    
    
    //3、创建shader对象
    GLuint shader = glCreateShader(shaderType);
    
    //4、附着
    glShaderSource(shader, 1, &shaderStringUTF8, &shaderStringLength);
    
    //5、编译
    glCompileShader(shader);
    
    //6、检查编译
    GLint compileStatus;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &compileStatus);
    
    if (compileStatus == GL_FALSE) {
        GLchar messages[256];
        glGetShaderInfoLog(shader, sizeof(messages), 0, &messages[0]);
        NSString *messageString = [NSString stringWithUTF8String:messages];
        NSLog(@"shader编译失败:%@", messageString);
        exit(1);
    }
    
    return shader;
}
#pragma mark - 3
- (void)startRender {

    //1.因为会重复调用,严谨一点,先判断一下
    if (self.displayLink) {
        
        [self.displayLink invalidate];
        self.displayLink = nil;
    }
    //2. 设置displayLink 的方法
    self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(timeAction)];
    
    //3.将displayLink 添加到runloop 运行循环
    [self.displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}

 
- (void)timeAction{
    
   
    //使用program
    glUseProgram(self.program);
    //绑定buffer
    glBindBuffer(GL_ARRAY_BUFFER, self.vertexBuffer);
     
    // 清除画布
    glClear(GL_COLOR_BUFFER_BIT);
    glClearColor(1, 1, 1, 1);
    
    // 重绘
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    //渲染到屏幕上
    [self.context presentRenderbuffer:GL_RENDERBUFFER];
    
}


//获取渲染缓存区的宽
- (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;
}
@end


Copy the code

Source code link:

Link: pan.baidu.com/s/1HPEDyWqd… Password: c6hi