OpenGLES FBO-BlitFramebuffer MSAA 抗锯齿的实现

来源:互联网 发布:关闭1099端口 编辑:程序博客网 时间:2024/06/04 20:07

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。
刚刚起步。

这里写图片描述

这里写图片描述

原创粉丝点击