Anti-aliasing/smoothing/supersampling 带有 opengl 的 2d 图像

Anti-aliasing/smoothing/supersampling 2d images with opengl

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

首先,我查看了Apple docs on anti-aliasing。我更新了我的像素格式初始化:

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

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

这使我的渲染速度变慢但似乎没有进行抗锯齿处理,所以我尝试了 OpenGL Wiki on Multisampling 中描述的 "Render-to-FBO" 方法。我尝试了很多变体,结果各不相同:成功渲染(似乎没有消除锯齿)、在屏幕上渲染垃圾(有趣!)、崩溃(应用程序消失,我得到一个系统对话框关于图形问题),并使我的笔记本电脑除了光标外没有响应(硬重启后出现关于图形问题的系统对话框)。

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

我已经花了相当多的时间在这上面,但仍然不太明白如何处理这个问题。我想要一些帮助的一件事是如何查询 GL 以查看是否正确启用了超级采样,或者如何判断我的片段着色器被调用了多少次等。我也对某些调用的位置感到困惑go - 我发现的大多数示例只是说明要调用哪些方法,但没有指定哪些方法需要进入 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 乘数,它仍然在屏幕上绘制垃圾。然后我关闭了与 multi/super-sampling 相关的 NSOpenGLPFA 选项,并且它正常呈现,没有抗锯齿。

我也尝试过使用非多重采样纹理,但不断出现附件不完整的错误。我不确定这是否是由于 OpenGL wiki 上提到的 NVidia 问题(将 post 发表评论,因为我没有足够的代表 post 超过 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 仍然会导致它在屏幕上绘制垃圾。 >.<

更新三:我傻了,完全流畅了。只是看起来 不好 因为我使用了丑陋的配色方案。而且我必须将所有坐标加倍,因为视口是 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
  • glBlitFramebuffer()GL_READ_FRAMEBUFFER 复制到 GL_DRAW_FRAMEBUFFER

关于代码的更多评论:

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

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

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

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