重新自学学习openGL 之性能优化帧缓冲

题记

我研究帧缓冲技术研究了两天,但是,其实本身帧缓冲本身不难,可是网上找的帧缓冲技术资料都非常雷同.讲 的也不够具体.可能对大神来说只是记录下而已,可对于小白来说简直看着博客就是天书,今天写的博客虽然主要借鉴这里.但是我中间会说下自己学习镇缓冲的遇到的问题.

这里先上下自己做的demo 效果


正常离屏渲染

反向渲染
灰度
核效果

帧缓冲

到目前为止,我们已经使用了很多屏幕缓冲了:用于写入颜色值的颜色缓冲、用于写入深度信息的深度缓冲和允许我们根据一些条件丢弃特定片段的模板缓冲(模板缓冲我在ios的博客也写了,但是由于看不出效果来因此没有发布.感觉ios的模板缓冲不好用,希望大神可以给与指点)。这些缓冲 结合起来叫做帧缓冲(Framebuffer),它被储存在内存中。OpenGL允许我们定义我们自己的帧缓冲,也就是说我们能够定义我们自己的颜色缓冲,甚至是深度缓冲和模板缓冲。

一个帧缓冲可以包含颜色缓冲,深度缓冲和模板缓冲。

以前的博客所做的所有操作都是在默认帧缓冲的渲染缓冲上进行的。默认的帧缓冲是在你创建窗口的时候生成和配置的(GLFW帮我们做了这些)。有了我们自己的帧缓冲,我们就能够有更多方式来渲染了。

你可能不能很快理解帧缓冲的应用,但渲染你的场景到不同的帧缓冲能够让我们在场景中加入类似镜子的东西,或者做出很酷的后期处理效果。首先我们会讨论它是如何工作的,之后我们将来实现这些炫酷的后期处理效果。

基本原理

原理图

在OpenGL渲染管道中,对几何数据和纹理进行了转换,并通过了多次测试,最后以二维像素的形式呈现到屏幕上OpenGL管道的最终呈现目标称为framebufferframebuffer是OpenGL使用的二维数组或存储的集合;颜色缓冲区、深度缓冲区、模板缓冲区和累积缓冲区。默认情况下,OpenGL使用framebuffer作为完全由窗口系统创建和管理的呈现目标此默认帧缓冲区称为Windows系统提供的帧缓冲区

OpenGL扩展名gl_arb_framebuffer_对象提供了一个接口,用于创建其他不可显示的framebuffer对象(FBO)。这个framebuffer被称为应用程序创建的framebuffer,以区别于系统提供的默认窗口framebuffer。通过使用framebuffer对象(FBO),OpenGL应用程序可以将渲染输出重定向到应用程序创建的framebuffer对象(FBO),而不是传统的窗口系统提供的framebuffer。而且,它完全由OpenGL控制。

与窗口系统提供的帧缓冲区类似,FBO包含渲染目标的集合;颜色、深度和模具缓冲区。(请注意,累积缓冲区未在FBO中定义。)FBO中的这些逻辑缓冲区称为帧缓冲区可附加图像这些逻辑缓冲区是可以附加到帧缓冲区对象的二维像素数组

有两种类型的帧缓冲区可附加图像:纹理图像和渲染缓冲图像。如果纹理对象的图像附加到帧缓冲区,OpenGL将执行“渲染到纹理”。如果renderbuffer对象的图像附加到framebuffer,则OpenGL将执行“屏幕外渲染”。(见上图结构)

另外,renderbuffer对象是在gl_arb_framebuffer_object扩展中定义的一种新的存储对象。它在渲染过程中用作单个二维图像的渲染目标。

OpenGL帧缓冲对象(FBO)
FBO、纹理和渲染器之间的连接
上图显示了framebuffer对象、纹理对象和renderbuffer对象之间的连接。可以通过附着点将多个纹理对象或renderBuffer对象附着到帧缓冲区对象。
(opengl 3.0 是有多个纹理对象的, 但是opengl 2.0 貌似只有连接一个纹理对象)

帧缓冲区对象中有多个颜色附着点(gl_color_attachment0,…、gl_color_attachmentn)、一个深度附着点(gl_depth_attachment)和一个模具附着点(gl_stencil_attachment)。颜色附着点的数量取决于实现,但每个FBO必须至少有一个颜色附着点。您可以使用图形卡支持的gl_max_color_附件查询最大颜色附着点数。FBO具有多个颜色附加点的原因是允许同时将颜色缓冲区呈现到多个目标。这个“多重渲染目标”(MRT)可以通过gl_Arb_Draw_Buffers扩展来完成。请注意,framebuffer对象本身没有任何图像存储(数组),但它只有多个附着点。

帧缓冲区对象(FBO)提供了一种有效的切换机制;将以前的帧缓冲区可附加图像与FBO分离并将新的帧缓冲区可附加图像附加到FBO切换帧缓冲区可附加图像比在FBO之间切换快得多。FBO提供glframeBufferTexture2d()以切换二维纹理对象,以及glframeBufferRenderBuffer()以切换renderBuffer对象。

这里需要关注的点, framebuffer 是一个数组,数组中的item是缓冲区的id
而 framebuffer 有两种缓冲区 纹理缓冲区和深度缓冲区
framebuffer 渲染的图像是不会直接显示在屏幕上的

这里也可以framebuffer 比喻成工厂,而绑定的缓冲区是工厂的设备.

帧缓冲的具体使用

创建一个帧缓冲

和OpenGL中的其它对象一样,我们会使用一个叫做glGenFramebuffers的函数来创建一个帧缓冲对象(Framebuffer Object, FBO):

unsigned int fbo;
glGenFramebuffers(1, &fbo);

这种创建和使用对象的方式我们已经见过很多次了,所以它的使用函数也和其它的对象类似。首先我们创建一个帧缓冲对象,将它绑定为激活的(Active)帧缓冲,做一些操作,之后解绑帧缓冲。我们使用glBindFramebuffer来绑定帧缓冲。

glBindFramebuffer(GL_FRAMEBUFFER, fbo);

在绑定到GL_FRAMEBUFFER目标之后,所有的读取写入帧缓冲的操作将会影响当前绑定的帧缓冲。我们也可以使用GL_READ_FRAMEBUFFERGL_DRAW_FRAMEBUFFER,将一个帧缓冲分别绑定到读取目标写入目标。绑定到GL_READ_FRAMEBUFFER的帧缓冲将会使用在所有像是glReadPixels的读取操作中,而绑定到GL_DRAW_FRAMEBUFFER的帧缓冲将会被用作渲染、清除等写入操作的目标。大部分情况你都不需要区分它们,通常都会使用GL_FRAMEBUFFER,绑定到两个上。

不幸的是,我们现在还不能使用我们的帧缓冲,因为它还不完整(Complete),一个完整的帧缓冲需要满足以下的条件:

  • 附加至少一个缓冲(颜色、深度或模板缓冲)。
  • 至少有一个颜色附件(Attachment)。
  • 所有的附件都必须是完整的(保留了内存)。
  • 每个缓冲都应该有相同的样本数。

解释,这里我们创建了Framebuffer , framebuffer相当于工厂,可是没有设备是没办法工作的.因此,我们还需要给工厂采购设备才行.

上面的条件中可以知道,我们需要为帧缓冲创建一些附件,并将附件附加到帧缓冲上。在完成所有的条件之后,我们可以GL_FRAMEBUFFER以参数调用glCheckFramebufferStatus检查帧缓冲是否完整。它将会检测当前绑定的帧缓冲,并返回规范中这些值的其中之一。如果它返回的是GL_FRAMEBUFFER_COMPLETE,帧缓冲就是完整的了。

f(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE)
  // 执行胜利的舞蹈

之后所有的渲染操作将会渲染到当前绑定帧缓冲的附件中。由于我们的帧缓冲不是默认帧缓冲,渲染指令将不会对窗口的视觉输出有任何影响。出于这个原因,渲染到一个不同的帧缓冲被叫做离屏渲染(Off-screen Rendering)。要保证所有的渲染操作在主窗口中有视觉效果,我们需要再次激活默认帧缓冲,将它绑定到0

glBindFramebuffer(GL_FRAMEBUFFER, 0);

这里需要注意,我们是切换到Framebuffer0,但是最终渲染的Framebuffer 的值不见得是0.

在完成所有的帧缓冲操作之后,不要忘记删除这个帧缓冲对象:

glDeleteFramebuffers(1, &fbo);

在完整性检查执行之前,我们需要给帧缓冲附加一个附件。附件是一个内存位置,它能够作为帧缓冲的一个缓冲,可以将它想象为一个图像。当创建一个附件的时候我们有两个选项:纹理或渲染缓冲对象(Renderbuffer Object)。

添加 Framebuffer 附件 (工厂中的设备)

纹理附件

当把一个纹理附加到帧缓冲的时候,所有的渲染指令将会写入到这个纹理中,就想它是一个普通的颜色/深度或模板缓冲一样。使用纹理的优点是,所有渲染操作的结果将会被储存在一个纹理图像中,我们之后可以在着色器中很方便地使用它。

这里需要加深印象.当我们绑定纹理到帧缓冲的时候,渲染指令将会写入到这个纹理中
其实这个纹理就是我们渲染的结果

为帧缓冲创建一个纹理和创建一个普通的纹理差不多:

unsigned int texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 800, 600, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

帧缓冲里面不需要数据,需要渲染的时候写入数据,而普通纹理需要数据.供顶点使用
那么我们在帧缓冲的纹理附件中写入数据可以不?应该是可以的.但是我们在每次绘制的时候都会清理该附件中的数据,因此写入的数据就没啥意义了.

主要的区别就是,我们将维度设置为了屏幕大小(尽管这不是必须的),并且我们给纹理的data参数传递了NULL。对于这个纹理,我们仅仅分配了内存而没有填充它。填充这个纹理将会在我们渲染到帧缓冲之后来进行。同样注意我们并不关心环绕方式或多级渐远纹理,我们在大多数情况下都不会需要它们。

如果你想将你的屏幕渲染到一个更小或更大的纹理上,你需要(在渲染到你的帧缓冲之前)再次调用glViewport,使用纹理的新维度作为参数,否则只有一小部分的纹理或屏幕会被渲染到这个纹理上。

现在我们已经创建好一个纹理了,要做的最后一件事就是将它附加到帧缓冲上了:

glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);

glFrameBufferTexture2D有以下的参数:
+target:帧缓冲的目标(绘制、读取或者两者皆有)

  • attachment:我们想要附加的附件类型。当前我们正在附加一个颜色附件。注意最后的0意味着我们可以附加多个颜色附件。我们将在之后的教程中提到。
    +textarget:你希望附加的纹理类型
  • texture:要附加的纹理本身
  • level:多级渐远纹理的级别。我们将它保留为0。
渲染缓冲对象附件

渲染缓冲对象(Renderbuffer Object)是在纹理之后引入到OpenGL中,作为一个可用的帧缓冲附件类型的,所以在过去纹理是唯一可用的附件。和纹理图像一样,渲染缓冲对象是一个真正的缓冲,即一系列的字节、整数、像素等。渲染缓冲对象附加的好处是,它会将数据储存为OpenGL原生的渲染格式,它是为离屏渲染到帧缓冲优化过的。

渲染缓冲对象直接将所有的渲染数据储存到它的缓冲中,不会做任何针对纹理格式的转换,让它变为一个更快的可写储存介质。然而,渲染缓冲对象通常都是只写的,所以你不能读取它们(比如使用纹理访问)。当然你仍然还是能够使用glReadPixels来读取它,这会从当前绑定的帧缓冲,而不是附件本身,中返回特定区域的像素。

因为它的数据已经是原生的格式了,当写入或者复制它的数据到其它缓冲中时是非常快的。所以,交换缓冲这样的操作在使用渲染缓冲对象时会非常快。我们在每个渲染迭代最后使用的glfwSwapBuffers,也可以通过渲染缓冲对象实现:只需要写入一个渲染缓冲图像,并在最后交换到另外一个渲染缓冲就可以了。渲染缓冲对象对这种操作非常完美。

创建一个渲染缓冲对象的代码和帧缓冲的代码很类似:

unsigned int rbo;
glGenRenderbuffers(1, &rbo);
  glBindRenderbuffer(GL_RENDERBUFFER, self.rbo);
 CGFloat scale = UIScreen.mainScreen.scale;
    GLsizei fbo_width = [UIScreen mainScreen].bounds.size.width*scale;
    GLsizei fbo_height =  [UIScreen mainScreen].bounds.size.height*scale;
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, fbo_width, fbo_height);
    glBindRenderbuffer(GL_RENDERBUFFER,0);
  ///绑定渲染buffer
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER,  self.rbo);

由于渲染缓冲对象通常都是只写的,它们会经常用于深度和模板附件,因为大部分时间我们都不需要从深度和模板缓冲中读取值,只关心深度和模板测试。我们需要深度和模板值用于测试,但不需要对它们进行采样,所以渲染缓冲对象非常适合它们。当我们不需要从这些缓冲中采样的时候,通常都会选择渲染缓冲对象,因为它会更优化一点。

渲染到纹理

既然我们已经知道帧缓冲(大概)是怎么工作的了,是时候实践它们了。我们将会将场景渲染到一个附加到帧缓冲对象上的颜色纹理中,之后将在一个横跨整个屏幕的四边形上绘制这个纹理。这样视觉输出和没使用帧缓冲时是完全一样的,但这次是打印到了一个四边形上。这为什么很有用呢?我们会在下一部分中知道原因。
首先要创建一个帧缓冲对象,并绑定它,这些都很直观:

unsigned int framebuffer;
glGenFramebuffers(1, &framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);

接下来我们需要创建一个纹理图像,我们将它作为一个颜色附件附加到帧缓冲上。我们将纹理的维度设置为窗口的宽度和高度,并且不初始化它的数据:

// 生成纹理
unsigned int texColorBuffer;
glGenTextures(1, &texColorBuffer);
glBindTexture(GL_TEXTURE_2D, texColorBuffer);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 800, 600, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, 0);

// 将它附加到当前绑定的帧缓冲对象
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texColorBuffer, 0);  

我们还希望OpenGL能够进行深度测试(如果你需要的话还有模板测试),所以我们还需要添加一个深度(和模板)附件到帧缓冲中。由于我们只希望采样颜色缓冲,而不是其它的缓冲,我们可以为它们创建一个渲染缓冲对象。还记得当我们不需要采样缓冲的时候,渲染缓冲对象是更好的选择吗?

unsigned int rbo;
glGenRenderbuffers(1, &rbo);
  glBindRenderbuffer(GL_RENDERBUFFER, self.rbo);
 CGFloat scale = UIScreen.mainScreen.scale;
    GLsizei fbo_width = [UIScreen mainScreen].bounds.size.width*scale;
    GLsizei fbo_height =  [UIScreen mainScreen].bounds.size.height*scale;
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, fbo_width, fbo_height);
    glBindRenderbuffer(GL_RENDERBUFFER,0);
  ///绑定渲染buffer
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER,  self.rbo);

最后,我们希望检查帧缓冲是否是完整的,如果不是,我们将打印错误信息。

if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
    std::cout << "ERROR::FRAMEBUFFER:: Framebuffer is not complete!" << std::endl;
glBindFramebuffer(GL_FRAMEBUFFER, 0);

记得要解绑帧缓冲,保证我们不会不小心渲染到错误的帧缓冲上。

现在这个帧缓冲就完整了,我们只需要绑定这个帧缓冲对象,让渲染到帧缓冲的缓冲中而不是默认的帧缓冲中。之后的渲染指令将会影响当前绑定的帧缓冲。所有的深度和模板操作都会从当前绑定的帧缓冲的深度和模板附件中(如果有的话)读取。如果你忽略了深度缓冲,那么所有的深度测试操作将不再工作,因为当前绑定的帧缓冲中不存在深度缓冲。

所以,要想绘制场景到一个纹理上,我们需要采取以下的步骤:

  • 1.将新的帧缓冲绑定为激活的帧缓冲,和往常一样渲染场景
  • 2.绑定默认的帧缓冲
  • 3.绘制一个横跨整个屏幕的四边形,将帧缓冲的颜色缓冲作为它的纹理。

场景

在屏幕上绘制一个立方体.
绘制的方式如下:
我们使用离屏渲染将渲染结果渲染到 framebuffer的颜色附件中,从颜色附件中读取数据,然后将其渲染到屏幕上.

我们从颜色附件中读取数据到屏幕上,因此没有深度和模板测试,只是单纯的绘制一个屏幕长方体而已.因此shader比较简单

precision mediump float;
attribute vec3 aPos;
//attribute vec3 aNormal;
attribute vec2 aTexCoords;

varying vec2 vTexCoords;

//uniform mat4 model;
//uniform mat4 view;
//uniform mat4 projection;

void main()
{
    vTexCoords = aTexCoords;
    
    gl_Position =vec4(aPos.x, aPos.y, 0.0, 1.0); 
}

precision mediump float;
varying vec2 vTexCoords;
uniform sampler2D uTexture;

void main()
{
    vec3  textureColor = texture2D(uTexture, vTexCoords).rgb;
 
    gl_FragColor = vec4(textureColor,1.0);
}

不采用离屏渲染的屏幕结果


从上图看出来,我们就是简单的渲染一个立方体而已.没有啥特别的地方(立方体怎么渲染可以看前面的文章)

核心代码

#import "DefaultRenderViewController.h"
#import "DefaultRenderBindObject.h"
#import "CubeManager.h"
float DR_planeVertices[] = {
    // positions          // texture Coords (note we set these higher than 1 (together with GL_REPEAT as texture wrapping mode). this will cause the floor texture to repeat)
    5.0f, -0.5f,  5.0f,  2.0f, 0.0f,
    -5.0f, -0.5f,  5.0f,  0.0f, 0.0f,
    -5.0f, -0.5f, -5.0f,  0.0f, 2.0f,
    
    5.0f, -0.5f,  5.0f,  2.0f, 0.0f,
    -5.0f, -0.5f, -5.0f,  0.0f, 2.0f,
    5.0f, -0.5f, -5.0f,  2.0f, 2.0f
};

@interface DefaultRenderViewController ()
@property (nonatomic ,strong) Vertex * vertex;
@property (nonatomic ,strong) Vertex * planeVertex ;
@property (nonatomic ,strong) TextureUnit * cubeUnit ;
@property (nonatomic ,strong) TextureUnit * floorUnit ;
@end

@implementation DefaultRenderViewController

-(void)initSubObject{
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);
    self.bindObject = [DefaultRenderBindObject new];
}


-(void)loadVertex{
    self.vertex= [Vertex new];
    int vertexNum =[CubeManager getTextureNormalVertexNum];
    [self.vertex allocVertexNum:vertexNum andEachVertexNum:5];
    for (int i=0; iuniforms[DR_uniform_view], 1, 0,viewMatrix.m);
}
-(void)_bindProjectionMatrix4:(GLBaseBindObject*)bindObject{
    GLfloat aspectRatio= CGRectGetWidth([UIScreen mainScreen].bounds) / CGRectGetHeight([UIScreen mainScreen].bounds);
    GLKMatrix4 projectionMatrix =
    GLKMatrix4MakePerspective(
                              GLKMathDegreesToRadians(100.0f),
                              aspectRatio,
                              0.1f,
                              100.0f);
    glUniformMatrix4fv(bindObject->uniforms[DR_uniform_projection], 1, 0,projectionMatrix.m);
}

    
-(void)glkView:(GLKView *)view drawInRect:(CGRect)rect{
    
    glClearColor(1,0, 0, 1);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    GLKMatrix4 cubeMode1 =[self _getModeMatrix4Location:GLKVector3Make(0.0f, 0.0f, 1.0f)];
    cubeMode1 = GLKMatrix4Rotate(cubeMode1, 30*M_PI/180.0, 1, 1, 0);
    [self.shader use];
    [self _bindViewMatrix4:self.bindObject];
    [self _bindProjectionMatrix4:self.bindObject];
    [self _drawFloor:self.bindObject];
    [self _drawCubeMode:cubeMode1 bindObject:self.bindObject];
   
}

-(GLKMatrix4)_getModeMatrix4Location:(GLKVector3)location{
    GLKMatrix4  mode = GLKMatrix4Identity;
    mode =  GLKMatrix4TranslateWithVector3(mode, location);
//    mode = GLKMatrix4Rotate(mode, 30*M_PI/180.0, 0, 1, 0);
    return mode;
}

-(void)_drawCubeMode:(GLKMatrix4)mode bindObject:(GLBaseBindObject*)bindObject{
  
    glUniformMatrix4fv(bindObject->uniforms[DR_uniform_model], 1, 0,mode.m);
    
    [self.cubeUnit bindtextureUnitLocationAndShaderUniformSamplerLocation:bindObject->uniforms[DR_uniform_Texture]];
    [self.vertex enableVertexInVertexAttrib:DR_aPos numberOfCoordinates:3 attribOffset:0];
    [self.vertex enableVertexInVertexAttrib:DR_aTexCoords numberOfCoordinates:2 attribOffset:sizeof(float)*3];
    
    [self.vertex drawVertexWithMode:GL_TRIANGLES startVertexIndex:0 numberOfVertices:[CubeManager getTextureNormalVertexNum]];
}

-(void)_drawFloor:(GLBaseBindObject*)bindObject{
    
    GLKMatrix4 mode = GLKMatrix4Identity;
    glUniformMatrix4fv(bindObject->uniforms[DR_uniform_model], 1, 0,mode.m);
     [self.floorUnit bindtextureUnitLocationAndShaderUniformSamplerLocation:bindObject->uniforms[DR_uniform_Texture]];
    [self.planeVertex enableVertexInVertexAttrib:DR_aPos numberOfCoordinates:3 attribOffset:0];
    [self.planeVertex enableVertexInVertexAttrib:DR_aTexCoords numberOfCoordinates:2 attribOffset:sizeof(float)*3];
    
    [self.planeVertex drawVertexWithMode:GL_TRIANGLES startVertexIndex:0 numberOfVertices:6];
}

@end

这里的背景颜色是红色的glClearColor(1,0, 0, 1);控制的.

离屏渲染

离屏渲染
float OSR_planeVertices[] = {
    // positions          // texture Coords (note we set these higher than 1 (together with GL_REPEAT as texture wrapping mode). this will cause the floor texture to repeat)
    5.0f, -0.5f,  5.0f,  2.0f, 0.0f,
    -5.0f, -0.5f,  5.0f,  0.0f, 0.0f,
    -5.0f, -0.5f, -5.0f,  0.0f, 2.0f,
    
    5.0f, -0.5f,  5.0f,  2.0f, 0.0f,
    -5.0f, -0.5f, -5.0f,  0.0f, 2.0f,
    5.0f, -0.5f, -5.0f,  2.0f, 2.0f
};

float OSR_quadVertices[] = { // vertex attributes for a quad that fills the entire screen in Normalized Device Coordinates.
    // positions   // texCoords
    -1.0f,  1.0f,  0.0f, 1.0f,
    -1.0f, -1.0f,  0.0f, 0.0f,
    1.0f, -1.0f,  1.0f, 0.0f,
    
    -1.0f,  1.0f,  0.0f, 1.0f,
    1.0f, -1.0f,  1.0f, 0.0f,
    1.0f,  1.0f,  1.0f, 1.0f
};

@interface OffScreenRenderViewController ()
@property (nonatomic ,strong) Vertex * vertex;
@property (nonatomic ,strong) Vertex * planeVertex ;
@property (nonatomic ,strong) TextureUnit * cubeUnit ;
@property (nonatomic ,strong) TextureUnit * floorUnit ;

@property (nonatomic ,strong) Vertex * quadVertex ;
@property (nonatomic ,strong) FrameBuffer * framebuffer ;
@property (nonatomic ,strong) RenderBuffer * renderBuffer ;
@property (nonatomic ,strong) TextureUnit * spaceUnit ;


@property (nonatomic , assign) GLint mDefaultFBO;

@end

@implementation OffScreenRenderViewController

-(void)initSubObject{
    self.bindObject = [OffScreenRenderBindObject new];
    self.frameBufferBinder = [FrameBufferBindObject new];
    
    ///颜色组件
    self.spaceUnit = [TextureUnit new];
    [self.spaceUnit createSpaceTextureUnit];

    self.renderBuffer = [RenderBuffer new];
    [self.renderBuffer bindRenderBuffer:^(RenderBuffer * _Nonnull buffer) {
        [buffer setRenderStorageWithInternalformat:GL_DEPTH_COMPONENT16];
    }];
    
    
    self.framebuffer = [FrameBuffer new];
    [self.framebuffer bindOffScreenBufferAndSettingBlock:^(FrameBuffer * _Nonnull buffer) {
        [buffer bindColorAttachmentInTexture:self.spaceUnit.textureBuffer];
        [buffer bingDepthAttachmentInRenderBuffer:self.renderBuffer.rbo];
        [buffer check];
    }];

}

-(void)loadVertex{
    self.vertex= [Vertex new];
    int vertexNum =[CubeManager getTextureNormalVertexNum];
    [self.vertex allocVertexNum:vertexNum andEachVertexNum:5];
    for (int i=0; iuniforms[OSR_uniform_view], 1, 0,viewMatrix.m);
}
-(void)_bindProjectionMatrix4:(GLBaseBindObject*)bindObject{
    GLfloat aspectRatio= CGRectGetWidth([UIScreen mainScreen].bounds) / CGRectGetHeight([UIScreen mainScreen].bounds);
    GLKMatrix4 projectionMatrix =
    GLKMatrix4MakePerspective(
                              GLKMathDegreesToRadians(100.0f),
                              aspectRatio,
                              0.1f,
                              100.0f);
    glUniformMatrix4fv(bindObject->uniforms[OSR_uniform_projection], 1, 0,projectionMatrix.m);
}

    
-(void)glkView:(GLKView *)view drawInRect:(CGRect)rect{
     glGetIntegerv(GL_FRAMEBUFFER_BINDING, &_mDefaultFBO);
    [self.framebuffer bindOffScreenBuffer];
    glEnable(GL_DEPTH_TEST);
    glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    GLKMatrix4 cubeMode1 =[self _getModeMatrix4Location:GLKVector3Make(0.0f, 0.0f, 1.0f)];
    cubeMode1 = GLKMatrix4Rotate(cubeMode1, 30*M_PI/180.0,1, 1, 0);
    [self.shader use];
    [self _bindViewMatrix4:self.bindObject];
    [self _bindProjectionMatrix4:self.bindObject];
    [self _drawFloor:self.bindObject];
    [self _drawCubeMode:cubeMode1 bindObject:self.bindObject];
    glBindFramebuffer(GL_FRAMEBUFFER, self.mDefaultFBO);
    glDisable(GL_DEPTH_TEST);
    glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    [self.frameShader use];
    
    [self.spaceUnit activeTextureUnit:GL_TEXTURE2];
    [self.spaceUnit bindtextureUnitLocationAndShaderUniformSamplerLocation:self.frameBufferBinder->uniforms[FB_uniform_Texture]];
    [self.quadVertex enableVertexInVertexAttrib:OSR_aPos numberOfCoordinates:2 attribOffset:0];
    [self.quadVertex enableVertexInVertexAttrib:OSR_aTexCoords numberOfCoordinates:2 attribOffset:sizeof(float)*2];
    [self.quadVertex drawVertexWithMode:GL_TRIANGLES startVertexIndex:0 numberOfVertices:6];
}

-(GLKMatrix4)_getModeMatrix4Location:(GLKVector3)location{
    GLKMatrix4  mode = GLKMatrix4Identity;
    mode =  GLKMatrix4TranslateWithVector3(mode, location);
//    mode = GLKMatrix4Rotate(mode, 30*M_PI/180.0, 0, 1, 0);
    return mode;
}

-(void)_drawCubeMode:(GLKMatrix4)mode bindObject:(GLBaseBindObject*)bindObject{
  
    glUniformMatrix4fv(bindObject->uniforms[OSR_uniform_model], 1, 0,mode.m);
    [self.cubeUnit bindtextureUnitLocationAndShaderUniformSamplerLocation:bindObject->uniforms[OSR_uniform_Texture]];
    [self.vertex enableVertexInVertexAttrib:OSR_aPos numberOfCoordinates:3 attribOffset:0];
    [self.vertex enableVertexInVertexAttrib:OSR_aTexCoords numberOfCoordinates:2 attribOffset:sizeof(float)*3];
    
    [self.vertex drawVertexWithMode:GL_TRIANGLES startVertexIndex:0 numberOfVertices:[CubeManager getTextureNormalVertexNum]];
}

-(void)_drawFloor:(GLBaseBindObject*)bindObject{
    
    GLKMatrix4 mode = GLKMatrix4Identity;
    glUniformMatrix4fv(bindObject->uniforms[OSR_uniform_model], 1, 0,mode.m);
    [self.floorUnit bindtextureUnitLocationAndShaderUniformSamplerLocation:bindObject->uniforms[OSR_uniform_Texture]];
    [self.planeVertex enableVertexInVertexAttrib:OSR_aPos numberOfCoordinates:3 attribOffset:0];
    [self.planeVertex enableVertexInVertexAttrib:OSR_aTexCoords numberOfCoordinates:2 attribOffset:sizeof(float)*3];
    
    [self.planeVertex drawVertexWithMode:GL_TRIANGLES startVertexIndex:0 numberOfVertices:6];
}

@end

@interface RenderBuffer ()


@end

@implementation RenderBuffer
- (instancetype)init
{
    self = [super init];
    if (self) {
        [self _customInit];
    }
    return self;
}

-(void)_customInit{
    [self _createRenderBuffer];
}
-(void)bindRenderBuffer:(void(^)(RenderBuffer * buffer))renderBufferSettingBlock;{
    glBindRenderbuffer(GL_RENDERBUFFER, self.rbo);
    if (renderBufferSettingBlock) {
        renderBufferSettingBlock(self);
    }
    glBindRenderbuffer(GL_RENDERBUFFER,0);
}

-(void)setRenderStorageWithInternalformat:(GLenum) internalformat{
    CGFloat scale = UIScreen.mainScreen.scale;
    GLsizei fbo_width = [UIScreen mainScreen].bounds.size.width*scale;
    GLsizei fbo_height =  [UIScreen mainScreen].bounds.size.height*scale;
    glRenderbufferStorage(GL_RENDERBUFFER, internalformat, fbo_width, fbo_height);

}



#pragma mark  - private
-(void)_createRenderBuffer{
    glGenRenderbuffers(1, &_rbo);
}

-(void)_deleteRenderBuffer{
    glDeleteRenderbuffers(1, &_rbo);
}


- (void)dealloc
{
    [self _deleteRenderBuffer];
}
@end

#import "FrameBuffer.h"

@interface FrameBuffer()
@property (nonatomic, assign) GLuint fbo;
@end

@implementation FrameBuffer

- (instancetype)init
{
    self = [super init];
    if (self) {
        [self _customInit];
    }
    return self;
}

-(void)_customInit{
    [self _createFrameBuffer];
}

-(void)bindOffScreenBuffer{
     glBindFramebuffer(GL_FRAMEBUFFER, self.fbo);
}

-(void)bindOffScreenBufferAndSettingBlock:(void(^)(FrameBuffer * frameBuffer))frameBufferSettingBlock{
    glBindFramebuffer(GL_FRAMEBUFFER, self.fbo);
    if (frameBufferSettingBlock) {
        frameBufferSettingBlock(self);
    }
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

-(BOOL)check{
    if(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE){
        NSLog(@"[framebuffer]:ok");
        return YES;
    }
    NSLog(@"[framebuffer]:error");
    return NO;
}

-(void)bindColorAttachmentInTexture:(GLuint) texture{
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
}

-(void)bingDepthAttachmentInRenderBuffer:(GLint)renderBuffer{
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, renderBuffer);

}

#pragma mark  - private
-(void)_createFrameBuffer{
    glGenFramebuffers(1, &_fbo);
}

-(void)_deleteFrameBuffer{
    glDeleteFramebuffers(1, &_fbo);
}


- (void)dealloc
{
    [self _deleteFrameBuffer];
}
@end
#import "TextureUnit.h"

@interface TextureUnit ()

@property (nonatomic ,assign) int textureUnitLocation ;
@end

@implementation TextureUnit
- (instancetype)init
{
    self = [super init];
    if (self) {
        [self _customInit];
    }
    return self;
}
-(void)_customInit{
    self.textureUnitLocation = -1;
    glGenTextures(1, &_textureBuffer);
}

#pragma mark  - public
-(void)setPixels: (float*) pixels pixelsWidth:(GLsizei)width pixelsHeight:(GLsizei)height IntoTextureUnit:(GLenum)textureUnit andConfigTextureUnit:(nullable void(^)(void))configTextureUnitBlock PixelFormat:(GLint)internalformat{
    glActiveTexture(textureUnit);
    self.textureUnitLocation = [self _getTextureBindLocationForTexture:textureUnit];
    glBindTexture(GL_TEXTURE_2D,  _textureBuffer);
     glTexImage2D(GL_TEXTURE_2D, 0, internalformat , width, height, 0, internalformat, GL_FLOAT, pixels);
    if (configTextureUnitBlock) {
        configTextureUnitBlock();
    }else{
      [self _textureBaseConfig];
    }
}

-(void)setImage:(UIImage *)image IntoTextureUnit:(GLenum)textureUnit andConfigTextureUnit:(nullable void(^)(void))configTextureUnitBlock  {
    [self setImage:image IntoTextureUnit:textureUnit andConfigTextureUnit:configTextureUnitBlock PixelFormat:GL_RGBA];
}
-(void)setImage:(UIImage *)image IntoTextureUnit:(GLenum)textureUnit andConfigTextureUnit:(void(^)(void))configTextureUnitBlock  PixelFormat:(GLint)internalformat{
    glActiveTexture(textureUnit);
    self.textureUnitLocation = [self _getTextureBindLocationForTexture:textureUnit];
    glBindTexture(GL_TEXTURE_2D,  _textureBuffer);
    GLubyte *imageData = [self _getImageData:image];
    glTexImage2D(GL_TEXTURE_2D, 0, internalformat , image.size.width, image.size.height, 0, internalformat, GL_UNSIGNED_BYTE, imageData);
    free(imageData);
    if (configTextureUnitBlock) {
        configTextureUnitBlock();
    }else{
        [self _textureBaseConfig];
    }
}
-(void)setImage:(UIImage *)image andConfigTextureUnit:(nullable  void(^)(void))configTextureUnitBlock {
    [self setImage:image andConfigTextureUnit:configTextureUnitBlock PixelFormat:GL_RGBA];
}
-(void)setImage:(UIImage *)image andConfigTextureUnit:(nullable  void(^)(void))configTextureUnitBlock  PixelFormat:(GLint)internalformat{
    glBindTexture(GL_TEXTURE_2D,  _textureBuffer);
    GLubyte *imageData = [self _getImageData:image];
    glTexImage2D(GL_TEXTURE_2D, 0, internalformat , image.size.width, image.size.height, 0, internalformat, GL_UNSIGNED_BYTE, imageData);
    free(imageData);
    if (configTextureUnitBlock) {
        configTextureUnitBlock();
    }else{
        [self _textureBaseConfig];
    }
}
-(void)activeTextureUnit:(GLenum)textureUnit{
    glActiveTexture(textureUnit);
    glBindTexture(GL_TEXTURE_2D,  _textureBuffer);
    self.textureUnitLocation = [self _getTextureBindLocationForTexture:textureUnit];
}


-(void)bindtextureUnitLocationAndShaderUniformSamplerLocation:(GLint) uniformSamplerLocation {
    if (self.textureUnitLocation == -1) {
        NSLog(@"没有设置纹理单元或者设置纹理单元错误");
        return;
    }
    glUniform1i(uniformSamplerLocation, self.textureUnitLocation);
    GLenum error = glGetError();
    if(GL_NO_ERROR != error)
    {
        NSLog(@"GL Error: bindtextureUnitLocationAndShaderUniformSamplerLocation 0x%x", error);
    }
    
}

//创建空的纹理单元
-(void)createSpaceTextureUnit{
    CGFloat scale = UIScreen.mainScreen.scale;
    GLsizei fbo_width = [UIScreen mainScreen].bounds.size.width*scale;
    GLsizei fbo_height =  [UIScreen mainScreen].bounds.size.height*scale;
    glBindTexture(GL_TEXTURE_2D,  self.textureBuffer);
    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, GL_RGBA, fbo_width, fbo_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
    glBindTexture(GL_TEXTURE_2D, 0);
}



#pragma mark  - private

-(int)_getTextureBindLocationForTexture:(GLenum)texture{
    int textureLocation = texture-GL_TEXTURE0;
    if (textureLocation>=0 && textureLocation <32) {
        return textureLocation;
    }
    NSLog(@"超出纹理单元");
    return -1;
}
-(void)_textureBaseConfig{
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_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);
}

- (void*)_getImageData:(UIImage*)image{
    CGImageRef imageRef = [image CGImage];
    size_t imageWidth = CGImageGetWidth(imageRef);
    size_t imageHeight = CGImageGetHeight(imageRef);
    GLubyte *imageData = (GLubyte *)malloc(imageWidth*imageHeight*4);
    memset(imageData, 0,imageWidth *imageHeight*4);
    CGContextRef imageContextRef = CGBitmapContextCreate(imageData, imageWidth, imageHeight, 8, imageWidth*4, CGImageGetColorSpace(imageRef), kCGImageAlphaPremultipliedLast);
    CGContextTranslateCTM(imageContextRef, 0, imageHeight);
    CGContextScaleCTM(imageContextRef, 1.0, -1.0);
    CGContextDrawImage(imageContextRef, CGRectMake(0.0, 0.0, (CGFloat)imageWidth, (CGFloat)imageHeight), imageRef);
    CGContextRelease(imageContextRef);
    return  imageData;
}

- (void)dealloc
{
    glDeleteTextures(1, &_textureBuffer);
}

@end

代码解释

  • FrameBuffer类代表离屏渲染buffer
  • TextureUnit 代表纹理单元,也可以是纹理附件
  • RenderBuffer代表渲染附件
  • initSubObject 中完成了FrameBuffer的组装工作(项目启动就创建好了)
  • 该类中有两个shader .默认的shader 属性是 立方体绘制的shader ,而frameShader 是从framebuffer中的纹理附件中读取值,显示屏幕的shader

绘制核心代码解释-(void)glkView:(GLKView *)view drawInRect:(CGRect)rect

  • glGetIntegerv(GL_FRAMEBUFFER_BINDING, &_mDefaultFBO);
    获取默认buffer 的id
  • [self.framebuffer bindOffScreenBuffer];
    切换到离屏渲染buffer
  • glEnable(GL_DEPTH_TEST);
    开启深度测试
  • glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
  • glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    清除渲染buffer的颜色附件和深度附件缓存中的值
  • GLKMatrix4 cubeMode1 =[self _getModeMatrix4Location:GLKVector3Make(0.0f, 0.0f, 1.0f)];
  • cubeMode1 = GLKMatrix4Rotate(cubeMode1, 30*M_PI/180.0,1, 1, 0);
  • [self.shader use];
  • [self _bindViewMatrix4:self.bindObject];
  • [self _bindProjectionMatrix4:self.bindObject];
  • [self _drawFloor:self.bindObject];
  • [self _drawCubeMode:cubeMode1 bindObject:self.bindObject];
    将立方体渲染到离屏buffer的纹理单元中, 即spaceUnit 的纹理中
  • glBindFramebuffer(GL_FRAMEBUFFER, self.mDefaultFBO);
    切换到默认buffer中
  • glDisable(GL_DEPTH_TEST);
  • glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
  • glClear(GL_COLOR_BUFFER_BIT);
  • [self.frameShader use];
    设置默认buffer 中的数据状态 以及使用的shader
  • [self.spaceUnit activeTextureUnit:GL_TEXTURE2];
  • [self.spaceUnit bindtextureUnitLocationAndShaderUniformSamplerLocation:self.frameBufferBinder->uniforms[FB_uniform_Texture]];
    我们将离屏渲染buffer的纹理绑定到 纹理单元2上,并且将纹理单元2 绑定到shader 上
  • [self.quadVertex enableVertexInVertexAttrib:OSR_aPos numberOfCoordinates:2 attribOffset:0];
  • [self.quadVertex enableVertexInVertexAttrib:OSR_aTexCoords numberOfCoordinates:2 attribOffset:sizeof(float)*2];
  • [self.quadVertex drawVertexWithMode:GL_TRIANGLES startVertexIndex:0 numberOfVertices:6];
    将纹理单元2 绘制到屏幕上

逻辑图如下

这样我们利用离屏渲染buffer 将结果渲染成了一张图片,接下来我们可以对图片进行加工了.
一下是对图片基本的加工

效果反向

反向渲染

我们现在能够访问渲染输出的每个颜色,所以在(译注:屏幕的)片段着色器中返回这些颜色的反相(Inversion)并不是很难。我们将会从屏幕纹理中取颜色值,然后用1.0减去它,对它进行反相:
我们只更改shader 即可

precision mediump float;
varying vec2 vTexCoords;
uniform sampler2D uTexture;

const float offset = 1.0 / 300.0;

void main()
{
    vec3  textureColor = texture2D(uTexture, vTexCoords).rgb;
    gl_FragColor = vec4(vec3(1.0)-textureColor,1.0);
}

灰度处理

灰度

另外一个很有趣的效果是,移除场景中除了黑白灰以外所有的颜色,让整个图像灰度化(Grayscale)。很简单的实现方式是,取所有的颜色分量,将它们平均化:

precision mediump float;
varying vec2 vTexCoords;
uniform sampler2D uTexture;

const float offset = 1.0 / 300.0;

void main()
{
    vec3  textureColor = texture2D(uTexture, vTexCoords).rgb;
         float average = (textureColor.r + textureColor.g + textureColor.b) / 3.0;
     gl_FragColor = vec4(average,average,average,1.0);

}

这已经能创造很好的结果了,但人眼会对绿色更加敏感一些,而对蓝色不那么敏感,所以为了获取物理上更精确的效果,我们需要使用加权的(Weighted)通道:

precision mediump float;
varying vec2 vTexCoords;
uniform sampler2D uTexture;

const float offset = 1.0 / 300.0;

void main()
{
    vec3  textureColor = texture2D(uTexture, vTexCoords).rgb;
     float average = 0.2126 * textureColor.r + 0.7152 * textureColor.g + 0.0722 * textureColor.b;
    gl_FragColor = vec4(average,average,average,1.0);
    

核效果

模糊

在一个纹理图像上做后期处理的另外一个好处是,我们可以从纹理的其它地方采样颜色值。比如说我们可以在当前纹理坐标的周围取一小块区域,对当前纹理值周围的多个纹理值进行采样。我们可以结合它们创建出很有意思的效果。

核(Kernel)(或卷积矩阵(Convolution Matrix))是一个类矩阵的数值数组,它的中心为当前的像素,它会用它的核值乘以周围的像素值,并将结果相加变成一个值。所以,基本上我们是在对当前像素周围的纹理坐标添加一个小的偏移量,并根据核将结果合并。下面是核的一个例子:


这个核取了8个周围像素值,将它们乘以2,而把当前的像素乘以-15。这个核的例子将周围的像素乘上了一个权重,并将当前像素乘以一个比较大的负权重来平衡结果。

你在网上找到的大部分核将所有的权重加起来之后都应该会等于1,如果它们加起来不等于1,这就意味着最终的纹理颜色将会比原纹理值更亮或者更暗了。

核是后期处理一个非常有用的工具,它们使用和实验起来都很简单,网上也能找到很多例子。我们需要稍微修改一下片段着色器,让它能够支持核。我们假设使用的核都是3x3核(实际上大部分核都是):

precision mediump float;
varying vec2 vTexCoords;
uniform sampler2D uTexture;

const float offset = 1.0 / 300.0;

void main()
{
    vec3  textureColor = texture2D(uTexture, vTexCoords).rgb;
    vec2 offsets[9];
    offsets[0] = vec2(-offset,  offset); // 左上
    offsets[1] = vec2( 0.0,    offset); // 正上
    offsets[2] = vec2( offset,  offset); // 右上
    offsets[3] = vec2(-offset,  0.0);   // 左
    offsets[4] = vec2( 0.0,    0.0);  // 中
    offsets[5] = vec2( offset,  0.0);   // 右
    offsets[6] = vec2(-offset, -offset);// 左下
    offsets[7] = vec2( 0.0,   -offset); // 正下
    offsets[8] = vec2( offset, -offset) ; // 右下
    
    
    float kernel[9];
    for(int i = 0; i < 9; I++)
    {
        kernel[i]=-1.0;
    }
    kernel[4]=9.0;
    
    vec3 sampleTex[9];
    for(int i = 0; i < 9; I++)
    {
        sampleTex[i] = vec3(texture2D(uTexture, vTexCoords.st + offsets[i]));
    }
    vec3 col = vec3(0.0);
    for(int i = 0; i < 9; I++)
        col += sampleTex[i] * kernel[I];
    
    gl_FragColor = vec4(col, 1.0);
}

在片段着色器中,我们首先为周围的纹理坐标创建了一个9个vec2偏移量的数组。偏移量是一个常量,你可以按照你的喜好自定义它。之后我们定义一个核,在这个例子中是一个锐化(Sharpen)核,它会采样周围的所有像素,锐化每个颜色值。最后,在采样时我们将每个偏移量加到当前纹理坐标上,获取需要采样的纹理,之后将这些纹理值乘以加权的核值,并将它们加到一起。

这个锐化核看起来是这样的:


核效果
模糊

创建模糊(Blur)效果的核是这样的:

由于所有值的和是16,所以直接返回合并的采样颜色将产生非常亮的颜色,所以我们需要将核的每个值都除以16。最终的核数组将会是:

float kernel[9] = float[](
    1.0 / 16, 2.0 / 16, 1.0 / 16,
    2.0 / 16, 4.0 / 16, 2.0 / 16,
    1.0 / 16, 2.0 / 16, 1.0 / 16  
);

shader 代码

precision mediump float;
varying vec2 vTexCoords;
uniform sampler2D uTexture;

const float offset = 1.0 / 300.0;

void main()
{
    vec3  textureColor = texture2D(uTexture, vTexCoords).rgb;
    vec2 offsets[9];
    offsets[0] = vec2(-offset,  offset); // 左上
    offsets[1] = vec2( 0.0,    offset); // 正上
    offsets[2] = vec2( offset,  offset); // 右上
    offsets[3] = vec2(-offset,  0.0);   // 左
    offsets[4] = vec2( 0.0,    0.0);  // 中
    offsets[5] = vec2( offset,  0.0);   // 右
    offsets[6] = vec2(-offset, -offset);// 左下
    offsets[7] = vec2( 0.0,   -offset); // 正下
    offsets[8] = vec2( offset, -offset) ; // 右下
    
    
    float kernel[9];
    kernel[0]=1.0;
    kernel[1]=2.0;
    kernel[2]=1.0;
    kernel[3]=2.0;
    kernel[4]=4.0;
    kernel[5]=2.0;
    kernel[6]=1.0;
    kernel[7]=2.0;
    kernel[8]=1.0;
    for(int i = 0; i < 9; I++)
    {
        kernel[i]=kernel[i]/16.0;
    }
    vec3 sampleTex[9];
    for(int i = 0; i < 9; I++)
    {
        sampleTex[i] = vec3(texture2D(uTexture, vTexCoords.st + offsets[i]));
    }
    vec3 col = vec3(0.0);
    for(int i = 0; i < 9; I++)
        col += sampleTex[i] * kernel[I];
    
    gl_FragColor = vec4(col, 1.0);
      
}

通过在片段着色器中改变核的float数组,我们完全改变了后期处理效果。它现在看起来是这样子的:


模糊

这样的模糊效果创造了很多的可能性。我们可以随着时间修改模糊的量,创造出玩家醉酒时的效果,或者在主角没带眼镜的时候增加模糊。模糊也能够让我们来平滑颜色值,我们将在之后教程中使用到。

你可以看到,只要我们有了这个核的实现,创建炫酷的后期处理特效是非常容易的事。我们再来看最后一个很流行的效果来结束本节的讨论。

边缘检测

下面的边缘检测(Edge-detection)核和锐化核非常相似:


image.png

这个核高亮了所有的边缘,而暗化了其它部分,在我们只关心图像的边角的时候是非常有用的。


注意,核在对屏幕纹理的边缘进行采样的时候,由于还会对中心像素周围的8个像素进行采样,其实会取到纹理之外的像素。由于环绕方式默认是GL_REPEAT,所以在没有设置的情况下取到的是屏幕另一边的像素,而另一边的像素本不应该对中心像素产生影响,这就可能会在屏幕边缘产生很奇怪的条纹。为了消除这一问题,我们可以将屏幕纹理的环绕方式都设置为GL_CLAMP_TO_EDGE。这样子在取到纹理外的像素时,就能够重复边缘的像素来更精确地估计最终的值了。


OpenGLZeroStudyDemo(13)-高级Opengl-帧缓冲
参考博客
api查询学习
frameBuffer

你可能感兴趣的:(重新自学学习openGL 之性能优化帧缓冲)