在OpenGL的渲染管线中,几何数据和纹理通过一系列变换和测试,最终被渲染成屏幕上的二维像素。那些用于存储颜色值和测试结果的二维数组的几何被称为帧缓冲区(frame buffer)
用于写入颜色值的颜色缓冲、用于写入深度信息的深度缓冲和允许我们根据一些条件丢弃特定片段的模板缓冲,这些缓冲结合起来叫做帧缓冲(Framebuffer),它被储存在内存中
我们目前所做的所有操作都是在默认帧缓冲的渲染缓冲上进行的。默认的帧缓冲是在你创建窗口的时候生成和配置的
OpenGL允许我们定义我们自己的帧缓冲,也就是说我们能够定义我们自己的颜色缓冲,甚至是深度缓冲和模板缓冲。
帧缓冲,即是允许开发将渲染内容绘制到另一个缓冲上,不影响当前屏幕显示,需要显示上述帧缓冲时再取出显示即可。
1、帧缓冲理解
如上图,帧缓冲由以下三种缓冲组成:
- 颜色缓冲,可以有多个颜色缓冲
- 深度缓冲,只有一个
- 模板缓冲,只有一个
从上图看,这些缓冲并不是由帧缓冲创建提供,需要其它来attach。一般来说,颜色缓冲由纹理附件提供,而深度缓冲、模板缓冲由渲染缓冲对象提供。
那么纹理附件和渲染缓冲对象有什么区别呢?
如果你不需要从一个缓冲中采样数据,那么对这个缓冲使用渲染缓冲对象会是明智的选择。如果你需要从缓冲中采样颜色或深度值等数据,那么你应该选择纹理附件。性能方面它不会产生非常大的影响的。
通俗来说,深度及模板缓冲用渲染缓冲对象,颜色缓冲用纹理附件
2、帧缓冲使用
这里不得不提一句时机问题,android中创建帧缓冲如果时机不对,会失败。
GLSurfaceView.Render
有三个接口:
- onSurfaceCreated,suface刚创建,此时创建帧缓冲会失败
- onSurfaceChanged,可以创建帧缓冲
- onDrawFrame,可以创建帧缓冲,不过这个接口可能会被调用很多次,导致会重复创建很多次帧缓冲,冗余调用,于性能有损,不建议在此时操作
整体代码如下:
//先生成帧缓冲id
glGenFramebuffers(1, &mFrameBufferId);
//再绑定帧缓冲id
glBindFramebuffer(GL_FRAMEBUFFER, mFrameBufferId);
//生成纹理
glGenTextures(1, &mFrameTextureId);
//绑定纹理
glBindTexture(GL_TEXTURE_2D, mFrameTextureId);
//给纹理指定数据,确定内存大小,注意给纹理赋值的buf为null
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, MyGlRenderContext::getInstance()->getWidth(),
MyGlRenderContext::getInstance()->getHeight(),
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);
//绑定纹理到对应的颜色缓冲,attach
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mFrameTextureId, 0);
GLuint rbo;
//生成渲染缓冲对象id
glGenRenderbuffers(1, &rbo);
//绑定渲染缓冲对象
glBindRenderbuffer(GL_RENDERBUFFER, rbo);
//创建一个深度和模板渲染缓冲对象
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8,
MyGlRenderContext::getInstance()->getWidth(),
MyGlRenderContext::getInstance()->getHeight());
//绑定到帧缓冲中,attach操作
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);
//检查帧缓冲状态是否异常
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
LOGI("error framebuffer");
}
//修改帧缓冲为默认的,即屏幕
glBindFramebuffer(GL_FRAMEBUFFER, 0);
注意,在指定渲染缓冲对象格式时,是GL_DEPTH24_STENCIL8
,它封装了24位的深度和8位的模板缓冲。
也会有同学有疑问,为什么纹理赋值的buf为null呢?
因为当我们往当前帧缓冲上渲染时,渲染的一切数据就会自动填充到帧缓冲关联的纹理上了,有点类似于android中的bitmap和canvas之间的关系,如果canvas关联了一个bitmap,调用canvas绘制的任何东西都会保存在bitmap中。
所以如何利用帧缓冲绘制呢?通常是渲染到帧缓冲结束时,拿与帧缓冲关联的纹理来绘制即可。且由于帧缓冲也有深度和模板测试,所以再次绘制纹理时,需要关闭深度测试、模板测试。
而且我们拿到这个纹理,可以改动片段着色器,实现很多不一样的效果,比如反相、灰度等。
3、反相的实现
实现反相分为三步:
- 绘制东西到帧缓冲中
- 切换为默认帧缓冲
- 取与帧缓冲关联的纹理id,绘制
具体代码如下:
void FrameBufferSample::draw() {
//绑定
glBindFramebuffer(GL_FRAMEBUFFER, mFrameBufferId);
glEnable(GL_DEPTH_TEST);
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//绘制两个正常的箱子到帧缓冲中
frameShader.use();
auto rat = MyGlRenderContext::getInstance()->getWidth() * 1.0f /
MyGlRenderContext::getInstance()->getHeight();
glm::mat4 projection = glm::perspective(glm::radians(45.0f), rat, 0.1f, 100.0f);
glm::mat4 view = camera.getViewMatrix();
glm::mat4 model = glm::mat4(1.0f);
frameShader.setMat4("view", view);
frameShader.setMat4("projection", projection);
glBindVertexArray(mCubeVao);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, mCubeId);
model = glm::translate(model, glm::vec3(-1.0f, 0.0f, -1.0f));
frameShader.setMat4("model", model);
glDrawArrays(GL_TRIANGLES, 0, 36);
model = glm::mat4(1.0f);
model = glm::translate(model, glm::vec3(2.0f, 0.0f, 0.0f));
frameShader.setMat4("model", model);
glDrawArrays(GL_TRIANGLES, 0, 36);
//绘制地板到帧缓冲中
glBindVertexArray(mFloorVao);
glBindTexture(GL_TEXTURE_2D, mFloorId);
frameShader.setMat4("model", glm::mat4(1.0f));
glDrawArrays(GL_TRIANGLES, 0, 6);
glBindVertexArray(0);
//切换为默认帧缓冲并关闭深度测试
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glDisable(GL_DEPTH_TEST);
glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
//绘制与帧缓冲相关联的纹理
screenShader.use();
glBindVertexArray(mScreenVao);
glBindTexture(GL_TEXTURE_2D, mFrameTextureId);
glDrawArrays(GL_TRIANGLES, 0, 6);
}
到目前为止,我们只是把原本想要绘制的箱子地板等东西绘制到了帧缓冲上,再直接取帧缓冲内容绘制到默认帧缓冲上(即当前屏幕),那说好的反相呢?怎么让整体颜色值取反呢?
在绘制与帧缓冲关联纹理时,我们在它的片段着色器上修改点东西,就可以实现效果:
void main() {
FragColor = vec4(vec3(1.0 - texture(screenTexture, TexCoords)), 1.0);
}
反相效果如图所示:
正常效果:
这里也不得不佩服opengl,做一些相应效果太容易了,如果用c++做,往往得遍历像素,一个个像素处理