【OpenGL】学习笔记#5

一、帧缓冲

什么是帧缓冲?可以理解为GPU在渲染前预先准备的一个区域,之后将把它渲染成屏幕上的像素。但是,帧缓冲本身并不储存数据,仅仅储存指向数据的指针。所以,帧缓冲需要绑定几个缓冲区,我们特殊地称它们为附件:颜色附件,深度缓冲附件,模板缓冲附件。需要注意的是,一个完整的帧缓冲必须包括一个颜色附件。

除了这种分类之外,附件还可以分为纹理附件和渲染缓冲对象(RBO,Render Buffer Object)。其中,纹理附件就是颜色附件,RBO分为深度缓冲附件和模板缓冲附件。让我们来列一个树形图。

帧缓冲--[纹理附件]--颜色附件

     |

     [渲染缓冲附件]--深度缓冲附件

           |

           --模板缓冲附件

为什么要讲到帧缓冲呢?因为帧缓冲是后期处理相当重要的部分。例如阴影,模糊,反相等后期处理都要依靠帧缓冲来实现。打个比方,帧缓冲就好像是拍出来的一张照片,可以让我们PS。

此处的PS便是着色器。OpenGL提供了可以让我们编辑的帧缓冲,好伟大!我们可以把一张帧缓冲保存的2D纹理或深度缓冲获得为一个句柄,在后面直接使用,塞进着色器里。

说了这么多,来看看代码吧,让我们以用着色器编辑一个帧缓冲为例子:

首先生成并绑定一个帧缓冲:

GLuint FramebufferName = 0;
    glGenFramebuffers(1, &FramebufferName);
    glBindFramebuffer(GL_FRAMEBUFFER, FramebufferName);

生成一个纹理:

GLuint renderedTexture;
    glGenTextures(1, &renderedTexture);

状态机不解释:

glBindTexture(GL_TEXTURE_2D, renderedTexture);

由于帧缓冲必须包含一个颜色附件,所以接下来对之前绑定的纹理填充一个空的图像(最后一个参数0代表空,empty):

glTexImage2D(GL_TEXTURE_2D, 0,GL_RGB, windowWidth, windowHeight, 0,GL_RGB, GL_UNSIGNED_BYTE, 0);

可  怜  的  过  滤(性能upup),设置过滤模式:

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_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);

由于我们要渲染一个3D模型,所以要进行深度测试,所以来生成并绑定一个渲染缓冲区,将要作为我们的RBO:

GLuint depthrenderbuffer;
    glGenRenderbuffers(1, &depthrenderbuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, depthrenderbuffer);

由于OpenGL只知道这是个渲染缓冲区,对其中的数据格式和大小全然不知,所以我们得告诉它,使用glRenderbufferStorage来指定RBO的数据格式和大小,此处指定为深度缓冲区:

glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, windowWidth, windowHeight);

对于此函数,第一个参数必须填GL_RENDERBUFFER,第二个指定了类型为深度缓冲,还可以是GL_RGB,GL_RGBA,剩下的两个参数指定了数据长宽,这里设置为屏幕大小。

接下来开始为帧缓冲绑定附件,将此深度缓冲绑定为深度附件:

glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthrenderbuffer);

第二个参数指定了类型为深度附件。

然后绑定纹理附件,这个比较特殊,先看代码:

glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, renderedTexture, 0);

可以看到,第二个参数并不是指定的类型,而是GL_COLOR_ATTACHMENT0,这意味着什么呢,这意味着颜色附件可以有多个,0代表的是颜色附件的位置。同时我们也发现RBO只能有一个,因为它直接指定了类型。

所以,我们就需要指定接下来渲染时需要渲染到哪些颜色附件上,这次我们只渲染到GL_COLOR_ATTACHMENT0上:

GLenum DrawBuffers[1] = {GL_COLOR_ATTACHMENT0};
    glDrawBuffers(1, DrawBuffers);

不需要多说了吧。

终于,帧缓冲的前置操作完成了。这时需要检查你的帧缓冲是否完整,否则会有很可怕的错误:

if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
        return false;

经常check是好习惯呦~

由于我们要把帧缓冲的图像经过处理绘制到屏幕上,所以一会渲染的时候将要分两个步骤:第一步,按照往常的操作渲染,但是在渲染前把渲染目标绑定到我们自定义的帧缓冲上,此为离屏渲染。然后,通过颜色附件获得渲染得到的深度缓冲和纹理,再把它们传到着色器里处理,作为2D纹理渲染到一个屏幕大小的四边形上。

那么,下面开始定义四边形:

static const GLfloat g_quad_vertex_buffer_data[] = { 
        -1.0f, -1.0f, 0.0f,
         1.0f, -1.0f, 0.0f,
        -1.0f,  1.0f, 0.0f,
        -1.0f,  1.0f, 0.0f,
         1.0f, -1.0f, 0.0f,
         1.0f,  1.0f, 0.0f,
    };

    GLuint quad_vertexbuffer;
    glGenBuffers(1, &quad_vertexbuffer);
    glBindBuffer(GL_ARRAY_BUFFER, quad_vertexbuffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(g_quad_vertex_buffer_data), g_quad_vertex_buffer_data, GL_STATIC_DRAW);

定义顶点缓冲,里面包括的是一个2D充满屏幕的四边形的顶点,注意此时顶点依然是3维的格式,只不过Z=0,相当于2D。

好了,一切都准备好了,下面进入主循环:

首先把渲染目标绑定到我们定义的帧缓冲里:

glBindFramebuffer(GL_FRAMEBUFFER, FramebufferName);

这里是渲染操作...

glUseProgram(programID);

(这里使用通常的着色器)

这里是渲染操作...

然后重头戏来了,开始渲染四边形,先把渲染目标绑定到默认帧缓冲:这个帧缓冲将会直接被GPU提取渲染:

glBindFramebuffer(GL_FRAMEBUFFER, 0);

这里是渲染操作...

glUseProgram(quad_programID);

(使用第二个着色器,这个着色器只负责渲染一个2D纹理并稍稍处理)

这里是渲染操作...

怎么样,这样就完成了呢,小朋友们学会了吗?(狗头)

咳咳,说了这么多,第二个着色器长啥样呢,仅仅渲染一个2D纹理的着色器,来看吧:

#version 330 core

// Input vertex data, different for all executions of this shader.
layout(location = 0) in vec3 vertexPosition_modelspace;

// Output data ; will be interpolated for each fragment.
out vec2 UV;

void main(){
    gl_Position =  vec4(vertexPosition_modelspace,1);
    UV = (vertexPosition_modelspace.xy+vec2(1,1))/2.0;
}

出人意料的简单呢......仅仅传出一个UV坐标。不过,这里要注意,我们的顶点坐标xy的范围是[-1,1],而UV坐标uv的范围是[0,1],所以需要(xy+(1,1))/2来转换。

看片元着色器:

#version 330 core

in vec2 UV;

out vec3 color;

uniform sampler2D renderedTexture;
uniform float time;

void main(){
    color = texture( renderedTexture, UV + 0.005*vec2( sin(time+1024.0*UV.x),cos(time+768.0*UV.y)) ).xyz ;
}

用时间来做一个偏移量,不多说了吧。

那么看看结果吧:

像素会随着时间的变化慢慢移动喔!

原文地址:https://www.cnblogs.com/dudujerry/p/13573649.html