到目前为止,我们使用了几种不同类型的屏幕缓冲:用于写入颜色值的颜色缓冲,用于写入深度信息的深度缓冲,以及允许我们基于一些条件丢弃指定片段的模板缓冲。把这几种缓冲结合起来叫做帧缓冲(Framebuffer),它被储存于内存中。OpenGL给了我们自己定义帧缓冲的自由,我们可以选择性的定义自己的颜色缓冲、深度和模板缓冲。
我们目前所做的渲染操作都是是在默认的帧缓冲之上进行的。当你创建了你的窗口的时候默认帧缓冲就被创建和配置好了(GLFW为我们做了这件事)。通过创建我们自己的帧缓冲我们能够获得一种额外的渲染方式。
你也许不能立刻理解应用程序的帧缓冲的含义,通过帧缓冲可以将你的场景渲染到一个不同的帧缓冲中,可以使我们能够在场景中创建镜子这样的效果,或者做出一些炫酷的特效。首先我们会讨论它们是如何工作的,然后我们将利用帧缓冲来实现一些炫酷的效果。
创建一个帧缓冲
GLuint fbo;
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
现在我们还不能使用自己的帧缓冲,因为还没做完呢。建构一个完整的帧缓冲必须满足以下条件:
- 我们必须往里面加入至少一个附件(颜色、深度、模板缓冲)。
- 其中至少有一个是颜色附件。
- 所有的附件都应该是已经完全做好的(已经存储在内存之中)。
- 每个缓冲都应该有同样数目的样本。
如果你不知道什么是样本也不用担心,我们会在后面的教程中讲到。
纹理附件
当把一个纹理附加到帧缓冲上的时候,所有渲染命令会写入到纹理上,就像它是一个普通的颜色/深度或者模板缓冲一样。使用纹理的好处是,所有渲染操作的结果都会被储存为一个纹理图像,这样我们就可以简单的在着色器中使用了。
创建一个帧缓冲的纹理和创建普通纹理差不多:
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);
//opengl 纹理必须是2的n次幂,如果要在 ES 2.0支持NPOT ,需要 使用 linear filtering 和clamp to edge.
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);
这里主要的区别是我们把纹理的维度设置为屏幕大小(尽管不是必须的),我们还传递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
。
也可以同时附加一个深度缓冲和一个模板缓冲为一个单独的纹理。这样纹理的每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, texture, 0);
渲染缓冲对象附件
和纹理图像一样,渲染缓冲对象也是一个缓冲,它可以是一堆字节、整数、像素或者其他东西。渲染缓冲对象的一大优点是,它以OpenGL原生渲染格式储存它的数据,因此在离屏渲染到帧缓冲的时候,这些数据就相当于被优化过的了。 渲染缓冲对象将所有渲染数据直接储存到它们的缓冲里,而不会进行针对特定纹理格式的任何转换,这样它们就成了一种快速可写的存储介质了。然而,渲染缓冲对象通常是只写的,不能修改它们(就像获取纹理,不能写入纹理一样)。可以用glReadPixels
函数去读取,函数返回一个当前绑定的帧缓冲的特定像素区域,而不是直接返回附件本身。
因为它们的数据已经是原生格式了,在写入或把它们的数据简单地到其他缓冲的时候非常快。当使用渲染缓冲对象时,像切换缓冲这种操作变得异常高速。我们在每个渲染迭代末尾使用的那个glfwSwapBuffers
函数,同样以渲染缓冲对象实现:我们简单地写入到一个渲染缓冲图像,最后交换到另一个里。渲染缓冲对象对于这种操作来说很完美。
创建一个渲染缓冲对象和创建帧缓冲代码差不多:
GLuint rbo;
glGenRenderbuffers(1, &rbo);
相似地,我们打算把渲染缓冲对象绑定,这样所有后续渲染缓冲操作都会影响到当前的渲染缓冲对象:
glBindRenderbuffer(GL_RENDERBUFFER, rbo);
由于渲染缓冲对象通常是只写的,它们经常作为深度和模板附件来使用,由于大多数时候,我们不需要从深度和模板缓冲中读取数据,但仍关心深度和模板测试。我们就需要有深度和模板值提供给测试,但不需要对这些值进行采样(sample),所以深度缓冲对象是完全符合的。当我们不去从这些缓冲中采样的时候,渲染缓冲对象通常很合适,因为它们等于是被优化过的。
调用glRenderbufferStorage
函数可以创建一个深度和模板渲染缓冲对象:
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 800, 600);
创建一个渲染缓冲对象与创建纹理对象相似,不同之处在于这个对象是专门被设计用于图像的,而不是通用目的的数据缓冲,比如纹理。这里我们选择GL_DEPTH24_STENCIL8
作为内部格式,它同时代表24位的深度和8位的模板缓冲。
最后一件还要做的事情是把帧缓冲对象附加上:
//GL_DEPTH_STENCIL_ATTACHMENT 在opengl es中未定义,只能分别把渲染缓冲对象附加到帧缓冲的深度和模板附件上
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rbo);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);
在帧缓冲项目中,渲染缓冲对象可以提供一些优化,但更重要的是知道何时使用渲染缓冲对象,何时使用纹理。通常的规则是,如果你永远都不需要从特定的缓冲中进行采样,渲染缓冲对象对特定缓冲是更明智的选择。如果哪天需要从比如颜色或深度值这样的特定缓冲采样数据的话,你最好还是使用纹理附件。从执行效率角度考虑,它不会对效率有太大影响。
渲染到纹理
现在我们知道了(一些)帧缓冲如何工作的,是时候把它们用起来了。我们会把场景渲染到一个颜色纹理上,这个纹理附加到一个我们创建的帧缓冲上,然后把纹理绘制到一个简单的四边形上,这个四边形铺满整个屏幕。输出的图像看似和没用帧缓冲一样,但是这次,它其实是直接打印到了一个单独的四边形上面。为什么这很有用呢?下一部分我们会看到原因。
第一件要做的事情是创建一个帧缓冲对象,并绑定它,这比较明了:
GLuint framebuffer;
glGenFramebuffers(1, &framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
下一步我们创建一个纹理图像,这是我们将要附加到帧缓冲的颜色附件。我们把纹理的尺寸设置为窗口的宽度和高度,并保持数据未初始化:
// 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, texColorBuffer, 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);
然后我们要检查帧缓冲是否真的做好了,如果没有,我们就打印一个错误消息。
//framebuffer并且添加了所有的附件,现在检查它是否完成了
if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE){
CCLOG("ERROR::FRAMEBUFFER:: Framebuffer is not complete!:%x",glCheckFramebufferStatus(GL_FRAMEBUFFER));
}
//恢复默认framebuffer
glBindFramebuffer(GL_FRAMEBUFFER, _oldFBO);
还要保证解绑帧缓冲,这样我们才不会意外渲染到错误的帧缓冲上。
现在帧缓冲做好了,我们要做的全部就是渲染到帧缓冲上,而不是绑定到帧缓冲对象的默认缓冲。余下所有命令会影响到当前绑定的帧缓冲上。所有深度和模板操作同样会从当前绑定的帧缓冲的深度和模板附件中读取,当然,得是在它们可用的情况下。如果你遗漏了比如深度缓冲,所有深度测试就不会工作,因为当前绑定的帧缓冲里没有深度缓冲。
所以,为把场景绘制到一个单独的纹理,我们必须以下面步骤来做:
- 使用新的绑定为激活帧缓冲的帧缓冲,像往常那样渲染场景。
- 绑定到默认帧缓冲。
- 绘制一个四边形,让它平铺到整个屏幕上,用新的帧缓冲的颜色缓冲作为他的纹理。
// screen.vsh
attribute vec2 a_position;
attribute vec2 a_texcoord;
varying vec2 v_textureCoord;
void main()
{
gl_Position = vec4(a_position.x, a_position.y, 0.0, 1.0);
v_textureCoord = a_texcoord;
}
片段着色器:
//screen.fsh
varying vec2 v_textureCoord;
uniform sampler2D u_myTexture; //纹理采样器, 它不需要手动赋值,在onDraw()中绑定纹理时自动赋值。
void main()
{
//原相
// gl_FragColor = texture2D(u_myTexture, v_textureCoord);
///反相
// gl_FragColor = vec4(vec3(1.0 -texture2D(u_myTexture, v_textureCoord)), 1);
//灰度
// vec4 color = texture2D(u_myTexture, v_textureCoord);
// float average = 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b;
// gl_FragColor = vec4(average, average, average, 1.0);
//Kernel effects
const float offset = 1.0 / 300.0;
vec2 offsets[9];
offsets[0] = vec2(-offset, offset); // top-left
offsets[1] = vec2(0.0, offset); // top-center
offsets[2] = vec2(offset, offset); // top-right
offsets[3] = vec2(-offset, 0.0); // center-left
offsets[4] = vec2(0.0, 0.0); // center-center
offsets[5] = vec2(offset, 0.0); // center-right
offsets[6] = vec2(-offset, -offset); // bottom-left
offsets[7] = vec2(0.0, -offset); // bottom-center
offsets[8] = vec2(offset, -offset); // bottom-right
//锐化kernel数组
// -1.0, -1.0, -1.0,
// -1.0, 9.0, -1.0,
// -1.0, -1.0, -1.0,
// float kernel[9];
// for (int i=0; i<9; i++) {
// kernel[i] = -1.0;
// }
// kernel[4] = 9.0;
//模糊kernel
// // | 1 2 1 |
// // | 2 4 2 | / 16
// // | 1 2 1 |
// float kernel[9];
// for (int i=0; i<9; i++) {
// kernel[i] = 1.0 / 16.0;
// if (i == 1 || i==3 || i==5 || i== 7) {
// kernel[i] = 2.0 / 16.0;
// }
// if (i == 4) {
// kernel[i] = 4.0 / 16.0;
// }
// }
//边检测kernel
// | 1 1 1 |
// | 1 -8 1 |
// | 1 1 1 |
float kernel[9];
for (int i=0; i<9; i++) {
kernel[i] = 1.0;
}
kernel[4] = -8.0;
vec3 sampleTex[9];
for(int i = 0; i < 9; i++)
{
sampleTex[i] = vec3(texture2D(u_myTexture, v_textureCoord.st + offsets[i]));
}
vec3 col = vec3(0.0, 0.0, 0.0);
for(int i = 0; i < 9; i++){
col += sampleTex[i] * kernel[i];
}
gl_FragColor = vec4(col, 1.0);
}
接着需要你为屏幕上的四边形创建和配置一个VAO。渲染迭代中帧缓冲处理会有下面的结构:
// 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, _oldFBO); // 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);
全部代码如下:
//
// OpenGLFrameBuffer.cpp
#include "OpenGLFrameBuffer.h"
#ifndef GL_STENCIL_INDEX
#define GL_STENCIL_INDEX 0x1901
#endif
OpenGLFrameBuffer::~OpenGLFrameBuffer()
{
glDeleteFramebuffers(1, &framebuffer);
}
bool OpenGLFrameBuffer::init()
{
if (!Layer::init()) {
return false;
}
origin = Director::getInstance()->getVisibleOrigin();
vsize = Director::getInstance()->getVisibleSize();
GLfloat cubeVertices[] = {
// Back face
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, // Bottom-left
0.5f, 0.5f, -0.5f, 1.0f, 1.0f, // top-right
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, // bottom-right
0.5f, 0.5f, -0.5f, 1.0f, 1.0f, // top-right
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, // bottom-left
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, // top-left
// Front face
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, // bottom-left
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, // bottom-right
0.5f, 0.5f, 0.5f, 1.0f, 1.0f, // top-right
0.5f, 0.5f, 0.5f, 1.0f, 1.0f, // top-right
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f, // top-left
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, // bottom-left
// Left face
-0.5f, 0.5f, 0.5f, 1.0f, 0.0f, // top-right
-0.5f, 0.5f, -0.5f, 1.0f, 1.0f, // top-left
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f, // bottom-left
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f, // bottom-left
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, // bottom-right
-0.5f, 0.5f, 0.5f, 1.0f, 0.0f, // top-right
// Right face
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, // top-left
0.5f, -0.5f, -0.5f, 0.0f, 1.0f, // bottom-right
0.5f, 0.5f, -0.5f, 1.0f, 1.0f, // top-right
0.5f, -0.5f, -0.5f, 0.0f, 1.0f, // bottom-right
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, // top-left
0.5f, -0.5f, 0.5f, 0.0f, 0.0f, // bottom-left
// Bottom face
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f, // top-right
0.5f, -0.5f, -0.5f, 1.0f, 1.0f, // top-left
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, // bottom-left
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, // bottom-left
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, // bottom-right
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f, // top-right
// Top face
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, // top-left
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, // bottom-right
0.5f, 0.5f, -0.5f, 1.0f, 1.0f, // top-right
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, // bottom-right
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, // top-left
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f // bottom-left
};
GLfloat planeVertices[] = {
// 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
};
auto gprogram = GLProgram::createWithFilenames("box.vsh", "box.fsh");
this->setGLProgram(gprogram);
// box vao;
glGenVertexArrays(1, &boxvao);
glBindVertexArray(boxvao);
GLuint boxvbo;
glGenBuffers(1, &boxvbo);
glBindBuffer(GL_ARRAY_BUFFER, boxvbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(cubeVertices), cubeVertices, GL_STATIC_DRAW);
//给vertex shader 的属性传值
GLuint posloc = glGetAttribLocation(gprogram->getProgram(), "a_position");
glEnableVertexAttribArray(posloc);
glVertexAttribPointer(posloc, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)0);
GLuint texloc = glGetAttribLocation(gprogram->getProgram(), "a_texcoord");
glEnableVertexAttribArray(texloc);
glVertexAttribPointer(texloc, 2, GL_FLOAT, GL_FALSE, 5* sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
glBindVertexArray(0);
//floor vao;
glGenVertexArrays(1, &floorvao);
glBindVertexArray(floorvao);
GLuint floorvbo;
glGenBuffers(1, &floorvbo);
glBindBuffer(GL_ARRAY_BUFFER, floorvbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(planeVertices), planeVertices, GL_STATIC_DRAW);
//给vertex shader 的属性传值
GLuint posloc2 = glGetAttribLocation(gprogram->getProgram(), "a_position");
glVertexAttribPointer(posloc2, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(posloc2);
GLuint texloc2 = glGetAttribLocation(gprogram->getProgram(), "a_texcoord");
glVertexAttribPointer(texloc2, 2, GL_FLOAT, GL_FALSE, 5* sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
glEnableVertexAttribArray(texloc2);
glBindVertexArray(0);
//
//screen
screenprogram = new GLProgram();
screenprogram->initWithFilenames("screen.vsh", "screen.fsh");
screenprogram->link();
glGenVertexArrays(1, &screenvao);
glBindVertexArray(screenvao);
GLuint screenvbo;
glGenBuffers(1, &screenvbo);
glBindBuffer(GL_ARRAY_BUFFER, screenvbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), quadVertices, GL_STATIC_DRAW);
GLuint gpos = screenprogram->getAttribLocation("a_position");
glEnableVertexAttribArray(gpos);
glVertexAttribPointer(gpos, 2, GL_FLOAT, GL_FALSE, 4*sizeof(GLfloat), (GLvoid*)0);
GLuint gtex = screenprogram->getAttribLocation("a_texcoord");
glVertexAttribPointer(gtex, 2, GL_FLOAT, GL_FALSE, 4*sizeof(GLfloat), (GLvoid*)(2*sizeof(GLfloat)));
glEnableVertexAttribArray(gtex);
glBindVertexArray(0);
//
// 开启深度测试
Director::getInstance()->setDepthTest(true);
// glDepthFunc(GL_LESS);
//加载纹理
auto sprite = Sprite::create("container2.png");
boxTexture = sprite->getTexture()->getName();
auto floorimg = Sprite::create("wall.jpg");
floorTexture = floorimg->getTexture()->getName();
cam = new MyCamera(Vec3(0, 0, 3));
//touch事件
auto elistener = EventListenerTouchOneByOne::create();
elistener->onTouchBegan = CC_CALLBACK_2(OpenGLFrameBuffer::onTouchBegan, this);
elistener->onTouchMoved = CC_CALLBACK_2(OpenGLFrameBuffer::onTouchMoved, this);
elistener->onTouchEnded = CC_CALLBACK_2(OpenGLFrameBuffer::onTouchEnded, this);
_eventDispatcher->addEventListenerWithSceneGraphPriority(elistener, this);
//
//获取系统默认的framebuffer
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &_oldFBO);
//自定义framebuffer
glGenFramebuffers(1, &framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
//给帧缓冲添加一个颜色附件
screenTexture = this->generateAttachmentTexture(false, false);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, screenTexture, 0);
//创建一个renderbuffer渲染缓冲物体用以做深度和模板附件
GLuint rbo;
glGenRenderbuffers(1, &rbo);
glBindRenderbuffer(GL_RENDERBUFFER, rbo);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, vsize.width, vsize.height);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
//GL_DEPTH_STENCIL_ATTACHMENT 在opengl es中未定义,只能分别把渲染缓冲对象附加到帧缓冲的深度和模板附件上
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rbo);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);
//既然已经创建了framebuffer并且添加了所有的附件,现在检查它是否完成了
if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE){
CCLOG("ERROR::FRAMEBUFFER:: Framebuffer is not complete!:%x",glCheckFramebufferStatus(GL_FRAMEBUFFER));
}
//恢复默认framebuffer
glBindFramebuffer(GL_FRAMEBUFFER, _oldFBO);
//RenderTexture
return true;
}
// Generates a texture that is suited for attachments to a framebuffer
//产生一个适合于给帧缓冲当附件的纹理
GLuint OpenGLFrameBuffer::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);
//纹理长宽必须是2的n次幂
float width = vsize.width;//ccNextPOT(vsize.width);
float height = vsize.height;//ccNextPOT(vsize.height);
if(!depth && !stencil){
glTexImage2D(GL_TEXTURE_2D, 0, attachment_type, width, height, 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, width, height, 0, GL_DEPTH_STENCIL_OES, GL_UNSIGNED_INT_24_8_OES, NULL);
}
//opengl 纹理必须是2的n次幂,如果要在 ES 2.0支持NPOT ,需要 requires that you use linear filtering and clamp to edge. Also, no mipmaps.
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);
glBindTexture(GL_TEXTURE_2D, 0);
return textureID;
}
void OpenGLFrameBuffer::visit(cocos2d::Renderer *render, const cocos2d::Mat4 &parentTransform, uint32_t parentflag)
{
Layer::visit(render, parentTransform, parentflag);
_command.init(_globalZOrder);
_command.func = CC_CALLBACK_0(OpenGLFrameBuffer::onDraw, this);
Director::getInstance()->getRenderer()->addCommand(&_command);
}
void OpenGLFrameBuffer::onDraw()
{
/
/
//获取系统默认的framebuffer
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &_oldFBO);
//绑定自定义framebuffer
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Director::getInstance()->setDepthTest(true);
auto program = this->getGLProgram();
program->use();
auto view = cam->GetViewMatrix();
auto projection = new Mat4;
Mat4::createPerspective(cam->Zoom, vsize.width/vsize.height, 0.1, 1000.0f, projection);
//给vertex shader 的uniform 传值
glUniformMatrix4fv(glGetUniformLocation(program->getProgram(), "view"), 1, GL_FALSE,view->m);
glUniformMatrix4fv(program->getUniformLocation("projection"), 1, GL_FALSE, projection->m);
//box
glBindVertexArray(boxvao);
GL::bindTexture2DN(0, boxTexture);
glUniform1i(program->getUniformLocation("u_tex"), 0);
auto model = new Mat4();
model->translate(-1, 0, -1);
glUniformMatrix4fv(glGetUniformLocation(program->getProgram(), "model"), 1, GL_FALSE, model->m);
glDrawArrays(GL_TRIANGLES, 0, 36);
auto model2 = new Mat4;
model2->translate(1, 0, 0);
glUniformMatrix4fv(program->getUniformLocation("model"), 1, GL_FALSE, model2->m);
glDrawArrays(GL_TRIANGLES, 0, 36);
glBindVertexArray(0);
//floor
glBindVertexArray(floorvao);
GL::bindTexture2D(floorTexture);
auto fmodel = new Mat4;
glUniformMatrix4fv(program->getUniformLocation("model"), 1, GL_FALSE, fmodel->m);
glDrawArrays(GL_TRIANGLES, 0, 6);
glBindVertexArray(0);
/
/
//恢复默认framebuffer, 绘制带有screen 纹理的四边形平面
glBindFramebuffer(GL_FRAMEBUFFER, _oldFBO);
glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// Director::getInstance()->setDepthTest(false);
//screen
screenprogram->use();
glBindVertexArray(screenvao);
GL::bindTexture2D(screenTexture); //screenTexture 写入的是 0000
glDrawArrays(GL_TRIANGLES, 0, 6);
glBindVertexArray(0);
}
#pragma mark touch
bool OpenGLFrameBuffer::onTouchBegan(cocos2d::Touch *touch, cocos2d::Event *evt)
{
return true;
}
void OpenGLFrameBuffer::onTouchMoved(cocos2d::Touch *touch, cocos2d::Event *evt)
{
Vec2 curpos = touch->getLocationInView();
Vec2 prepos = touch->getPreviousLocationInView();
GLfloat dx = curpos.x - prepos.x;
GLfloat dy = curpos.y - prepos.y;
//移动摄像机
GLfloat camspeed = 0.05f;
if (curpos.y - prepos.y > 0) { //w
cam->ProcessKeyboard(Cam_Move::FORWARD, camspeed);
}else if (curpos.y - prepos.y < 0){ //s
cam->ProcessKeyboard(Cam_Move::BACKWARD, camspeed);
}
else if (curpos.x - prepos.x < 0){ //a
cam->ProcessKeyboard(Cam_Move::LEFT, camspeed);
}else if (curpos.x - prepos.x > 0){ //d
cam->ProcessKeyboard(Cam_Move::RIGHT, camspeed);
}
//(3)旋转摄像机
// cam->ProcessMouseMovement(dx, dy);
//(4)缩放
// if(fov >= 1 && fov <= 45){
// fov -= dx * camspeed;
// }
// if(fov <= 1){
// fov = 1;
// }
// if(fov >= 45){
// fov = 45;
// }
}
void OpenGLFrameBuffer::onTouchEnded(cocos2d::Touch *touch, cocos2d::Event *evt)
{
}