使用OpenGL进行抗锯齿/平滑/超级采样2D图像



我在OpenGL中玩2D图形 - 分形和其他有趣的东西^_^。我的基本设置是渲染几个三角形来填充屏幕,并使用片段着色器在上面绘制很酷的东西。我想把事情理顺一点,所以我开始研究超级采样。对我来说,如何做到这一点并不明显。这是我到目前为止尝试过的...

首先,我查看了有关抗锯齿的Apple文档。我更新了像素格式初始化:

NSOpenGLPixelFormatAttribute attrs[] =
{
    NSOpenGLPFADoubleBuffer,
    NSOpenGLPFADepthSize, 24,
    NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion4_1Core,
    NSOpenGLPFASupersample,
    NSOpenGLPFASampleBuffers, 1,
    NSOpenGLPFASamples, 4,
    0
};

我还添加了glEnable(GL_MULTISAMPLE);行。 GL_MULTISAMPLE_FILTER_HINT_NV似乎没有定义(文档似乎已过时),所以我不确定在那里做什么。

这使得我的渲染速度变慢,但似乎没有进行抗锯齿,所以我尝试了OpenGL Wiki上描述的"渲染到FBO"方法。我尝试了很多变化,结果多种多样:成功渲染(似乎没有抗锯齿),将垃圾渲染到屏幕上(有趣!),崩溃(应用程序蒸发,我得到一个关于图形问题的系统对话框),以及让我的笔记本电脑除了光标之外没有响应(硬重启后出现有关图形问题的系统对话框)。

我在绘制之前正在检查帧缓冲的状态,所以我知道这不是问题所在。而且我确定我使用的是硬件而不是软件进行渲染 - 在其他帖子上看到了该建议。

我已经花了很多时间在上面,但仍然不太明白如何处理这个问题。我希望得到一些帮助的一件事是如何查询 GL 以查看是否正确启用了超级采样,或者如何判断我的片段着色器被调用了多少次等。我也对一些调用的去向有点困惑 - 我找到的大多数示例只是说要调用哪些方法,但没有指定哪些方法需要在draw回调中调用。任何人都有一个简单的SSAA与OpenGL 3或4和OSX的例子。还是其他可以尝试的事情?

编辑:绘图代码 - 超级破碎(不要评判我),但供参考:

- (void)draw
{
    glBindVertexArray(_vao);    // todo: is this necessary? (also in init)
    glBufferData(GL_ARRAY_BUFFER, 12 * sizeof(GLfloat), points, GL_STATIC_DRAW);
    glGenTextures( 1, &_tex );
    glBindTexture( GL_TEXTURE_2D_MULTISAMPLE, _tex );
    glTexImage2DMultisample( GL_TEXTURE_2D_MULTISAMPLE, 4, GL_RGBA8, _width * 2, _height * 2, false );
    glGenFramebuffers( 1, &_fbo );
    glBindFramebuffer( GL_FRAMEBUFFER, _fbo );
    glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, _tex, 0 );
    GLint status;
    status = glCheckFramebufferStatus( GL_FRAMEBUFFER );
    if (status != GL_FRAMEBUFFER_COMPLETE) {
        NSLog(@"incomplete buffer 0x%x", status);
        return;
    }
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
    glBindFramebuffer(GL_READ_FRAMEBUFFER, _fbo);
    glDrawBuffer(GL_BACK);
    glClear(GL_COLOR_BUFFER_BIT);
    glDrawArrays(GL_TRIANGLES, 0, 6);
    glBlitFramebuffer(0, 0, _width * 2, _height * 2, 0, 0, _width, _height, GL_COLOR_BUFFER_BIT, GL_LINEAR);
    glDeleteTextures(1, &_tex);
    glDeleteFramebuffers(1, &_fbo);
    glBindFramebuffer( GL_FRAMEBUFFER, 0 );
}

更新:我根据以下 Reto 的建议更改了我的代码:

glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo);
glClear(GL_COLOR_BUFFER_BIT);
glDrawArrays(GL_TRIANGLES, 0, 6);
glBindFramebuffer(GL_READ_FRAMEBUFFER, _fbo);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glBlitFramebuffer(0, 0, _width * 2, _height * 2, 0, 0, _width, _height,
                  GL_COLOR_BUFFER_BIT, GL_LINEAR);

这导致程序将垃圾呈现到屏幕上。然后我去掉了* 2乘数,它仍然把垃圾画到屏幕上。然后,我关闭了与多重/超级采样相关的NSOpenGLPFA选项,它正常渲染,没有抗锯齿。

我也尝试使用非多重采样纹理,但不断收到不完整的附件错误。我不确定这是由于 OpenGL wiki 上提到的 NVidia 问题(将发表评论,因为我没有足够的代表发布超过 2 个链接)还是其他原因。如果有人可以建议一种方法来找出附件不完整的原因,那将非常非常有帮助。

最后,我尝试使用渲染缓冲区而不是纹理,发现在glRenderbufferStorage中指定大于视口大小的宽度和高度似乎无法按预期工作。

GLuint rb;
glGenRenderbuffers(1, &rb);
glBindRenderbuffer(GL_RENDERBUFFER, rb);
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, _width * 2, _height * 2);
// ...
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo);
glClear(GL_COLOR_BUFFER_BIT);
glDrawArrays(GL_TRIANGLES, 0, 6);
glBindFramebuffer(GL_READ_FRAMEBUFFER, _fbo);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glBlitFramebuffer(0, 0, _width * 2, _height * 2, 0, 0, _width, _height,
                  GL_COLOR_BUFFER_BIT, GL_LINEAR);

。在屏幕的左下角 1/4 渲染。不过它似乎并不更顺畅...

更新2:视口大小翻倍,没有更流畅。转动NSOpenGLPFASupersample仍然会导致它将垃圾吸引到屏幕上。>.<</p>

更新3:我是个白痴,完全更顺畅。它只是看起来不好,因为我使用的是丑陋的配色方案。我必须将所有坐标加倍,因为视口是 2x。哦,好吧。我仍然希望得到一些帮助来理解为什么NSOpenGLPFASupersample会导致如此疯狂的行为......

您在此处的调用序列看起来不会执行您的预期:

glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glBindFramebuffer(GL_READ_FRAMEBUFFER, _fbo);
glDrawBuffer(GL_BACK);
glClear(GL_COLOR_BUFFER_BIT);
glDrawArrays(GL_TRIANGLES, 0, 6);
glBlitFramebuffer(0, 0, _width * 2, _height * 2, 0, 0, _width, _height, GL_COLOR_BUFFER_BIT, GL_LINEAR);

当您调用 glClear()glDrawArrays() 时,当前绘制帧缓冲区(由上次调用 glBindFramebuffer(GL_DRAW_FRAMEBUFFER, ...) 确定)是默认帧缓冲区。所以你永远不会渲染给FBO。让我对上述内容进行注释:

// Set draw framebuffer to default (0) framebuffer. This is where the rendering will go.
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
// Set read framebuffer to the FBO.
glBindFramebuffer(GL_READ_FRAMEBUFFER, _fbo);
// This is redundant, GL_BACK is the default draw buffer for the default framebuffer.
glDrawBuffer(GL_BACK);
// Clear the current draw framebuffer, which is the default framebuffer.
glClear(GL_COLOR_BUFFER_BIT);
// Draw to the current draw framebuffer, which is the default framebuffer.
glDrawArrays(GL_TRIANGLES, 0, 6);
// Copy from read framebuffer (which is the FBO) to the draw framebuffer (which is the
// default framebuffer). Since no rendering was done to the FBO, this will copy garbage
// into the default framebuffer, wiping out what was previously rendered.
glBlitFramebuffer(0, 0, _width * 2, _height * 2, 0, 0, _width, _height,
                  GL_COLOR_BUFFER_BIT, GL_LINEAR);

要使其正常工作,您需要在渲染时将绘制帧缓冲设置为 FBO,然后将读取帧缓冲设置为 FBO,将绘制帧缓冲设置为副本的默认值:

glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo);
glClear(GL_COLOR_BUFFER_BIT);
glDrawArrays(GL_TRIANGLES, 0, 6);
glBindFramebuffer(GL_READ_FRAMEBUFFER, _fbo);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glBlitFramebuffer(0, 0, _width * 2, _height * 2, 0, 0, _width, _height,
                  GL_COLOR_BUFFER_BIT, GL_LINEAR);

回顾:

  • 绘制命令写入GL_DRAW_FRAMEBUFFER .
  • GL_READ_FRAMEBUFFERGL_DRAW_FRAMEBUFFER glBlitFramebuffer()副本。

关于代码的更多评论:

  • 由于您要创建两倍大小的多重采样纹理,因此您同时使用多重采样和超级采样:

    glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, _tex);
    glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 4, GL_RGBA8,
                            _width * 2, _height * 2, false);
    

    这是完全合法的。可是...大量的采样。如果您只想进行超级采样,则可以使用常规纹理。

  • 您可以对 FBO 颜色目标使用渲染缓冲区而不是纹理。没有巨大的优势,但它更简单,并且可能更有效。如果您想稍后对结果进行采样,则只需要将纹理用作附件,而此处的情况并非如此。

最新更新