iOS 直播视频音视频开发iOS效果

OpenGL ES入门12-帧缓存与叠加滤镜

2017-04-11  本文已影响1475人  秦明Qinmin

本文是关于OpenGL ES的系统性学习过程,记录了自己在学习OpenGL ES时的收获。
这篇文章的作用是学习的OpenGL ES帧缓存FBO技术,实例是利用FBO技术对纹理进行多次叠加滤镜。
环境是Xcode8.1+OpenGL ES 2.0
目前代码已经放到github上面,OpenGL ES入门12-帧缓存

欢迎关注我的 OpenGL ES入门专题

实现效果

效果图

帧缓存

帧缓冲存储器(Frame Buffer):简称帧缓存或显存,它是屏幕所显示画面的一个直接映象,又称为位映射图(Bit Map)或光栅。帧缓存的每一存储单元对应屏幕上的一个像素,整个帧缓存对应一帧图像。

建构一个完整的帧缓存必须满足以下条件:
我们必须往里面加入至少一个附件(颜色、深度、模板缓存)。
其中至少有一个是颜色附件。
所有的附件都应该是已经完全做好的(已经存储在内存之中)。
每个缓存都应该有同样数目的样本。

void glGenRenderbuffers (GLsizei n, GLuint* renderbuffers)  
void glBindRenderbuffer (GLenum target, GLuint renderbuffer)
void glDeleteFramebuffers (GLsizei n, const GLuint* framebuffers)
glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE

当我们希望图像渲染到iOS视图上的时候,我们需要申请一个帧缓存对象用来让所有的渲染操作对iOS视图产生影响。 如果后续我们又申请了另外的帧缓存对象,并且激活了该缓存对象,所有渲染操作将渲染到当前绑定的帧缓存的附加缓存中,由于我们的帧缓存不是默认的帧缓存,渲染命令对窗口的视频输出不会产生任何影响。出于这个原因,它被称为离屏渲染(off-screen rendering),就是渲染到一个另外的缓存中,而不是默认的缓存。

纹理附件

当把一个纹理附加到帧缓存上的时候,所有渲染命令会写入到纹理上,就像它是一个普通的颜色、深度或者模板缓存一样。使用纹理的好处是,所有渲染操作的结果都会被储存为一个纹理图像,这样我们就可以简单的在着色器中使用了。

GLuint createTexture2D(GLenum format, int width, int height, void *data)
{
    GLuint texture;
    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture);
    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);
    glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data);
    glBindTexture(GL_TEXTURE_2D, 0);
    return texture;
}

纹理附加到帧缓存上:

void glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level)

渲染缓存

渲染缓存是OpenGL所管理的一处高效的内存区域,它可以存储格式化的图像数据。渲染缓存中的数据只有关联到一个帧缓存对象之后才有意义,并且需要保证图像缓存的格式需要与OpenGL要求的渲染格式相符,不能将颜色值渲染到深度缓存中。和纹理图像一样,渲染缓存对象也是一个缓存,它可以是一堆字节、整数、像素或者其他东西。渲染缓存对象的一大优点是,它以OpenGL原生渲染格式储存它的数据,因此在离屏渲染到帧缓存的时候,这些数据就相当于被优化过的了。然而,渲染缓存对象通常是只写的,不能修改它们(就像获取纹理,不能写入纹理一样)。可以用 glReadPixels 函数去读取,函数返回一个当前绑定的帧缓存的特定像素区域,而不是直接返回附件本身。

void glGenRenderbuffers (GLsizei n, GLuint* renderbuffers)  
void glBindRenderbuffer (GLenum target, GLuint renderbuffer)
void glRenderbufferStorage (GLenum target, GLenum internalformat, GLsizei width, GLsizei height) 
void glFramebufferRenderbuffer (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer)

帧缓存附件

下表是常见的帧缓存附件

附件名称 描述
GL_COLOR_ATTACHMENTi 第i个颜色缓存
GL_DEPTH_ATTACHMENT 深度缓存
GL_STENCIL_ATTACHMENT 模板缓存
GL_DEPTH_STENCIL_ATTACHMENT 特殊的附件类型,用于保存压缩后的深度-模板缓存

实现过程

- (void)setupGLProgram
{
    NSString *vertFile1 = [[NSBundle mainBundle] pathForResource:@"vert.glsl" ofType:nil];
    NSString *fragFile1 = [[NSBundle mainBundle] pathForResource:@"mosaic.glsl" ofType:nil];
    _program = createGLProgramFromFile(vertFile1.UTF8String, fragFile1.UTF8String);
}

- (void)setupGLProgram1
{
    NSString *vertFile = [[NSBundle mainBundle] pathForResource:@"vert.glsl" ofType:nil];
    NSString *fragFile = [[NSBundle mainBundle] pathForResource:@"frag.glsl" ofType:nil];
    _program1 = createGLProgramFromFile(vertFile.UTF8String, fragFile.UTF8String);
}
- (void)setupFrameAndRenderBuffer
{
    // 窗口默认帧缓存
    glGenRenderbuffers(1, &_colorRenderBuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderBuffer);
    // 为 color renderbuffer 分配存储空间
    [_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:_eaglLayer];
    
    glGenFramebuffers(1, &_frameBuffer);
    // 设置为当前 framebuffer
    glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer);
    // 将 _colorRenderBuffer 装配到 GL_COLOR_ATTACHMENT0 这个装配点上
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                              GL_RENDERBUFFER, _colorRenderBuffer);
}

- (void)setupFrameBuffer1
{
    _texture1 = createTexture2D(GL_RGBA, self.frame.size.width, self.frame.size.height, NULL);
    glGenFramebuffers(1, &_frameBuffer1);
    // 设置为当前 framebuffer
    glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer1);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture1, 0);
}
- (void)setupVBO
{
    _vertCount = 6;
    
    GLfloat vertices[] = {
        0.5f,  0.5f, 0.0f, 1.0f, 0.0f,   // 右上
        0.5f, -0.5f, 0.0f, 1.0f, 1.0f,   // 右下
        -0.5f, -0.5f, 0.0f, 0.0f, 1.0f,  // 左下
        -0.5f,  0.5f, 0.0f, 0.0f, 0.0f   // 左上
    };
    
    // 创建VBO
    _vbo = createVBO(GL_ARRAY_BUFFER, GL_STATIC_DRAW, sizeof(vertices), vertices);
    
    glEnableVertexAttribArray(glGetAttribLocation(_program, "position"));
    glVertexAttribPointer(glGetAttribLocation(_program, "position"), 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*5, NULL);
    
    glEnableVertexAttribArray(glGetAttribLocation(_program, "texcoord"));
    glVertexAttribPointer(glGetAttribLocation(_program, "texcoord"), 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*5, NULL+sizeof(GL_FLOAT)*3);
}

- (void)setupVBO1
{
    _vertCount = 6;
    
    GLfloat vertices[] = {
        1.0f,  1.0f, 0.0f, 1.0f, 1.0f,   // 右上
        1.0f, -1.0f, 0.0f, 1.0f, 0.0f,   // 右下
        -1.0f, -1.0f, 0.0f, 0.0f, 0.0f,  // 左下
        -1.0f,  1.0f, 0.0f, 0.0f, 1.0f   // 左上
    };
    
    // 创建VBO
    _vbo1 = createVBO(GL_ARRAY_BUFFER, GL_STATIC_DRAW, sizeof(vertices), vertices);
    
    glEnableVertexAttribArray(glGetAttribLocation(_program1, "position"));
    glVertexAttribPointer(glGetAttribLocation(_program1, "position"), 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*5, NULL);
    
    glEnableVertexAttribArray(glGetAttribLocation(_program1, "texcoord"));
    glVertexAttribPointer(glGetAttribLocation(_program1, "texcoord"), 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*5, NULL+sizeof(GL_FLOAT)*3);
}
- (void)render
{
    /************************离屏渲染********************************************/
    glUseProgram(_program1);
    glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer1);
    
    glClearColor(1.0, 1.0, 1.0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
    glLineWidth(2.0);
    
    glViewport(0, 0, self.frame.size.width, self.frame.size.height);
    
    [self setupVBO1];
    [self setupTexure];
    
    // 激活纹理
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, _texture);
    glUniform1i(glGetUniformLocation(_program1, "image"), 0);
    
    // 索引数组
    unsigned int indices1[] = {0,1,2,3,2,0};
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, indices1);
    
    /************************当前屏幕渲染********************************************/
    glUseProgram(_program);
    glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer);
    
    glClearColor(1.0, 1.0, 1.0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
    glLineWidth(2.0);
    
    glViewport(0, 0, self.frame.size.width, self.frame.size.height);
    
    [self setupVBO];
    
    // 激活纹理
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, _texture1);
    glUniform1i(glGetUniformLocation(_program, "image"), 1);
    
    // 索引数组
    unsigned int indices[] = {0,1,2,3,2,0};
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, indices);
    
    //将指定 renderbuffer 呈现在屏幕上,在这里我们指定的是前面已经绑定为当前 renderbuffer 的那个,在 renderbuffer 可以被呈现之前,必须调用renderbufferStorage:fromDrawable: 为之分配存储空间。
    [_context presentRenderbuffer:GL_RENDERBUFFER];
}
上一篇 下一篇

猜你喜欢

热点阅读