美文网首页
OpenGL ES滤镜:分屏滤镜

OpenGL ES滤镜:分屏滤镜

作者: 奉灬孝 | 来源:发表于2020-08-08 16:40 被阅读0次

9分屏效果如下所示:


9分屏.jpeg

滤镜初始化

1. 初始化上下文并设置为当前上下文
2. 开辟顶点数组内存空间
3. 初始化4个顶点的顶点坐标以及纹理坐标
4. 创建图层(CAEAGLLayer)
5. 绑定渲染缓存区
6. 图片解压缩加载纹理
7. 设置视口
8. 设置顶点缓存区,加载顶点数据
9. 设置默认着色器
10. 将顶点缓存保存,退出时才释放

- (void)filterInit {
    
    //1. 初始化上下文并设置为当前上下文
    self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
    [EAGLContext setCurrentContext:self.context];
    
    //2.开辟顶点数组内存空间
    self.vertices = malloc(sizeof(SenceVertex) * 4);
    
    //3.初始化4个顶点的顶点坐标以及纹理坐标
    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}};
    
    //4.创建图层(CAEAGLLayer)
    CAEAGLLayer *layer = [[CAEAGLLayer alloc] init];
    //设置图层frame
    layer.frame = CGRectMake(0, 100, self.view.frame.size.width, self.view.frame.size.width);
    //设置图层的scale
    layer.contentsScale = [[UIScreen mainScreen] scale];
    //给View添加layer
    [self.view.layer addSublayer:layer];
    
    //5.绑定渲染缓存区
    [self bindRenderLayer:layer];
    
    //6.获取处理的图片路径
    NSString *imagePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"kunkun.jpg"];
  
    //读取图片
    UIImage *image = [UIImage imageWithContentsOfFile:imagePath];
    //将JPG图片转换成纹理图片
    GLuint textureID = [self createTextureWithImage:image];
    //设置纹理ID
    self.textureID = textureID;  // 将纹理 ID 保存,方便后面切换滤镜的时候重用
    
    //7.设置视口
    glViewport(0, 0, self.drawableWidth, self.drawableHeight);
    
    //8.设置顶点缓存区,加载顶点数据
    GLuint vertexBuffer;
    glGenBuffers(1, &vertexBuffer);
    glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
    GLsizeiptr bufferSizeBytes = sizeof(SenceVertex) * 4;
    glBufferData(GL_ARRAY_BUFFER, bufferSizeBytes, self.vertices, GL_STATIC_DRAW);
    
    
    //9.设置默认着色器
    [self setupNormalShaderProgram]; // 一开始选用默认的着色器
    
    //10.将顶点缓存保存,退出时才释放
    self.vertexBuffer = vertexBuffer;
}

1. 初始化上下文并设置为当前上下文

//1. 初始化上下文并设置为当前上下文
self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
[EAGLContext setCurrentContext:self.context];

2. 开辟顶点数组内存空间

  • 初始化顶点、纹理数据结构体

positionCoord:顶点数据
positionCoord:纹理数据

typedef struct {
    GLKVector3 positionCoord; // (X, Y, Z)
    GLKVector2 textureCoord; // (U, V)
} SenceVertex;
  • 开辟顶点数组内存空间
self.vertices = malloc(sizeof(SenceVertex) * 4);

3. 初始化4个顶点的顶点坐标以及纹理坐标

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}};

4. 创建图层(CAEAGLLayer)

CAEAGLLayer *layer = [[CAEAGLLayer alloc] init];
//设置图层frame
layer.frame = CGRectMake(0, 100, self.view.frame.size.width, self.view.frame.size.width);
//设置图层的scale
layer.contentsScale = [[UIScreen mainScreen] scale];
//给View添加layer
[self.view.layer addSublayer:layer];

5. 绑定渲染缓冲区

  1. 生成渲染缓存区,帧缓存区对象
  2. 获取帧渲染缓存区名称,绑定渲染缓存区以及将渲染缓存区与layer建立连接。
    - (BOOL)renderbufferStorage:(NSUInteger)target fromDrawable:(nullable id<EAGLDrawable>)drawable;:作用:将可绘制对象的存储绑定到OpenGL ES 渲染缓存区(renderbuffer)对象。
    参数target:OpenGL ES绑定点用于当前绑定的renderbuffer。该参数的值必须是GL_RENDERBUFFER。
    参数drawable:管理renderbuffer的数据存储的对象。在iOS中,此参数的值必须是一个CAEAGLLayer
  3. 获取帧缓存区名称,绑定帧缓存区以及将渲染缓存区(RBO)绑定到到帧缓存区(FBO)上
//绑定渲染缓存区和帧缓存区
- (void)bindRenderLayer:(CALayer <EAGLDrawable> *)layer {
    
    //1.渲染缓存区,帧缓存区对象
    GLuint renderBuffer;
    GLuint frameBuffer;
    
    //2.获取帧渲染缓存区名称,绑定渲染缓存区以及将渲染缓存区与layer建立连接。

    glGenRenderbuffers(1, &renderBuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, renderBuffer);
    [self.context renderbufferStorage:GL_RENDERBUFFER fromDrawable:layer];
    
    //3.获取帧缓存区名称,绑定帧缓存区以及将渲染缓存区附着到帧缓存区上
    glGenFramebuffers(1, &frameBuffer);
    glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER,
                              GL_COLOR_ATTACHMENT0,
                              GL_RENDERBUFFER,
                              renderBuffer);
}

6. 图片解压缩加载纹理

  1. 将 UIImage 转换为 CGImageRef
  2. 读取图片的大小,宽和高
  3. 获取图片字节数 宽4(RGBA)
  4. 创建上下文
  5. 获取纹理ID
  6. 载入纹理2D数据
  7. 设置纹理参数(环绕方式、过滤方式)
  8. 绑定纹理
  9. 释放上下文context,图片字节数imageData
  10. 返回纹理ID
//从图片中加载纹理
- (GLuint)createTextureWithImage:(UIImage *)image {
    
    //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;
}

7. 设置视口

glViewport(0, 0, self.drawableWidth, self.drawableHeight);
7.1 获取渲染缓存区的宽和高
//获取渲染缓存区的宽
- (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;
}

8. 设置顶点缓存区,加载顶点数据

  • glGenBuffers()创建缓存对象并且返回缓存对象的标示符。它需要2个参数:第一个为需要创建的缓存数量,第二个为用于存储单一ID或多个IDGLuint变量或数组的地址。
  • glBindBuffer()当缓存对象创建之后,在使用缓存对象之前,我们需要将缓存对象连接到相应的缓存上。glBindBuffer()有2个参数:targetbuffer

target:告诉VBO该缓存对象将保存顶点数组数据还是索引数组数据:GL_ARRAY_BUFFERGL_ELEMENT_ARRAY。任何顶点属性,如顶点坐标、纹理坐标、法线与颜色分量数组都使用GL_ARRAY_BUFFER。用于glDraw[Range]Elements()的索引数据需要使用GL_ELEMENT_ARRAY绑定。

  • void glBufferData(GLenum target,GLsizeiptr size, const GLvoid* data, GLenum usage);当缓存初始化之后,你可以使用glBufferData()将数据拷贝到缓存对象。

target: GL_ARRAY_BUFFERGL_ELEMENT_ARRAY
size: 待传递数据字节数量。
data:源数据数组指针,如dataNULL,则VBO仅仅预留给定数据大小的内存空间。
usage:标志位,VBO的另一个性能提示,它提供缓存对象将如何使用:staticdynamicstream、与readcopydraw

GLuint vertexBuffer;
glGenBuffers(1, &vertexBuffer);
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
GLsizeiptr bufferSizeBytes = sizeof(SenceVertex) * 4;
glBufferData(GL_ARRAY_BUFFER, bufferSizeBytes, self.vertices, GL_STATIC_DRAW);

9. 设置着色器

9.1 初始化着色器程序
  1. 获取着色器程序program
  2. 执行着色器程序program
  3. 获取Position,Texture,TextureCoords的索引位置
  4. 激活纹理,绑定纹理ID
  5. 纹理sample
  6. 打开positionSlot属性并且传递数据到positionSlot中(顶点坐标)
  7. 打开textureCoordsSlot属性并传递数据到textureCoordsSlot(纹理坐标)
  8. 保存program,界面销毁则释放
// 初始化着色器程序
- (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(SenceVertex), NULL + offsetof(SenceVertex, positionCoord));
    
    //7.打开textureCoordsSlot 属性并传递数据到textureCoordsSlot(纹理坐标)
    glEnableVertexAttribArray(textureCoordsSlot);
    glVertexAttribPointer(textureCoordsSlot, 2, GL_FLOAT, GL_FALSE, sizeof(SenceVertex), NULL + offsetof(SenceVertex, textureCoord));
    
    //8.保存program,界面销毁则释放
    self.program = program;
}
9.2 获取着色器程序program

glCreateProgram 用于创建一个空的程序对象,程序对象是可以附加着色器对象的对象。通过使用glAttachShader成功将着色器对象附加到其上,使用glCompileShader成功编译该着色器对象,以及使用glLinkProgram成功链接该程序对象,可以在程序对象中创建一个或多个可执行文件。调用glUseProgram时,这些可执行文件将成为当前状态的一部分。可以通过调用glDeleteProgram删除程序对象。当与程序对象关联的内存不再是任何上下文的当前渲染状态的一部分时,将删除该内存。

  1. 编译顶点着色器/片元着色器
  2. 将顶点/片元着色器附着glAttachShader到着色器程序对象program
  3. 链接程序对象linkProgram
  4. 检查是否链接link成功
  5. 返回着色器程序对象program
//link Program
- (GLuint)programWithShaderName:(NSString *)shaderName {
    //1. 编译顶点着色器/片元着色器
    GLuint vertexShader = [self compileShaderWithName:shaderName type:GL_VERTEX_SHADER];
    GLuint fragmentShader = [self compileShaderWithName:shaderName type:GL_FRAGMENT_SHADER];
    
    //2. 将顶点/片元着色器附着(glAttachShader)到着色器程序对象(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;
}
9.3 编译着色器shader代码
//编译shader代码
- (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;
}
9.4 创建顶点着色器/片元着色器

分屏滤镜的显示主要也是在着色器对象中,其中顶点着色器是不需要改变的,改变的主要是片元着色器。其实显示这几种不同的分屏滤镜,就是对应相应的9.3 编译不同着色器的执行结果。

顶点着色器Normal.vsh
attribute vec4 Position;
attribute vec2 TextureCoords;
varying vec2 TextureCoordsVarying;

void main (void) {
    gl_Position = Position;
    TextureCoordsVarying = TextureCoords;
}
普通片元着色器Normal.fsh
precision highp float;
uniform sampler2D Texture;
varying vec2 TextureCoordsVarying;

void main (void) {
    vec4 mask = texture2D(Texture, TextureCoordsVarying);
    gl_FragColor = vec4(mask.rgb, 1.0);
}
二分屏片元着色器
precision highp float;
uniform sampler2D Texture;
varying highp vec2 TextureCoordsVarying;

void main() {
    vec2 uv = TextureCoordsVarying.xy;

    if (uv.y >= 0.0 && uv.y <= 0.5) {
        uv.y = uv.y + 0.25;
    } else {
        uv.y = uv.y - 0.25;
    }
    
    gl_FragColor = texture2D(Texture, uv);
}
三分屏片元着色器
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);
}

四分屏片元着色器
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);
}
六分屏片元着色器
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);
}
九分屏片元着色器
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);
}

释放

  1. 上下文释放
  2. 顶点缓存区释放
  3. 顶点数组释放
  4. 着色器程序释放
//释放
- (void)dealloc {
    //1. 上下文释放
    if ([EAGLContext currentContext] == self.context) {
        [EAGLContext setCurrentContext:nil];
    }
    //2. 顶点缓存区释放
    if (_vertexBuffer) {
        glDeleteBuffers(1, &_vertexBuffer);
        _vertexBuffer = 0;
    }
    //3. 顶点数组释放
    if (_vertices) {
        free(_vertices);
        _vertices = nil;
    }
    //4. 着色器程序释放
    if (_program) {
        glDeleteProgram(_program);
        _program = nil;
    }
}

Demo:
SplitScreenFilter

相关文章

网友评论

      本文标题:OpenGL ES滤镜:分屏滤镜

      本文链接:https://www.haomeiwen.com/subject/kslhdktx.html