OpenGL ES-11-案例06-分屏滤镜

一、效果图

image

二、流程图

image

三、着色器中的主要区别

顶点着色器中不需要改变,只在片元着色器改变像素绘制的点就ok了

1、顶点着色器

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

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

以下是不同滤镜的片元着色器代码

2、不分屏

precision highp float;
uniform sampler2D Texture;
varying vec2 TextureCoordsVarying;

void main (void) {
    vec4 mask = texture2D(Texture, TextureCoordsVarying);
    gl_FragColor = vec4(mask.rgb, 1.0);
}

3、2分屏

image

X方向不需要改动,我们来看Y方向的取样范围:

  • 当 y 在[0, 0.5]范围时,屏幕的(0,0)坐标需要对应图片的(0,0.25),所以y = y+0.25
  • 当 y 在[0.5, 1]范围时,屏幕的(0,0.5)坐标需要对应图片的(0,0.25),所以y = y-0.25
precision highp float;
uniform sampler2D Texture;
varying highp vec2 TextureCoordsVarying;

void main() {
    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));
}


4、3分屏

image

X方向不需要改动,我们来看Y方向的取样范围:

  • 当 y 在[0, 1/3]范围时,屏幕的(0,0)坐标需要对应图片的(0,1/3),所以y = y+1/3
  • 当 y 在[1/3, 2/3]范围时,屏幕的(0,1/3)坐标需要对应图片的(0,1/3),所以y 不变
  • 当 y 在[2/3, 1]范围时,屏幕的(0,2/3)坐标需要对应图片的(0,1/3),所以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);
}

5、4分屏

image

X、Y都需要改变了:

  • 当 x 在[0, 0.5]范围时,x = x * 2
  • 当 x 在[0.5, 1]范围时,x = ( x - 0.5 ) * 2
  • 当 y 在[0, 0.5]范围时,y = y * 2
  • 当 y 在[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);
}

6、6分屏

image

6分屏大多是两行三列。X方向分3份,Y方向分2份,其实相当于Y方向2分屏、X方向3分屏。

  • 当 x 在[0, 1/3]范围时,x = x+1/3
  • 当 x 在[1/3, 2/3]范围时,x 不变
  • 当 x 在[2/3, 1]范围时,x = x-1/3
  • 当 y 在[0, 0.5]范围时,y = y+0.25
  • 当 y 在[0.5, 1]范围时,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);
}

7、9分屏

image

类似4分屏,X、Y方向都进行3等份:

  • 当 x 在[0, 1/3]范围时,x = x * 3
  • 当 x 在[1/3, 2/3]范围时,x = ( x - 1/3 ) * 3
  • 当 x 在[2/3, 1]范围时,x = ( x - 2/3 ) * 3
  • 当 y 在[0, 1/3]范围时,y= y * 3
  • 当 y 在[1/3, 2/3]范围时,y = ( y - 1/3 ) * 3
  • 当 y 在[2/3, 1]范围时,y = ( y - 2/3 ) * 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);
}

四、代码部分

注释很详细了,就不赘述了

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

#import "ViewController.h"
#import 
#import "FilterBar.h"

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


@interface ViewController ()
// 顶点数组
@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


源码链接:

链接:https://pan.baidu.com/s/1HPEDyWqdLiI7q7EJQBIhjw 密码:c6hi

你可能感兴趣的:(OpenGL ES-11-案例06-分屏滤镜)