OpenGLES FBO-BlitFramebuffer MSAA 抗锯齿的实现
不太好用的FBO-Blit MSAA
昨天写了一篇 OpenGLES 在几种情况下的 MSAA(Multisample Anti-aliasing)抗锯齿,其中唯独没有提到一种利用 FBO-Blit 的抗锯齿方法,那是因为我没能实现。我对 OpenGL MSAA 的大部分代码的了解都是根据 关于支持多重采样的FBO和Texture 这篇博文的描述。他也提到了三种实现MSAA的方法,而我在实现其中第二种方法 FBO-BlitFramebuffer ,在调用 glBlitFramebuffer()
方法时,一直遇到了一个 GL_INVALID_OPERATION 的错误。根据OpenGLES的官方文档显示,在调用该方法时遇到这样的错误是因为 ReadBuffer 与 DrawBuffer 不一致导致的。直到昨天把 RTT MSAA 搞定交差以后,今天忽然心血来潮重新调试这段代码,最终实现了 FBO-BlitFramebuffer MSAA。
FBO BlitFramebuffer MSAA 的实现
首先,定义一个方法,用来作 MultisampleFramebuffer 的初始化工作,该方法可在创建完 GLContext 之后 glDraw() 之前调用一次。
void Renderer::InitMultisampleAntiAliasing(GLint samples) {
// 创建一个 Multisample Framebuffer,并绑定为当前操作的 Framebuffer。
glGenFramebuffers(1, &m_MSFBO);
glBindFramebuffer(GL_FRAMEBUFFER, m_MSFBO);
// 创建一个 Multisample 的 Renderbuffer colorBuffer
glGenRenderbuffers(1, &m_MSColor);
glBindRenderbuffer(GL_RENDERBUFFER, m_MSColor);
glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, GL_RGBA8, m_width, m_height); // 请注意这里的 GL_RGBA8,
checkGLError("GenMSColorBuffer");
// 把创建好的 colorBuffer 绑定到 Framebuffer 上。
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_MSColor);
checkGLError("FboRbo,COLORATTACHMENT");
// 创建一个 Multisample 的 Renderbuffer depthBuffer,如果不适用深度检测,这一步可以省略
glGenRenderbuffers(1, &m_MSDepth);
glBindRenderbuffer(GL_RENDERBUFFER, m_MSDepth);
glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, GL_DEPTH_COMPONENT16, m_width, m_height);
checkGLError("GenDepthBuffer");
// 把创建好的 depthBuffer 绑定到 Framebuffer 上。
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, m_MSDepth);
checkGLError("DepthBuffer,Renderbuffer");
// glDrawBuffers Specifies a list of color buffers to be drawn into
// 即,定义把图像绘制到 GL_COLOR_ATTACHMENT0 的 Framebuffer 中
GLenum drawBufs[] = {GL_COLOR_ATTACHMENT0};
glDrawBuffers(1, drawBufs);
checkGLError("DrawBuffer");
// 检查 Framebuffer 的完整性,处理异常情况
if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
LOG_ERROR("failed to make complete framebuffer object %x", glCheckFramebufferStatus(GL_FRAMEBUFFER));
}
}
在glDraw()
调用之前,先将上面申请的 Framebuffer
绑定为绘制对象。
glBindFramebuffer(GL_FRAMEBUFFER, m_MSFBO);
glBindRenderbuffer(GL_RENDERBUFFER, m_MSColor);
checkGLError("BindTwoBuffers");
然后调用 glDraw()
glFinish()
等绘制指令。然后进行传图:
glBindFramebuffer(GL_READ_FRAMEBUFFER, m_MSFBO);
checkGLError("BindReadBuffer");
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); // 把Framebuffer0,即屏幕绑定为写入Buffer
checkGLError("BindDrawBuffer");
glBlitFramebuffer(0, 0, m_width, m_height, 0, 0, m_width, m_height,
GL_COLOR_BUFFER_BIT, GL_NEAREST);
checkGLError("BlitFramebufferColor");
glBlitFramebuffer(0, 0, m_width, m_height, 0, 0, m_width, m_height,
GL_DEPTH_BUFFER_BIT, GL_NEAREST);
checkGLError("BlitFramebufferDepth");
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
传图完成后,MSAA过的图像就已经绘制在屏幕的Framebuffer上了,只需要再调用一下eglSwapBuffers(_display, _surface)
即可完成整个抗锯齿Demo。
但是
上面的流程看似简单,写起来也不难实现。然而在真正运行时却绘制了一片黑暗。因此,加入了众多 checkGLError("xxx")
语句来打 Log 查错。 checkGLError("xxx")
的原理很简单,里面调用了 glGetError()
并将 Error 名称及位置(传入的 xxx 字符串)打印出来。代码如下:
void Renderer::checkGLError(const char* str) {
switch (glGetError())
{
case GL_NO_ERROR:
LOG_INFO("ENOGH:NO_ERROR %s" , str);
break;
case GL_INVALID_ENUM:
LOG_INFO("ENOCH:INVALID_ENUM %s", str);
break;
case GL_INVALID_VALUE:
LOG_INFO("ENOCH:INVALID_VALUE %s", str);
break;
case GL_INVALID_OPERATION:
LOG_INFO("ENOCH:INVALID_OPERATION %s", str);
break;
case GL_INVALID_FRAMEBUFFER_OPERATION:
LOG_INFO("ENOCH:INVALID_FRAMEBUFFER_OPERATION %s", str);
break;
case GL_OUT_OF_MEMORY:
LOG_INFO("ENOCH:OUT_OF_MEMORY %s", str);
break;
default:
LOG_INFO("SOMETHING_WRONG %s", str);
break;
}
}
于是,在程序运行时,Log 中获得了这样的字样:
…………………………….
ENOCH:NO_ERROR BindDrawBuffer
ENOCH:INVALID_OPERATION BlitFramebufferColor
ENOCH:NO_ERROR BlitFramebufferDepth
………………………….
因此,问题就出现在了glBlitFramebuffer(0, 0, m_width, m_height, 0, 0, m_width, m_height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
这一句调用上。经过一堆尝试,以及去 Stack Overflow 上提问,终于了解到,是创建 Framebuffer 时的一些属性与创建 Context 时的 Attribs 不符。下面,再把上次那个Attribs 贴上来。
const EGLint attribs[] = {
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL_BLUE_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_RED_SIZE, 8,
EGL_ALPHA_SIZE, 8, // GL_RGBA8
EGL_SAMPLE_BUFFERS, 1, // 不该写
EGL_SAMPLES, 4, // 不该写
EGL_NONE
};
首先是,我们在 InitMultisampleAntiAliasing()
方法中调用的 glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, GL_RGBA8, m_width, m_height); // 请注意这里的 GL_RGBA8
这一句,GL_RGBA8,要与 attribs 中的那三个变量对应起来,否则将会报出上面提到的 INVALID_OPERATION 错误。
此外,按照逻辑,这里创建的是 samples = 4 的 Multisample Renderbuffer,本以为 attribs 中也应该有这样的定义,然而事实并非如此。只有把 attribs 中注释为 “不该写” 的两行注释掉,才能正常的绘制出预期的图形。
再此外,DepthBuffer 如果报错,则应考虑是否与“申请 Depth Multisample Renderbuffer 时的属性 与 attribs 中定义的Depth属性不符”有关。我可能是因为调用了 glDisable(GL_DEPTH_TEST)
,因此,glBlitFramebuffer depthbuffer 处并未报错。
最后,效果图与上篇文章 jni 环境中的 context MSAA 效果一致,不再贴图了。
欢迎关注我的个人公众号 VR_Tech。
刚刚起步。