前言
前面我们知道,在将渲染结果从帧缓冲区呈现到屏幕上之前,可以多次调用glDrawArrays()函数进行多次渲染。那么如果想对一张图片加饱和度,加对比度等等效果,将如何做呢?一种思路是在片元着色器中对获取到的纹理图片的颜色增加饱和度和对比度,最后将处理后的结果赋值给gl_FragColor,这是一个思路,但是这显然这种做法可能并不友好,不是一种好的设计模式。另外的思路就是利用离屏渲染,所谓离屏渲染,对纹理处理的中间过程(比如加饱和度,对比度等等)并不需要显示到屏幕上的渲染,就称为离屏渲染。加载一张纹理照片,给其加饱和度,同时将这个渲染结果保存为JPG图片,这将是本文的学习目标
opengl es系列文章
opengl es之-基础概念(一)
opengl es之-GLSL语言(二)
opengl es之-GLSL语言(三)
opengl es之-常用函数介绍(四)
opengl es之-渲染两张图片(五)
opengl es之-在图片上添加对角线(六)
opengl es之-离屏渲染简介(七)
opengl es之-CVOpenGLESTextureCache介绍(八)
opengl es之-播放YUV文件(九)
思路
1、创建离屏渲染缓冲区,该缓冲区用于加载原始的RGBA数据
2、将上面1中离屏渲染缓冲区对应的纹理作为要显示到屏幕上的缓冲区的输入
2、重新在要呈现到屏幕上的缓冲区中进行家饱和度的渲染
备注:真实的场景中不会这么写,因为离屏渲染相当于多了一次渲染,很显然性能
不及一次渲染的(不过一般性能也没什么影响),根据实际需求来做。
准备
1、上下文环境,着色器程序加载等等前期准备工作
步骤不在重复,具体参考前面文章或者项目Demo
这里显然有两个着色器程序,一个着色器程序用来渲染图片到离屏帧缓冲区中;另一个用来给图片加饱和度
// 调整饱和度GLSL程序
@property (strong, nonatomic)GLProgram *satprogram;
// 使用封装的离屏渲染组件
@property (strong, nonatomic)GLRenderRGBSource *rgbSource;
项目中,我将离屏渲染封装成了一个单独的组件,它作为所有其它渲染输入的开始
2、离屏帧缓冲区的创建
glActiveTexture(GL_TEXTURE1);
glGenTextures(1, &_texture);
glBindTexture(GL_TEXTURE_2D, _texture);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, _textureOptions.minFilter);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, _textureOptions.magFilter);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, _textureOptions.wrapS);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, _textureOptions.wrapT);
// 分配指定格式的一个像素内存块,但是像素数据都初始化为0。
glTexImage2D(GL_TEXTURE_2D, 0, _textureOptions.internalFormat, (int)_size.width, (int)_size.height, 0, _textureOptions.format, _textureOptions.type, 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture, 0);
glTexImage2D(GL_TEXTURE_2D, 0, _textureOptions.internalFormat, (int)_size.width, (int)_size.height, 0, _textureOptions.format, _textureOptions.type, 0);
最后一个参数为0,代表先创建一个内存区域,但是没有传递像素数据
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture, 0);
此函数的意思就是将当前framebuffer中的渲染结果转换成纹理数据定位到_texture中,那么_texture就是一个已经带有像素数据的纹理对象了(即不需要经过应用端通过glTexImage2D()函数来赋值了),那么它就可以直接作为其它着色器程序中uniform sampler2D 类型的输入了,通过如下流程:
* glUseProgram(otherProgramHandle); // 其它着色器程序句柄
* glGenFramebuffers(1, &otherframebuffer); // otherframebuffer就是其它着色器程序对应的frame buffer
* glBindFramebuffer(GL_FRAMEBUFFER, otherframebuffer);
* glActiveTexture(GL_TEXTUREi) // 这里的i不一定要与前面[self generateTexture]中调用的相同
* glBindTexture(GL_TEXTURE_2D, texture); // texutre就是这里生成的_texture,两者一定要相同
* glUniform1i(_uniformPresentSampler, i);
* ..... // 其它程序
* glxxx()
* glDrawsArrays();
* 这段程序将以_texture中所对应的渲染结果作为纹理输入,重新开始渲染流程,期间会用指定的色器程序otherProgramHandle进行处理,最后将渲染的结果保存到
* 新的帧缓冲区otherframebuffer中,这就是实现离屏渲染的使用流程;多次离屏渲染则依次类推
* 此函数是实现多次离屏渲染的关键函数
3、加载纹理图片到离屏渲染缓冲区进行渲染
glBindTexture(GL_TEXTURE_2D, _rgbTexture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
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, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, buf)
// 1.渲染前必须先绑定FBO,这样渲染的结果将存储到该FBO中;一定要先调用glBindFramebuffer()再调用glViewport()才有效,因为它是在frame buffer的大小基础上开辟一个
// 用于渲染的区域
[outputFramebuffer activateFramebuffer];
// 2.渲染前必须清除一下之前的颜色(好比画画之前先把画板清晰干净)
glClearColor(0, 0, 0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
// 3.渲染前先激活着色器程序,这样渲染管线才知道用哪个GLSL程序
[self.rgbProgram use];
GLuint position = [self.rgbProgram attribLocationForName:@"position"];
GLuint texcoord = [self.rgbProgram attribLocationForName:@"texcoord"];
GLuint texture = [self.rgbProgram uniformLocationForName:@"texture"];
glVertexAttribPointer(position, 2, GL_FLOAT, 0, 0, positions);
glEnableVertexAttribArray(position);
glVertexAttribPointer(texcoord, 2, GL_FLOAT, 0, 0, texcoords);
glEnableVertexAttribArray(texcoord);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, _rgbTexture);
glUniform1i(texture, 1);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
// 调用此函数,这样渲染指令才会立即执行,否则渲染可能会延迟(因为opengl es默认机制是等到它自己的指令缓冲区满了之后才执行)
glFlush();
备注:
1、每次开始一个新的渲染(即调用glDrawArrays()前)都必须先绑定重新绑定帧缓冲区,否则可能渲染到其它缓冲区中去了
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glViewport(0, 0, (int)_size.width, (int)_size.height);
2、每次开始一个新的渲染都必须重新启动对应的着色器程序
glUseProgram(filterProgram);
3、glFlush();
// 调用此函数,这样渲染指令才会立即执行,否则渲染可能会延迟(因为opengl es默认机制是等到它自己的指令缓冲区满了之后才执行)
4、将上面一步的纹理作为输入,进行家饱和度的渲染
// 重新使用新的着色器程序
[self.satprogram use];
GLuint postion = [self.satprogram attribLocationForName:@"position"];
GLuint texcoord = [self.satprogram attribLocationForName:@"texcoord"];
GLuint texture = [self.satprogram uniformLocationForName:@"inputImageTexture1"];
GLuint saturate = [self.satprogram uniformLocationForName:@"temperature"];
glVertexAttribPointer(postion, 2, GL_FLOAT, 0, 0, verData1);
glEnableVertexAttribArray(postion);
glVertexAttribPointer(texcoord, 2, GL_FLOAT, 0, 0, uvData);
glEnableVertexAttribArray(texcoord);
glActiveTexture(GL_TEXTURE1);
GLuint inputTexture = self.rgbSource.outputFramebuffer.texture;
glBindTexture(GL_TEXTURE_2D, inputTexture);
glUniform1i(texture, 1);
glUniform1f(saturate, sat); //将饱和度值传给GLSL
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); // S方向上的贴图模式
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); // T方向上的贴图模式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glFlush();
[_context presentForDisplay];
备注:
上面代码中,并没有调用glGenTextures()函数生成新的纹理句柄,而是使用创建离屏渲染缓冲区时创建的纹理句柄,这个意思就是将离屏渲染的渲染结果定位的指定的纹理句柄作为本次渲染的输入,所以看到没,这里并没有调用glTexImage2D()函数传入像素数据,因为该纹理对应已经有像素数据了。
tips:上面代码中有一些是我封装好了的模块,具体可以参考项目Demo
[_context presentForDisplay];
[self.satprogram use];
以上就是离屏渲染的流程
具体参考代码
GLView.h中的方法
// 离屏渲染,保存中间结果;先将一张照片渲染到离屏缓冲区中,然后再对该照片加饱和度,最终再将结果渲染到屏幕
- (void)offscreenRenderSrc:(NSString *)spath sat:(float)sat useFastupload:(BOOL)usefastup;
项目中usefastup是对ios系统基于CVOpenGLESTextureCacheRef和CMMemoryPoolRef的高效Texture缓存系统的标记,为YES就代表使用这个高效缓冲系统,NO就是使用正常的opengl es的流程
如果对高效的Texture缓冲区感兴趣,可以参考代码
Demo
网友评论