OpenGL ES 3.0 实现多通滤波(Multi Pass)

2019-04-13 12:17发布

本文档描述了OpenGL ES 3.0 Multiple Passes(Multi-Pass)的使用原因、编程思路及具体实现的代码分析。 1、概述 多通滤波(Multiple Passes或Multi Pass),印象中模电课有此内容,可惜忘光了。最近一次遇到是用Video Toolbox作硬编码,在非实时场合,可考虑用多通滤波(Multi Pass)实现体积与质量的均衡,具体是,一通实现视频的分析,得到全局视频信息,并记录到日志中。在二通处理过程中,可根据全局信息进行码率的调整,令动态场景有更高的数据量,静态场景使用更少数据,从而在计算复杂度、体积与质量之间得到一个最优解。有关Multi Pass,我之前整理了一个文档Video Toolbox Multi-pass Encoding。 对于OpenGL ES着 {MOD}器中使用多通滤波,是因为一些图像处理算法需要访问全局像素信息,而默认情况下,程序直接输出颜 {MOD}信息到窗口系统提供的帧缓冲区,在此过程,片段着 {MOD}器无法获取其邻近片段或像素信息、只能访问指定纹理坐标(即是,从顶点着 {MOD}器中传递过来且经过管线固定功能处理的坐标值,固定功能包括图元组装、光栅化)的纹理数据,从而无法满足算法要求。因此,为实现多通滤波,不得不渲染到离线帧缓冲区,在OpenGL ES 2.0时用pbuffer实现,OpenGL ES 3.0提供了帧缓冲区对象FBO(Framebuffer Object),据OpenGL ES 3.0 Programming Guide描述,FBO比pbuffer性能更高。通过FBO + texture或FBO + Renderbuffer Object可实现渲染到离线帧缓冲区的需求。 2、实现思路 多通滤波的一般实现步骤如下所述:
  1. 创建帧缓冲区对象offline_framebuffer
  2. 创建纹理offline_texture。通常情况下,一个纹理保存一次滤波的结果。
  3. 链接纹理offline_texture到帧缓冲区对象offline_framebuffe
  4. 绑定帧缓冲区offline_framebuffe
  5. 绘制场景到帧缓冲区offline_framebuffe
  6. 执行图像处理算法,对于多通滤波,则需重复步骤2、3、4、5、6。
  7. 绑定默认帧缓冲区,即使用窗口系统提供的帧缓冲区对象
  8. 绘制一个矩形,将已处理的纹理渲染到屏幕。
下面展示两个示例,(1)基于CAEAGLLayer通过Sobel Operator实现边缘检测(Edge Detection),(2)基于GLKViewController实现快速近似抗锯齿FAXX(Fast Approximate Anti-Aliasing)。 3、实现代码分析 继承UIView的实现中,GL_FRAMEBUFFER_BINDING、GL_RENDERBUFFER_BINDING调用glGetIntegerv()得不到默认帧缓冲区和渲染缓冲区。使用GLKViewController则可以。那么,派生UIView的方式,需再创建framebuffer、renderbuffer存储多通滤波的中间结果。同时,也因为不像GLKView提供了默认帧缓冲区,派生UIView的方式在处理二通滤波时,不需要创建额外的帧缓冲区来保存中间结果,具体原因可参考我另一个文档iOS OpenGL ES 3.0 数据可视化 4:纹理映射实现2维图像与视频渲染简介 3.1、保存默认帧缓冲区 GLint defaultFBO; glGetIntegerv(GL_FRAMEBUFFER_BINDING, &defaultFBO); GLint defaultRBO; glGetIntegerv(GL_RENDERBUFFER_BINDING, &defaultRBO); 3.2、创建帧缓冲区 GLuint renderbuffer; glGenRenderbuffers(1, &renderbuffer); glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer); [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:glLayer]; GLuint framebuffer; glGenFramebuffers(1, &framebuffer); glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbuffer); 这里创建的帧缓冲区用于最终的渲染,即步骤8. 绘制一个矩形,将已处理的纹理渲染到屏幕。 3.3、获取窗口系统分析的渲染缓冲区大小 获取从CAEAGLLayer中分配的渲染缓冲区大小。这里并不使用MIPMAP技术,只需获取大小信息,为后续创建离线纹理作准备。 GLint defaultRenderbufferWidth, defaultRenderbufferHeight; glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &defaultRenderbufferWidth); glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &defaultRenderbufferHeight); printf("defalut renderbuffer size = (%d, %d) ", defaultRenderbufferWidth, defaultRenderbufferHeight); 3.4、创建离线纹理 生成离线纹理并在后面绑定到framebuffer,一通滤波(Pass 1)将纹理渲染到这个离线纹理,之后的图像处理算法操作此纹理的数据。 GLuint offline_texture; glGenTextures(1, &offline_texture); glBindTexture(GL_TEXTURE_2D, offline_texture); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexImage2D(GL_TEXTURE_2D, 0/* level */, GL_RGBA/* internalformat */, defaultRenderbufferWidth, defaultRenderbufferHeight, 0/* border */, GL_RGBA, GL_UNSIGNED_BYTE, 0); glBindTexture(GL_TEXTURE_2D, 0); 值得注意的,glTexImage2D的最后一个参数是0,不像正常情况下传递的图像数据,这是因为后续操作会将数据写入此纹理,此时根据图像数据创建的纹理内容会被修改,所以没必要提供数据源。 3.5、创建离线帧缓冲区 GLuint framebuffer_multipass; glGenFramebuffers(1, &framebuffer_multipass); glBindFramebuffer(GL_FRAMEBUFFER, framebuffer_multipass); // attach the texture to FBO color attachment point glFramebufferTexture2D(GL_FRAMEBUFFER, // 1. fbo target: GL_FRAMEBUFFER GL_COLOR_ATTACHMENT0, // 2. Color attachment point GL_TEXTURE_2D, // 3. tex target: GL_TEXTURE_2D offline_texture, // 4. color texture ID 0); // 5. mipmap level: 0(base)
glFramebufferTexture2D用于将一个2D纹理的某个mip级别或者立方图面连接到帧缓冲区附着点,它可用来将纹理作为颜 {MOD}、缓冲区或者模版附着点连接。
参数说明:
  • target:必须设置为GL_READ_FRAMEBUFFER、GL_DRAW_FRAMEBUFFER或GL_FRAMEBUFFER
  • attachment:必须为如下枚举值之一:
    • GL_COLOR_ATTACHMENTx
    • GL_DEPTH_ATTACHMENT
    • GL_STENCIL_ATTACHMENT
    • GL_DEPTH_STENCIL_ATTACHMENT
  • textarget:指定纹理目标
  • texture:指定纹理对象
  • level:指定纹理图像的mip级别
3.6、检查帧缓冲区状态 // check FBO status GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); if(status != GL_FRAMEBUFFER_COMPLETE){ printf("Framebuffer creation fails: %d", status); } 3.7、一通滤波 一通只是简单地把原始图像渲染到离线帧缓冲区中。 NSString *offline_vertexShaderString = @"#version 300 es " "layout(location = 0) in vec4 a_Position; " "layout(location = 1) in vec2 a_TexCoord; " "uniform mat4 projection_matrix; " "out vec2 v_TexCoord; " "void main() { " "mat4 scale = mat4(0.5, 0.0, 0.0, 0.0," " 0.0, 0.5, 0.0, 0.0," " 0.0, 0.0, 1.0, 0.0," " 0.0, 0.0, 0.0, 1.0);" "gl_Position = a_Position * projection_matrix; " "v_TexCoord = a_TexCoord; }"; NSString *offline_fragmentShaderString = @"#version 300 es " "precision mediump float; " "uniform sampler2D u_sampler; " "in vec2 v_TexCoord; " "out vec4 o_Color; " "void main() { " "o_Color = texture(u_sampler, v_TexCoord); }"; GLuint offline_program = glCreateProgram(); GLint offline_vertexShader = [self compileShaderWithString:offline_vertexShaderString withType:GL_VERTEX_SHADER]; GLint offline_fragmentShader = [self compileShaderWithString:offline_fragmentShaderString withType:GL_FRAGMENT_SHADER]; glAttachShader(offline_program, offline_vertexShader); glAttachShader(offline_program, offline_fragmentShader); glLinkProgram(offline_program); GLint linkStatus; glGetProgramiv(offline_program, GL_LINK_STATUS, &linkStatus); if (linkStatus == GL_FALSE) { GLint length; glGetProgramiv(offline_program, GL_INFO_LOG_LENGTH, &length); if (length > 0) { GLchar *infolog = malloc(sizeof(GLchar) * length); glGetProgramInfoLog(offline_program, length, NULL, infolog); fprintf(stderr, "link error = %s", infolog); if (infolog) { free(infolog); } } } glUseProgram(offline_program); glValidateProgram(offline_program); glClearColor(1.0, 1.0, 1.0, 1.0); glClear(GL_COLOR_BUFFER_BIT); glViewport(0, 0, defaultRenderbufferWidth, defaultRenderbufferHeight); glActiveTexture(GL_TEXTURE0); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); NSString *path = [[NSBundle mainBundle] pathForResource:@"826HS604CL29_1600x900.jpg" ofType:nil]; UIImage *image = [UIImage imageWithContentsOfFile:path]; CGImageRef imageRef = [image CGImage]; float width = CGImageGetWidth(imageRef); float height = CGImageGetHeight(imageRef); CGDataProviderRef provider = CGImageGetDataProvider(imageRef); CFDataRef textureDataRef = CGDataProviderCopyData(provider); const unsigned char *pixels = CFDataGetBytePtr(textureDataRef); GLuint texture; glGenTextures(1, &texture); glBindTexture(GL_TEXTURE_2D, texture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); int sampler = glGetUniformLocation(offline_program, "u_sampler"); glUniform1i(sampler, 0); int offline_projection_matrix_index = glGetUniformLocation(offline_program, "projection_matrix"); CGFloat ratio = defaultRenderbufferWidth * 1.0 / defaultRenderbufferHeight; GLKMatrix4 orth = GLKMatrix4MakeOrtho(-ratio, ratio, - 1, 1, -1, 1); glUniformMatrix4fv(offline_projection_matrix_index, 1, GL_FALSE, orth.m); /* 顶点 0 --- 3 | 1 --- 2 纹理 0 --- 3 | 1 --- 2 */ GLfloat vertexs[] = { -1.0f, 0.5f, 0.0f, // Position 0 0.0f, 0.0f, // TexCoord 0 -1.0f, -0.5f, 0.0f, // Position 1 0.0f, 1.0f, // TexCoord 1 1.0f, -0.5f, 0.0f, // Position 2 1.0f, 1.0f, // TexCoord 2 1.0f, 0.5f, 0.0f, // Position 3 1.0f, 0.0f // TexCoord 3 }; glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), vertexs); glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), &vertexs[3]); glEnableVertexAttribArray(0); glEnableVertexAttribArray(1); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); 3.8、二通滤波(图像处理) 二通滤波则在渲染图像到窗口系统时进行图像处理。 进行二通滤波前需要绑定默认帧缓冲区,因为新内容将渲染到窗口系统上。 // bind back to default framebuffer glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, offline_texture); 绘制代码。 NSString *fragmentShaderPath = [[NSBundle mainBundle] pathForResource:@"shader.frag" ofType:nil]; NSString *fragmentShaderString = [NSString stringWithContentsOfFile:fragmentShaderPath encoding:NSUTF8StringEncoding error:nil]; GLint vertexShader = [self compileShaderWithString:vertexShaderString withType:GL_VERTEX_SHADER]; GLint fragmentShader = [self compileShaderWithString:fragmentShaderString withType:GL_FRAGMENT_SHADER]; GLuint program = glCreateProgram(); glAttachShader(program, vertexShader); glAttachShader(program, fragmentShader); glLinkProgram(program); // GLint linkStatus; glGetProgramiv(program, GL_LINK_STATUS, &linkStatus); if (linkStatus == GL_FALSE) { GLint length; glGetProgramiv(program, GL_INFO_LOG_LENGTH, &length); if (length > 0) { GLchar *infolog = malloc(sizeof(GLchar) * length); glGetProgramInfoLog(program, length, NULL, infolog); fprintf(stderr, "link error = %s", infolog); if (infolog) { free(infolog); } } } glValidateProgram(program); glUseProgram(program); glClearColor(1.0, 1.0, 1.0, 1.0); glClear(GL_COLOR_BUFFER_BIT); CGRect frame = [UIScreen mainScreen].bounds; glViewport(0, 0, frame.size.width * glLayer.contentsScale, frame.size.height * glLayer.contentsScale); glActiveTexture(GL_TEXTURE0); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); int projection_matrix_index = glGetUniformLocation(program, "projection_matrix"); glUniformMatrix4fv(projection_matrix_index, 1, GL_FALSE, orth.m); int u_screen_width_index = glGetUniformLocation(program, "u_screen_width"); glUniform1i(u_screen_width_index, frame.size.width * 2); int u_screen_height_index = glGetUniformLocation(program, "u_screen_height"); glUniform1i(u_screen_height_index, frame.size.height * 2); glClear(GL_COLOR_BUFFER_BIT); GLfloat vertexs[] = { -1.0f, 0.5f, 0.0f, // Position 0 0.0f, 0.0f, // TexCoord 0 -1.0f, -0.5f, 0.0f, // Position 1 0.0f, 1.0f, // TexCoord 1 1.0f, -0.5f, 0.0f, // Position 2 1.0f, 1.0f, // TexCoord 2 1.0f, 0.5f, 0.0f, // Position 3 1.0f, 0.0f // TexCoord 3 }; glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), vertexs); glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), &vertexs[3]); glEnableVertexAttribArray(0); glEnableVertexAttribArray(1); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); [context presentRenderbuffer:GL_RENDERBUFFER]; 在Android 7.0上,发现在glUseProgram之后执行glBindFrameBuffer会无效,不知是不是三星的ROM有问题。先绑定帧缓冲区再使用Program则正常工作。