十四、利用GLSL实现分屏滤镜

技术要点:
1.实现CAEAGLLayer添加特殊图层到VC;
2.设置并加载纹理;
3.绑定顶点、帧缓冲区;
4.初始化着色器程序并链接program;
5.编译着色器程序;

分屏原理
先走一波「死亡」凝视

喵之凝视

  • 1. 二分屏
    这是纹理坐标映射到屏幕顶点,且代码中实现了翻转,所以从左上(0,0)开始。
    为了截取图片中心有效内容实现二分屏,故从纹理图片中截取Y轴下 [0.25, 0.75] 范围的内容,对应到实际屏幕(0,0)坐标实际取值为纹理的(0,0.25);在Y值小于0.5时,换算到Y轴表达式:newY = textureY + 0.25;Y轴大于0.5时,换算Y轴表达式:newY = textureY - 0.25 ;
    二分屏

    片元着色器程序文件
precision highp float;
uniform sampler2D un_texture;
varying vec2 var_textureCoords;

void main()
{
    vec2 var_xy = var_textureCoords.xy;
    
    if (var_xy.y > 0.0 && var_xy.y < 0.5) {
  
        var_xy.y = var_xy.y + 0.25;
    }else{
        var_xy.y = var_xy.y - 0.25;
    }
    
    gl_FragColor = texture2D(un_texture,vec2( var_xy.x, var_xy.y));
    
}

三分屏、六分屏和二分屏是相同原理,改变对应的映射值即可,具体见Demo 中的着色器程序;

  • 2. 四分屏
    四分屏和二分屏不一样,四分屏是在固定的屏幕区域,将纹理的原尺寸缩小一倍,这样实现了原画均匀输出在一分为四的显示区域,不会有拉伸or放大显示了图片的某个区域,且不失真,以达到更好的体验。x, y在 0 -- 0.5 范围内,要想获取整个纹理全输出,就得将传进的纹理坐标 乘2,当x, y在 0.5 -- 1.0范围时,原纹理坐标就得 减0.5 再 乘2 ;
    四分屏
precision highp float;
uniform sampler2D un_texture;
varying vec2 var_textureCoords;

void main()
{
    vec2 var_xy = var_textureCoords.xy;
    
    if (var_xy.x <= 0.5) {
        var_xy.x = var_xy.x * 2.0;
    }else{
        var_xy.x = (var_xy.x - 0.5) * 2.0;
    }
    
    if (var_xy.y <= 0.5) {
        var_xy.y = var_xy.y * 2.0;
    }else{
        var_xy.y = (var_xy.y - 0.5) * 2.0;
    }
    
    gl_FragColor = texture2D(un_texture,vec2(var_xy.x, var_xy.y));
    
}

九分屏原理和四分屏一样,出现多的一个判断是在 1/3 至 2/3 范围时,x,y轴的取值。

precision highp float;
uniform sampler2D un_texture;
varying vec2 var_textureCoords;

void main()
{
    vec2 var_xy = var_textureCoords.xy;

    if (var_xy.x <= 1.0/3.0) {
        var_xy.x = var_xy.x * 3.0;
    }else if (var_xy.x > 1.0/3.0 && var_xy.x < 2.0/3.0){
        var_xy.x = (var_xy.x - 1.0/3.0) * 3.0;
    }else if (var_xy.x >= 2.0/3.0){
        var_xy.x = (var_xy.x - 2.0/3.0) * 3.0;
    }
    
    if (var_xy.y <= 1.0/3.0) {
        var_xy.y = var_xy.y * 3.0;
        
    }else if (var_xy.y > 1.0/3.0 && var_xy.y < 2.0/3.0){
        
        var_xy.y = (var_xy.y - 1.0/3.0) * 3.0;
    }else if (var_xy.y >= 2.0/3.0){
        
        var_xy.y = (var_xy.y - 2.0/3.0) * 3.0;
    }
    
    gl_FragColor = texture2D(un_texture,vec2( var_xy.x, var_xy.y));
    
}
主要实现

初始化Something

  • 初始化 EAGAContext & CAEAGLLayer
  • 准备数组
  • 绑定渲染 & 帧 缓冲区

#pragma mark - 初始化something
-(void)initContextAndCALayer
{
    self.mContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
    if (![EAGLContext setCurrentContext:self.mContext]) {
        NSLog(@"setCurrentContext failed");
        return;
    }
    
    //2.开辟顶点数组内存空间
    self.mVertices = malloc(sizeof(SenceVertex) * 4);
    
    //3.初始化顶点(0,1,2,3)的顶点坐标以及纹理坐标
    self.mVertices[0] = (SenceVertex){{-1, 1, 0}, {0, 1}};
    self.mVertices[1] = (SenceVertex){{-1, -1, 0}, {0, 0}};
    self.mVertices[2] = (SenceVertex){{1, 1, 0}, {1, 1}};
    self.mVertices[3] = (SenceVertex){{1, -1, 0}, {1, 0}};
    
    
    // 创建CAEAGLayer图层
    self.mEagaLayer = [[CAEAGLLayer alloc] init];
    self.mEagaLayer.frame = CGRectMake(0, 150, self.view.frame.size.width, self.view.frame.size.width);
    self.mEagaLayer.contentsScale = [[UIScreen mainScreen] scale];
    [self.view.layer addSublayer:self.mEagaLayer];
    
    //=====绑定渲染缓冲区 & 帧缓冲区======
    // 渲染缓存区,帧缓存区对象
    GLuint renderBuffer,frameBuffer;
    // 获取帧渲染缓存区名称,绑定渲染缓存区以及将渲染缓存区与layer建立连接
    glGenBuffers(1, &renderBuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, renderBuffer);
    [self.mContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:self.mEagaLayer];
    
    // 获取帧缓存区名称,绑定帧缓存区以及将渲染缓存区附着到帧缓存区上
    glGenBuffers(1, &frameBuffer);
    glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderBuffer);
    
    //=====加载纹理======
    //
    self.textureID = [self createTextureIDWithImage];
    
    // 设置视口
    glViewport(0, 0, self.drawableWidth, self.drawableHeight);
    
    // 设置顶点缓冲区
    GLuint vertexBuffer;
    glGenBuffers(1, &vertexBuffer);
    glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
    GLsizeiptr bufferSizeBytes = sizeof(SenceVertex) * 4;
    glBufferData(GL_ARRAY_BUFFER, bufferSizeBytes, self.mVertices, GL_STATIC_DRAW);
    
    // 设置默认着色器
//    [self setupNormalShaderProgram]; // 一开始选用默认的着色器
    [self setupShaderProgramWithName:@"normalShader"];
    
    //10.将顶点缓存保存,退出时才释放
    self.vertexBuffer = vertexBuffer;

}

纹理处理

  • 加载纹理

/**
 加载纹理,

 @return 返回的纹理ID
 */
- (GLuint)createTextureIDWithImage
{
    NSString * imgPath = [[NSBundle mainBundle] pathForResource:@"miao" ofType:@"jpg"];
    UIImage * image = [UIImage imageWithContentsOfFile:imgPath];
   
    // 将UIImage => CGImageRef
    CGImageRef imageRef = image.CGImage;
    if (!imageRef) {
        NSLog(@"can not get imageRef");
        return 0;
    }
    // 获取图片的数据 大小、宽高、字节数
    GLuint imgWidth = (GLuint)CGImageGetWidth(imageRef);
    GLuint imgHeight = (GLuint)CGImageGetHeight(imageRef);
    GLubyte  * imageData = (GLubyte *)calloc(imgWidth * imgHeight * 4, sizeof(GLubyte));
    //获取图片的颜色空间
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    /** 创建上下文
     para1: data,指向要渲染的绘制图像的内存地址
     para2: width,bitmap的宽度,单位为像素
     para3: height,bitmap的高度,单位为像素
     para4: bitPerComponent,内存中像素的每个组件的位数,比如32位RGBA,就设置为8
     para5: bytesPerRow,bitmap的没一行的内存所占的比特数
     para6: colorSpace,bitmap上使用的颜色空间  kCGImageAlphaPremultipliedLast:RGBA
     */
    CGContextRef imgContext = CGBitmapContextCreate(imageData, imgWidth, imgHeight, 8, imgWidth * 4, CGImageGetColorSpace(imageRef), kCGImageAlphaPremultipliedLast);

    CGRect imgRect = CGRectMake(0, 0, imgWidth, imgHeight);

    // 翻转图片
    CGContextTranslateCTM(imgContext, 0, imgHeight);
    CGContextScaleCTM(imgContext, 1.0f, -1.0f);
    CGColorSpaceRelease(colorSpace);
    CGContextClearRect(imgContext, imgRect);
    // 重新绘制图——解压缩的位图
    CGContextDrawImage(imgContext, imgRect, imageRef);

    // 设置图片纹理属性
    GLuint textureID;
    glGenTextures(1, &textureID);
    glBindTexture(GL_TEXTURE_2D, textureID);

    // 载入纹理
    /*
     参数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, imgWidth, imgHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageData);

    // 设置纹理属性  过滤方式 + 环绕方式
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    // 绑定纹理
    glBindTexture(GL_TEXTURE_2D, 0);

    CGContextRelease(imgContext);
    free(imageData);

    // 返回纹理id
    return textureID;
}
  • 设置默认着色器

/**
 设置着色器programID

 @param nameStr 着色程序文件名
 */
-(void)setupShaderProgramWithName:(NSString *)nameStr
{
    // 获取着色器program
    GLuint program = [self backProgramWithShaderName:nameStr];
    
    // 2.使用program
    glUseProgram(program);
    
    // 3.获取 att_position att_textuteCoords un_texture 的索引位置
    GLuint positionSlot = glGetAttribLocation(program, "att_position");
    GLuint textureCoordSlot = glGetAttribLocation(program, "att_textuteCoords");
    
    GLuint textureSlot = glGetAttribLocation(program, "un_texture");
    
    // 4.激活纹理,绑定纹理id
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, self.textureID);

    // 5.采样纹理
    glUniform1i(textureSlot , 0);

    // 6.打开positionSlolt属性,并将数据传递到 att_position 中
    glEnableVertexAttribArray(positionSlot);
    glVertexAttribPointer(positionSlot, 3, GL_FLOAT, GL_FALSE, sizeof(SenceVertex), NULL + offsetof(SenceVertex, positionCoord));

    // 7.打开positionSlolt属性,并将数据传递到 att_position 中
    glEnableVertexAttribArray(textureCoordSlot);
    glVertexAttribPointer(textureCoordSlot, 2, GL_FLOAT, GL_FALSE, sizeof(SenceVertex), NULL + offsetof(SenceVertex, textureCoord));

    // 8.保存program
    self.programID = program;
}

/**
 返回program

 @param nameStr shaderName
 @return 返回对应的program
 */
- (GLuint)backProgramWithShaderName:(NSString *)nameStr
{
    // 1.编译顶点、片元着色器
    GLuint vertexShader = [self compileShaderWithName:nameStr WithType:GL_VERTEX_SHADER];
    GLuint fragmentShader = [self compileShaderWithName:nameStr WithType:GL_FRAGMENT_SHADER];
    
    // 2.将顶点、片元着色程序着到program
    GLuint program = glCreateProgram();
    glAttachShader(program, vertexShader);
    glAttachShader(program, fragmentShader);
    
    // 3.链接program
    glLinkProgram(program);
    
    // 4.获取链接program的状态
    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];
        NSAssert(NO, @"program link err:%@", messageString);
        exit(1);
    }
    // 5.返回program
    return program;
    
}
  • 编译着色程序
    喵之偷懒:多分屏时,其顶点数据和无分屏时是一样的,在屏幕上的顶点坐标就可以共用一个顶点着色程序;多分屏处理的是纹理的映射变化,通过对图片的纹理操作,改变映射到屏幕的坐标来实现分屏效果。

/**
 编译shader

 @param shaderName 着色器文件名
 @param shaderType 着色器类型
 @return 返回对应的着色器
 */
- (GLuint)compileShaderWithName:(NSString *)shaderName WithType:(GLenum)shaderType
{
    // 1.获取shader的path
    // 使用2、3、4、6、9分屏,其顶点着色程序是不变的,故可使用用一个normalShader.vsh 文件
    shaderName = shaderType == GL_VERTEX_SHADER ? @"normalShader" : shaderName;
    NSString * shaderPath = [[NSBundle mainBundle] pathForResource:shaderName ofType:shaderType == GL_VERTEX_SHADER ? @"vsh" : @"fsh"];

    
    NSString * shaderString = [NSString stringWithContentsOfFile:shaderPath encoding:NSUTF8StringEncoding error:nil];
    if (!shaderString) {
        NSAssert(NO, @"shader read failed");
        return 0;
    }
    
    // 2.根据shaderType 创建对应的shader
    GLuint shader = glCreateShader(shaderType);
    
    // 3.获取shader Source
    const char * shadertStrUtf8 = shaderString.UTF8String;
     int shaderStringLength = (int)[shaderString length];
    glShaderSource(shader, 1, &shadertStrUtf8, &shaderStringLength);
    
    // 4.编译shader
    glCompileShader(shader);
    
    // 5.获取编译状态
    GLint complieStatus;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &complieStatus);
     if (complieStatus == GL_FALSE) {
        GLchar message[256];
        glGetShaderInfoLog(shader, sizeof(message), 0, &message[0]);
        NSString * messageStr = [NSString stringWithUTF8String:message];
        NSAssert(NO, @"shader compile error : %@",messageStr);
        return 0;
        
    }
    // 6.返回shader
    return shader;
    
}

github demo 地址

你可能感兴趣的:(十四、利用GLSL实现分屏滤镜)