具有多个颜色附件的多采样帧缓冲区的分辨率

Resolution of multi-sampled frame-buffer with multiple color attachments

尝试在延迟着色之上实现抗锯齿功能,我尝试使用多采样渲染缓冲区,然后使用缓冲区位块传递解析样本。

  1. 按照延迟着色的传统做法,我使用发出 3 种颜色输出的专用着色器渲染场景:

    • 职位
    • 法线
    • 漫反射和镜面反射
  2. 然后将这些用于光照计算过程,从而产生最终的场景纹理

  3. 使用简单的着色器将场景纹理渲染到全屏四边形屏幕上

您可能已经猜到了,屏幕上的 MSAA 在渲染到屏幕时不会应用于场景纹理的内容: 为了实现抗锯齿,我因此选择在步骤 1) 中使用多采样渲染缓冲区,并引入了一个额外的步骤 1.1) 来解决问题。当然多重采样只针对颜色图necessary/useful,其他2张图没有。

我的问题和问题显然是,只能为相同类型的附件定义具有多个 render-buffers/color 附件的帧缓冲区;这意味着如果一个附件是多重采样的,那么所有其他附件都必须是。

这会成为分辨率期间位置和法线缓冲区的问题,因为几何形状和照明会因抗锯齿而受到影响。

    // Create the frame buffer for deferred shading: 3 color attachments and a depth buffer
    glGenFramebuffers(1, &gBuffer);
    glBindFramebuffer(GL_FRAMEBUFFER, gBuffer);
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    {
        // - Position color buffer
        glGenRenderbuffers(1, &gPosition);
        glBindRenderbuffer(GL_RENDERBUFFER, gPosition);
        glRenderbufferStorageMultisample(GL_RENDERBUFFER, 8, GL_RGBA16F, w, h);
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, gPosition);

        // - Normal color buffer
        glGenRenderbuffers(1, &gNormal);
        glBindRenderbuffer(GL_RENDERBUFFER, gNormal);
        glRenderbufferStorageMultisample(GL_RENDERBUFFER, 8, GL_RGBA16F, w, h);
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_RENDERBUFFER, gNormal);

        // - Color + specular color buffer
        glGenRenderbuffers(1, &gColorSpec);
        glBindRenderbuffer(GL_RENDERBUFFER, gColorSpec);
        glRenderbufferStorageMultisample(GL_RENDERBUFFER, 8, GL_RGBA, w, h);
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_RENDERBUFFER, gColorSpec);

        unsigned int attachments[3] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2 };
        glDrawBuffers(3, attachments);

        // - Generate the depth buffer for rendering
        glGenRenderbuffers(1, &sceneDepth);
        glBindRenderbuffer(GL_RENDERBUFFER, sceneDepth);
        glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, w, h);
        glRenderbufferStorageMultisample(GL_RENDERBUFFER, 8, GL_DEPTH_COMPONENT, w, h);
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, sceneDepth);
    }
    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    // Create a frame buffer with 3 attachments for sample resolution
    glGenFramebuffers(1, &gFrameRes);
    glBindFramebuffer(GL_FRAMEBUFFER, gFrameRes);
    {
        glGenTextures(1, &gPositionRes);
        glBindTexture(GL_TEXTURE_2D, gPositionRes);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, w, h, 0, GL_RGB, GL_FLOAT, nullptr);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, gPositionRes, 0);

        glGenTextures(1, &gNormalRes);
        glBindTexture(GL_TEXTURE_2D, gNormalRes);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, w, h, 0, GL_RGBA, GL_FLOAT, nullptr);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, gNormalRes, 0);

        glGenTextures(1, &gColorSpecRes);
        glBindTexture(GL_TEXTURE_2D, gColorSpecRes);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_TEXTURE_2D, gColorSpecRes, 0);
    }
    glBindFramebuffer(GL_FRAMEBUFFER, 0);


    // ...
    //
    // Once the scene is rendered, resolve:
    glBindFramebuffer(GL_READ_FRAMEBUFFER, gBuffer);
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, gFrameRes);
    glReadBuffer(GL_COLOR_ATTACHMENT0);
    glDrawBuffer(GL_COLOR_ATTACHMENT0);
    glBlitFramebuffer(0, 0, sw, sh, 0, 0, sw, sh, GL_COLOR_BUFFER_BIT, GL_LINEAR);
    glReadBuffer(GL_COLOR_ATTACHMENT1);
    glDrawBuffer(GL_COLOR_ATTACHMENT1);
    glBlitFramebuffer(0, 0, sw, sh, 0, 0, sw, sh, GL_COLOR_BUFFER_BIT, GL_LINEAR);
    glReadBuffer(GL_COLOR_ATTACHMENT2);
    glDrawBuffer(GL_COLOR_ATTACHMENT2);
    glBlitFramebuffer(0, 0, sw, sh, 0, 0, sw, sh, GL_COLOR_BUFFER_BIT, GL_LINEAR);
    glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);

上面代码示例的结果是,被照亮的对象的边缘显示出不恰当的 dark/black 或 bright/white 像素的伪影,大概是因为它们的 and/or 法线位置在过程。

This becomes a problem for Positions and Normals buffers during resolution, because the geometry and lighting is affected as a result of anti-aliasing.

应该是。

位置和法线不使用多重采样在逻辑上是不一致的,而获取的 diffuse/specular 颜色是。记住什么是多重采样:每个像素都有多个样本,来自重叠三角形的不同数据可能写入同一像素中的不同样本。因此,您可以在同一像素中拥有来自两个或多个三角形的 diffuse/specular 颜色。但这也意味着你也应该有与每个 sub-pixel 颜色相关联的位置和法线。否则你的光照通行证就没有意义了;您将为未生成它们的颜色使用位置和正常值。

延迟渲染的适当多重采样昂贵。让它工作的唯一方法是对所有内容进行多重采样,然后在 per-sample 级别上执行光照传递计算。由于与 super-sampling 相比,多重采样的大部分性能增益都没有进行计算 per-sample,因此您只能在几何通道中获得多重采样(而不是超级采样)的好处,而不是在光照通道中。

这就是人们在使用延迟渲染时尽量避免多重采样的原因。这就是 pseudo-antialiasing FXAA 等技术存在的原因。