,我们使用了几种不同类型的屏幕缓冲:用于写入颜色值的颜色缓冲,用于写入
深度信息的深度缓冲,以及允许我们基于一些条件丢弃指定片段的模板缓冲。把这几种缓冲
结合起来叫做帧缓冲(Framebuffer),它被储存于内存中。OpenGL给了我们自己定义帧缓冲的
自由,我们可以选择性的定义自己的颜色缓冲、深度和模板缓冲。
我们目前所做的渲染操作都是是在默认的帧缓冲之上进行的。当你创建了你的窗口的时候默
认帧缓冲就被创建和配置好了(GLFW为我们做了这件事)。通过创建我们自己的帧缓冲我们
能够获得一种额外的渲染方式。
你也许不能立刻理解应用程序的帧缓冲的含义,通过帧缓冲可以将你的场景渲染到一个不同
的帧缓冲中,可以使我们能够在场景中创建镜子这样的效果,或者做出一些炫酷的特效。首
先我们会讨论它们是如何工作的,然后我们将利用帧缓冲来实现一些炫酷的效果。

创建一个帧缓冲
就像OpenGL中其他对象一样,我们可以使用一个叫做 glGenFramebuffers 的函数来创建一个
帧缓冲对象(简称FBO):
GLuint fbo;
glGenFramebuffers(1, &fbo);
这种对象的创建和使用的方式我们已经见过不少了,因此它们的使用方式也和之前我们见过
的其他对象的使用方式相似。首先我们要创建一个帧缓冲对象,把它绑定到当前帧缓冲,做
一些操作,然后解绑帧缓冲。我们使用 glBindFramebuffer 来绑定帧缓冲:
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
绑定到 GL_FRAMEBUFFER 目标后,接下来所有的读、写帧缓冲的操作都会影响到当前绑定的帧
缓冲。也可以把帧缓冲分开绑定到读或写目标上,分别使
用 GL_READ_FRAMEBUFFER 或 GL_DRAW_FRAMEBUFFER 来做这件事。如果绑定到
帧缓冲
230
了 GL_READ_FRAMEBUFFER ,就能执行所有读取操作,像 glReadPixels 这样的函数使用了;绑定
到 GL_DRAW_FRAMEBUFFER 上,就允许进行渲染、清空和其他的写入操作。大多数时候你不必分
开用,通常把两个都绑定到 GL_FRAMEBUFFER 上就行。
很遗憾,现在我们还不能使用自己的帧缓冲,因为还没做完呢。建构一个完整的帧缓冲必须
满足以下条件:
我们必须往里面加入至少一个附件(颜色、深度、模板缓冲)。
其中至少有一个是颜色附件。
所有的附件都应该是已经完全做好的(已经存储在内存之中)。
每个缓冲都应该有同样数目的样本。
如果你不知道什么是样本也不用担心,我们会在后面的教程中讲到。
从上面的需求中你可以看到,我们需要为帧缓冲创建一些附件,还需要把这些附件附加到帧
缓冲上。当我们做完所有上面提到的条件的时候我们就可以用 glCheckFramebufferStatus 带
上 GL_FRAMEBUFFER 这个参数来检查是否真的成功做到了。然后检查当前绑定的帧缓冲,返回
了这些规范中的哪个值。如果返回的是 GL_FRAMEBUFFER_COMPLETE 就对了:
if(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE)
// Execute victory dance
后续所有渲染操作将渲染到当前绑定的帧缓冲的附加缓冲中,由于我们的帧缓冲不是默认的
帧缓冲,渲染命令对窗口的视频输出不会产生任何影响。出于这个原因,它被称为离屏渲染
(off-screen rendering),就是渲染到一个另外的缓冲中。为了让所有的渲染操作对主窗口产
生影响我们必须通过绑定为0来使默认帧缓冲被激活:
glBindFramebuffer(GL_FRAMEBUFFER, 0);
当我们做完所有帧缓冲操作,不要忘记删除帧缓冲对象:
glDeleteFramebuffers(1, &fbo);
现在在执行完成检测前,我们需要把一个或更多的附件附加到帧缓冲上。一个附件就是一个
内存地址,这个内存地址里面包含一个为帧缓冲准备的缓冲,它可以是个图像。当创建一个
附件的时候我们有两种方式可以采用:纹理或渲染缓冲(renderbuffer)对象。
纹理附件
帧缓冲
231
当把一个纹理附加到帧缓冲上的时候,所有渲染命令会写入到纹理上,就像它是一个普通的
颜色/深度或者模板缓冲一样。使用纹理的好处是,所有渲染操作的结果都会被储存为一个纹
理图像,这样我们就可以简单的在着色器中使用了。
创建一个帧缓冲的纹理和创建普通纹理差不多:
GLuint 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);
这里主要的区别是我们把纹理的维度设置为屏幕大小(尽管不是必须的),我们还传递NULL
作为纹理的data参数。对于这个纹理,我们只分配内存,而不去填充它。纹理填充会在渲染
到帧缓冲的时候去做。同样,要注意,我们不用关心环绕方式或者Mipmap,因为在大多数时
候都不会需要它们的。
如果你打算把整个屏幕渲染到一个或大或小的纹理上,你需要用新的纹理的尺寸作为参数再
次调用 glViewport (要在渲染到你的帧缓冲之前做好),否则只有一小部分纹理或屏幕能够
绘制到纹理上。
现在我们已经创建了一个纹理,最后一件要做的事情是把它附加到帧缓冲上:
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D, texture, 0)
;
glFramebufferTexture2D 函数需要传入下列参数:
target:我们所创建的帧缓冲类型的目标(绘制、读取或两者都有)。
attachment:我们所附加的附件的类型。现在我们附加的是一个颜色附件。需要注意,
最后的那个0是暗示我们可以附加1个以上颜色的附件。我们会在后面的教程中谈到。
textarget:你希望附加的纹理类型。
texture:附加的实际纹理。
level:Mipmap level。我们设置为0。
除颜色附件以外,我们还可以附加一个深度和一个模板纹理到帧缓冲对象上。为了附加一个
深度缓冲,我们可以知道那个 GL_DEPTH_ATTACHMENT 作为附件类型。记住,这时纹理格式和内
部格式类型(internalformat)就成了 GL_DEPTH_COMPONENT 去反应深度缓冲的存储格式。附加
一个模板缓冲,你要使用 GL_STENCIL_ATTACHMENT 作为第二个参数,把纹理格式指定为
GL_STENCIL_INDEX 。
帧缓冲
232
也可以同时附加一个深度缓冲和一个模板缓冲为一个单独的纹理。这样纹理的每32位数值就
包含了24位的深度信息和8位的模板信息。为了把一个深度和模板缓冲附加到一个单独纹理
上,我们使用 GL_DEPTH_STENCIL_ATTACHMENT 类型配置纹理格式以包含深度值和模板值的结合
物。下面是一个附加了深度和模板缓冲为单一纹理的例子:
glTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, 800, 600, 0, GL_DEPTH_STENCIL, GL
_UNSIGNED_INT_24_8, NULL );
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, tex
ture, 0);
渲染缓冲对象附件(Renderbuffer object attachments)
在介绍了帧缓冲的可行附件类型——纹理后,OpenGL引进了渲染缓冲对象(Renderbuffer
objects),所以在过去那些美好时光里纹理是附件的唯一可用的类型。和纹理图像一样,渲
染缓冲对象也是一个缓冲,它可以是一堆字节、整数、像素或者其他东西。渲染缓冲对象的
一大优点是,它以OpenGL原生渲染格式储存它的数据,因此在离屏渲染到帧缓冲的时候,这
些数据就相当于被优化过的了。
渲染缓冲对象将所有渲染数据直接储存到它们的缓冲里,而不会进行针对特定纹理格式的任
何转换,这样它们就成了一种快速可写的存储介质了。然而,渲染缓冲对象通常是只写的,
不能修改它们(就像获取纹理,不能写入纹理一样)。可以用 glReadPixels 函数去读取,函
数返回一个当前绑定的帧缓冲的特定像素区域,而不是直接返回附件本身。
因为它们的数据已经是原生格式了,在写入或把它们的数据简单地到其他缓冲的时候非常
快。当使用渲染缓冲对象时,像切换缓冲这种操作变得异常高速。我们在每个渲染迭代末尾
使用的那个 glfwSwapBuffers 函数,同样以渲染缓冲对象实现:我们简单地写入到一个渲染缓
冲图像,最后交换到另一个里。渲染缓冲对象对于这种操作来说很完美。
创建一个渲染缓冲对象和创建帧缓冲代码差不多:
GLuint rbo;
glGenRenderbuffers(1, &rbo);
相似地,我们打算把渲染缓冲对象绑定,这样所有后续渲染缓冲操作都会影响到当前的渲染
缓冲对象:
glBindRenderbuffer(GL_RENDERBUFFER, rbo);
由于渲染缓冲对象通常是只写的,它们经常作为深度和模板附件来使用,由于大多数时候,
我们不需要从深度和模板缓冲中读取数据,但仍关心深度和模板测试。我们就需要有深度和
模板值提供给测试,但不需要对这些值进行采样(sample),所以深度缓冲对象是完全符合
帧缓冲
233
的。当我们不去从这些缓冲中采样的时候,渲染缓冲对象通常很合适,因为它们等于是被优
化过的。
调用 glRenderbufferStorage 函数可以创建一个深度和模板渲染缓冲对象:
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 800, 600);
创建一个渲染缓冲对象与创建纹理对象相似,不同之处在于这个对象是专门被设计用于图像
的,而不是通用目的的数据缓冲,比如纹理。这里我们选择 GL_DEPTH24_STENCIL8 作为内部格
式,它同时代表24位的深度和8位的模板缓冲。
最后一件还要做的事情是把帧缓冲对象附加上:
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER
, rbo);
在帧缓冲项目中,渲染缓冲对象可以提供一些优化,但更重要的是知道何时使用渲染缓冲对
象,何时使用纹理。通常的规则是,如果你永远都不需要从特定的缓冲中进行采样,渲染缓
冲对象对特定缓冲是更明智的选择。如果哪天需要从比如颜色或深度值这样的特定缓冲采样
数据的话,你最好还是使用纹理附件。从执行效率角度考虑,它不会对效率有太大影响。
渲染到纹理
现在我们知道了(一些)帧缓冲如何工作的,是时候把它们用起来了。我们会把场景渲染到
一个颜色纹理上,这个纹理附加到一个我们创建的帧缓冲上,然后把纹理绘制到一个简单的
四边形上,这个四边形铺满整个屏幕。输出的图像看似和没用帧缓冲一样,但是这次,它其
实是直接打印到了一个单独的四边形上面。为什么这很有用呢?下一部分我们会看到原因。
第一件要做的事情是创建一个帧缓冲对象,并绑定它,这比较明了:
GLuint framebuffer;
glGenFramebuffers(1, &framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
下一步我们创建一个纹理图像,这是我们将要附加到帧缓冲的颜色附件。我们把纹理的尺寸
设置为窗口的宽度和高度,并保持数据未初始化:
帧缓冲
234
// Generate texture
GLuint 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);
// Attach it to currently bound framebuffer object
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texColorBu
ffer, 0);
我们同样打算要让OpenGL确定可以进行深度测试(模板测试,如果你用的话)所以我们必须
还要确保向帧缓冲中添加一个深度(和模板)附件。由于我们只采样颜色缓冲,并不采样其
他缓冲,我们可以创建一个渲染缓冲对象来达到这个目的。记住,当你不打算从指定缓冲采
样的的时候,它们是一个不错的选择。
创建一个渲染缓冲对象不太难。唯一一件要记住的事情是,我们正在创建的是一个渲染缓冲
对象的深度和模板附件。我们把它的内部给事设置为 GL_DEPTH24_STENCIL8 ,对于我们的目的
来说这个精确度已经足够了。
GLuint rbo;
glGenRenderbuffers(1, &rbo);
glBindRenderbuffer(GL_RENDERBUFFER, rbo);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 800, 600);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
我们为渲染缓冲对象分配了足够的内存空间以后,我们可以解绑渲染缓冲。
接着,在做好帧缓冲之前,还有最后一步,我们把渲染缓冲对象附加到帧缓冲的深度和模板
附件上:
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER
, rbo);
然后我们要检查帧缓冲是否真的做好了,如果没有,我们就打印一个错误消息。
if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
cout << "ERROR::FRAMEBUFFER:: Framebuffer is not complete!" << endl;
glBindFramebuffer(GL_FRAMEBUFFER, 0);
还要保证解绑帧缓冲,这样我们才不会意外渲染到错误的帧缓冲上。
帧缓冲
235
现在帧缓冲做好了,我们要做的全部就是渲染到帧缓冲上,而不是绑定到帧缓冲对象的默认
缓冲。余下所有命令会影响到当前绑定的帧缓冲上。所有深度和模板操作同样会从当前绑定
的帧缓冲的深度和模板附件中读取,当然,得是在它们可用的情况下。如果你遗漏了比如深
度缓冲,所有深度测试就不会工作,因为当前绑定的帧缓冲里没有深度缓冲。
所以,为把场景绘制到一个单独的纹理,我们必须以下面步骤来做:
1. 使用新的绑定为激活帧缓冲的帧缓冲,像往常那样渲染场景。
2. 绑定到默认帧缓冲。
3. 绘制一个四边形,让它平铺到整个屏幕上,用新的帧缓冲的颜色缓冲作为他的纹理。
我们使用在深度测试教程中同一个场景进行绘制,但是这次使用老气横秋的箱子纹理。
为了绘制四边形我们将会创建新的着色器。我们不打算引入任何花哨的变换矩阵,因为我们
只提供已经是标准化设备坐标的顶点坐标,所以我们可以直接把它们作为顶点着色器的输
出。顶点着色器看起来像这样:

#version 330 core
layout (location = 0) in vec2 position;
layout (location = 1) in vec2 texCoords;
out vec2 TexCoords;
void main()
{
gl_Position = vec4(position.x, position.y, 0.0f, 1.0f);
TexCoords = texCoords;
}


没有花哨的地方。片段着色器更简洁,因为我们做的唯一一件事是从纹理采样:

#version 330 core
in vec2 TexCoords;
out vec4 color;
uniform sampler2D screenTexture;
void main()
{
color = texture(screenTexture, TexCoords);
}


接着需要你为屏幕上的四边形创建和配置一个VAO。渲染迭代中帧缓冲处理会有下面的结
构:
帧缓冲
236

// First pass
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // We're not using stencil buffer
now
glEnable(GL_DEPTH_TEST);
DrawScene();
// Second pass
glBindFramebuffer(GL_FRAMEBUFFER, 0); // back to default
glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
screenShader.Use();
glBindVertexArray(quadVAO);
glDisable(GL_DEPTH_TEST);
glBindTexture(GL_TEXTURE_2D, textureColorbuffer);
glDrawArrays(GL_TRIANGLES, 0, 6);
glBindVertexArray(0);


只有很少的事情要说明。第一,由于我们用的每个帧缓冲都有自己的一系列缓冲,我们打算
使用 glClear 设置的合适的位(bits)来清空这些缓冲。第二,当渲染四边形的时候,我们关
闭深度测试,因为我们不关系深度测试,我们绘制的是一个简单的四边形;当我们绘制普通
场景时我们必须再次开启深度测试。
这里的确有很多地方会做错,所以如果你没有获得任何输出,尝试排查任何可能出现错误的
地方,再次阅读教程中相关章节。如果每件事都做对了就一定能成功,你将会得到这样的输
出:

直接上代码:OpenGL绘制2次场景,:一次正常绘制,另一 次摄像机旋转180度后绘制.尝试在你的显示器顶端创建一个小四边形,在上面应用后视镜 的镜面纹理:

#include "stdafx.h"
#include "glew/glew.h"
#include "glfw/glfw3.h"
#include "SOIL.h"
#include "camera.h"
#include "filesystem.h"
#include "mesh.h"
#include "model.h"
#include "root_directory.h"
#include "shader_m.h"
//#include "stb_image.h"
#include "glm/glm.hpp"
#include "glm/gtc/matrix_transform.hpp"
#include "glm/gtc/type_ptr.hpp"

#include <iostream>

// Properties
GLuint screenWidth = 1366, screenHeight = 768;

// Function prototypes
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
void Do_Movement();
GLuint loadTexture(GLchar* path, GLboolean alpha = false);
GLuint generateAttachmentTexture(GLboolean depth, GLboolean stencil);

// Camera
Camera camera(glm::vec3(0.0f, 0.0f, 3.0f));
bool keys[1024];
GLfloat lastX = screenWidth/2.0, lastY = screenHeight/2.0;
bool firstMouse = true;

GLfloat deltaTime = 0.0f;
GLfloat lastFrame = 0.0f;

// The MAIN function, from here we start our application and run our Game loop
int main()
{
// Init GLFW
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);

GLFWwindow* window = glfwCreateWindow(screenWidth, screenHeight, "LearnOpenGL", nullptr, nullptr); // Windowed
glfwMakeContextCurrent(window);

// Set the required callback functions
glfwSetKeyCallback(window, key_callback);
glfwSetCursorPosCallback(window, mouse_callback);
glfwSetScrollCallback(window, scroll_callback);

// Options
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);

// Initialize GLEW to setup the OpenGL Function pointers
glewExperimental = GL_TRUE;
glewInit();

// Define the viewport dimensions
glViewport(0, 0, screenWidth, screenHeight);

// Setup some OpenGL options
glDepthFunc(GL_LESS);

// Setup and compile our shaders
Shader shader("framebuffers.vs", "framebuffers.fs");
Shader screenShader("framebuffers_screen.vs", "framebuffers_screen.fs");

//#pragma region "object_initialization"
// Set the object data (buffers, vertex attributes)
GLfloat cubeVertices[] = {
// Positions // Texture Coords
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f,

-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,

-0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 1.0f, 0.0f,

0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,

-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 1.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,

-0.5f, 0.5f, -0.5f, 0.0f, 1.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f
};
GLfloat floorVertices[] = {
// Positions // Texture Coords (note we set these higher than 1 that together with GL_REPEAT as texture wrapping mode 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
};
GLfloat 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
};

// Setup cube VAO
GLuint cubeVAO, cubeVBO;
glGenVertexArrays(1, &cubeVAO);
glGenBuffers(1, &cubeVBO);
glBindVertexArray(cubeVAO);
glBindBuffer(GL_ARRAY_BUFFER, cubeVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(cubeVertices), &cubeVertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
glBindVertexArray(0);
// Setup plane VAO
GLuint floorVAO, floorVBO;
glGenVertexArrays(1, &floorVAO);
glGenBuffers(1, &floorVBO);
glBindVertexArray(floorVAO);
glBindBuffer(GL_ARRAY_BUFFER, floorVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(floorVertices), &floorVertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
glBindVertexArray(0);
// Setup screen VAO
GLuint quadVAO, quadVBO;
glGenVertexArrays(1, &quadVAO);
glGenBuffers(1, &quadVBO);
glBindVertexArray(quadVAO);
glBindBuffer(GL_ARRAY_BUFFER, quadVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), &quadVertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), (GLvoid*)(2 * sizeof(GLfloat)));
glBindVertexArray(0);

// Load textures
GLuint cubeTexture = loadTexture("resources/textures/container.jpg");
GLuint floorTexture = loadTexture("resources/textures/metal.png");
//#pragma endregion

// Framebuffers
GLuint framebuffer;
glGenFramebuffers(1, &framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
// Create a color attachment texture
GLuint textureColorbuffer = generateAttachmentTexture(false, false);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureColorbuffer, 0);
// Create a renderbuffer object for depth and stencil attachment (we won't be sampling these)
GLuint rbo;
glGenRenderbuffers(1, &rbo);
glBindRenderbuffer(GL_RENDERBUFFER, rbo);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, screenWidth, screenHeight); // Use a single renderbuffer object for both a depth AND stencil buffer.
glBindRenderbuffer(GL_RENDERBUFFER, 0);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo); // Now actually attach it
// Now that we actually created the framebuffer and added all attachments we want to check if it is actually complete now
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
cout << "ERROR::FRAMEBUFFER:: Framebuffer is not complete!" << endl;
glBindFramebuffer(GL_FRAMEBUFFER, 0);


// Draw as wireframe
//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

// Game loop
while (!glfwWindowShouldClose(window))
{
// Set frame time
GLfloat currentFrame = glfwGetTime();
deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame;

// Check and call events
glfwPollEvents();
Do_Movement();


/
// Bind to framebuffer and draw to color texture
// as we normally would.
// //
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
// Clear all attached buffers
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // We're not using stencil buffer so why bother with clearing?

glEnable(GL_DEPTH_TEST);
// Set uniforms
shader.use();
glm::mat4 model;
glm::mat4 view = camera.GetViewMatrix();
glm::mat4 projection = glm::perspective(camera.Zoom, (float)screenWidth / (float)screenHeight, 0.1f, 100.0f);
glUniformMatrix4fv(glGetUniformLocation(shader.ID, "view"), 1, GL_FALSE, glm::value_ptr(view));
glUniformMatrix4fv(glGetUniformLocation(shader.ID, "projection"), 1, GL_FALSE, glm::value_ptr(projection));

// Floor
glBindVertexArray(floorVAO);
glBindTexture(GL_TEXTURE_2D, floorTexture);
model = glm::mat4();
glUniformMatrix4fv(glGetUniformLocation(shader.ID, "model"), 1, GL_FALSE, glm::value_ptr(model));
glDrawArrays(GL_TRIANGLES, 0, 6);
glBindVertexArray(0);
// Cubes
glBindVertexArray(cubeVAO);
glBindTexture(GL_TEXTURE_2D, cubeTexture);
model = glm::translate(model, glm::vec3(-1.0f, 0.0f, -1.0f));
glUniformMatrix4fv(glGetUniformLocation(shader.ID, "model"), 1, GL_FALSE, glm::value_ptr(model));
glDrawArrays(GL_TRIANGLES, 0, 36);
model = glm::mat4();
model = glm::translate(model, glm::vec3(2.0f, 0.0f, 0.0f));
glUniformMatrix4fv(glGetUniformLocation(shader.ID, "model"), 1, GL_FALSE, glm::value_ptr(model));
glDrawArrays(GL_TRIANGLES, 0, 36);
glBindVertexArray(0);

/
// Bind to default framebuffer again and draw the
// quad plane with attched screen texture.
// //
glBindFramebuffer(GL_FRAMEBUFFER, 0);
// Clear all relevant buffers
glClearColor(1.0f, 1.0f, 1.0f, 1.0f); // Set clear color to white (not really necessery actually, since we won't be able to see behind the quad anyways)
glClear(GL_COLOR_BUFFER_BIT);
glDisable(GL_DEPTH_TEST); // We don't care about depth information when rendering a single quad

// Draw Screen
screenShader.use();
glBindVertexArray(quadVAO);
glBindTexture(GL_TEXTURE_2D, textureColorbuffer); // Use the color attachment texture as the texture of the quad plane
glDrawArrays(GL_TRIANGLES, 0, 6);
glBindVertexArray(0);


// Swap the buffers
glfwSwapBuffers(window);
}

// Clean up
glDeleteFramebuffers(1, &framebuffer);

glfwTerminate();
return 0;
}

// This function loads a texture from file. Note: texture loading functions like these are usually
// managed by a 'Resource Manager' that manages all resources (like textures, models, audio).
// For learning purposes we'll just define it as a utility function.
GLuint loadTexture(GLchar* path, GLboolean alpha)
{
//Generate texture ID and load texture data
GLuint textureID;
glGenTextures(1, &textureID);
int width, height;
unsigned char* image = SOIL_load_image(path, &width, &height, 0, alpha ? SOIL_LOAD_RGBA : SOIL_LOAD_RGB);
// Assign texture to ID
glBindTexture(GL_TEXTURE_2D, textureID);
glTexImage2D(GL_TEXTURE_2D, 0, alpha ? GL_RGBA : GL_RGB, width, height, 0, alpha ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE, image);
glGenerateMipmap(GL_TEXTURE_2D);

// Parameters
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, alpha ? GL_CLAMP_TO_EDGE : GL_REPEAT); // Use GL_MIRRORED_REPEAT to prevent white borders. Due to interpolation it takes value from next repeat
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, alpha ? GL_CLAMP_TO_EDGE : GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, 0);
SOIL_free_image_data(image);
return textureID;
}

// Generates a texture that is suited for attachments to a framebuffer
GLuint generateAttachmentTexture(GLboolean depth, GLboolean stencil)
{
// What enum to use?
GLenum attachment_type;
if (!depth && !stencil)
attachment_type = GL_RGB;
else if (depth && !stencil)
attachment_type = GL_DEPTH_COMPONENT;
else if (!depth && stencil)
attachment_type = GL_STENCIL_INDEX;

//Generate texture ID and load texture data
GLuint textureID;
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_2D, textureID);
if (!depth && !stencil)
glTexImage2D(GL_TEXTURE_2D, 0, attachment_type, screenWidth, screenHeight, 0, attachment_type, GL_UNSIGNED_BYTE, NULL);
else // Using both a stencil and depth test, needs special format arguments
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, screenWidth, screenHeight, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, 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);

return textureID;
}

//#pragma region "User input"

// Moves/alters the camera positions based on user input
void Do_Movement()
{
// Camera controls
if (keys[GLFW_KEY_W])
camera.ProcessKeyboard(FORWARD, deltaTime);
if (keys[GLFW_KEY_S])
camera.ProcessKeyboard(BACKWARD, deltaTime);
if (keys[GLFW_KEY_A])
camera.ProcessKeyboard(LEFT, deltaTime);
if (keys[GLFW_KEY_D])
camera.ProcessKeyboard(RIGHT, deltaTime);
}

// Is called whenever a key is pressed/released via GLFW
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
glfwSetWindowShouldClose(window, GL_TRUE);

if (action == GLFW_PRESS)
keys[key] = true;
else if (action == GLFW_RELEASE)
keys[key] = false;
}

void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{
if (firstMouse)
{
lastX = xpos;
lastY = ypos;
firstMouse = false;
}

GLfloat xoffset = xpos - lastX;
GLfloat yoffset = lastY - ypos;

lastX = xpos;
lastY = ypos;

camera.ProcessMouseMovement(xoffset, yoffset);
}

void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
camera.ProcessMouseScroll(yoffset);
}

//#pragma endregion

 

运行效果

OpenGL绘制2次场景,:一次正常绘制,另一 次摄像机旋转180度后绘制.尝试在你的显示器顶端创建一个小四边形,在上面应用后视镜 的镜面纹理:_ci

如果出现编译错误:

严重性    代码    说明    项目    文件    行    禁止显示状态
错误    C1853    “Debug\framebuffers_exercise1.pch”预编译头文件来自编译器的早期版本,或者预编译头为 C++ 而在 C 中使用它(或相反)    framebuffers_exercise1    d:\openglexercise\framebuffers_exercise1\framebuffers_exercise1\soil.c    1    

在工程中移除SOIL.c文件即可

 

参考:https://learnopengl.com/code_viewer.php?code=advanced/framebuffers-exercise1