OpenGLES FBO-BlitFramebuffer MSAA 抗锯齿的实现

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

这里写图片描述

这里写图片描述

  • 6
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

深海Enoch

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值