Subpixel Reconstruction Antialiasing

Matthäus G. Chajdas1,3, Morgan McGuire3,2, David Luebke3

(Scene courtesy of DICE from the Frostbite 2 game engine)

1Technische Universität München, 2Williams College, 3NVIDIA

Accepted to ACM SIGGRAPH Symposium on Interactive 3D Graphics and Games 2011

Abstract

Subpixel Reconstruction Antialiasing (SRAA) combines singlepixel (1x) shading with subpixel visibility to create antialiased images without increasing the shading cost. SRAA targets deferred shading renderers, which cannot use multisample antialiasing. SRAA operates as a post-process on a rendered image with super-resolution depth and normal buffers, so it can be incorporated into an existing renderer without modifying the shaders. In this way SRAA resembles Morphological Antialiasing (MLAA), but the new algorithm can better respect geometric boundaries and has fixed runtime independent of scene and image complexity. SRAA benefits shading-bound applications. For example, our implementation evaluates SRAA in 1.8 ms (1280x720) to yield antialiasing quality comparable to 4-16x shading. Thus SRAA would produce a net speedup over supersampling for applications that spend 1 ms or more on shading; for comparison, most modern games spend 5-10 ms shading. We also describe simplifications that increase performance by reducing quality.

Download

Bibtex

@InProceedings{CML11,
  author       = "Chajdas, Matth{\"a}us G. and Mc{G}uire, Morgan and Luebke, David",
  title        = "Subpixel Reconstruction Antialiasing",
  booktitle    = "Proceedings of the ACM SIGGRAPH Symposium on Interactive 3D Graphics and Games",
  year         = "2011",
  publisher    = "ACM Press"
}

Kernel source

float3 normal(int x, int y)
{
    return normalBuffer.Get(x, y) * 2.0 -
        make_float3(1, 1, 1);
}

float depth(int x, int y)
{
    return depthBuffer.Get(x, y);
}

float bilateral(float3 centerN, float centerZ,
float3 tapN, float tapZ)
{
    return exp(-scale * max((1.0 - dot(centerN, tapN)),
        depthScale * abs(centerZ - tapZ)));
}

// Iterate the "center" (cx, cy) of the filter
// over the samples in the pixel at (x, y)
float weights[9] = {0};
for (int cy = y; cy < (y + 2); ++cy) {
    for (int cx = x; cx < (x + 2); ++cx) {
        float3 N = normal(cx, cy);
        float Z = depth(cx, cy);
        float tmpWeights[9] = {0};
        float sum = 0.0f;

        // Iterate over the neighboring samples
        for (int j = 0; j < 3; ++j) {
            for (int i = 0; i < 3; ++i) {
                // If inside filter support
                if ((abs(i - 1 - cx) <= 1) && (abs(j - 1 - cy) <= 1)) {
                    int tapX = x + i - 1;
                    int tapY = y + j - 1;
                    // Compute the filter weight
                    float w = bilateral (N, Z,
                        normal(tapX, tapY), depth(tapX, tapY));
                    tmpWeights[i + j * 3] = w;
                    sum += w;
                }
            }
        }

        for (int t = 0; t < 9; ++t) {
            weights[t] += tmpWeights[t] / sum;
        }
    }
}

// Apply the filter
float3 result = make_float3 (0, 0, 0);
for (int j = 0; j < 3; ++j) {
    for (int i = 0; i < 3; ++i) {
        result += weights[i + j * 3] * 0.25 *
        colorBuffer.Get(x + i - 1, y + j - 1);
    }
}

Updates

  • 1.3.2011, 8:30 UTC: Added full-resolution screenshots.
  • 1.3.2011, 10:20 UTC: Updated video, re-encoded with twice the bit-rate.
  • 7.3.2011, 7:00 UTC: Added fixed kernel source. Bilateral call was incorrect.
  • 28.03.2011: 8:00 UTC: Updated video again, the 4-up part is now compressed from new source material with higher quality.
  • 23.10.2012: 8:00 UTC: Updated the paper link, should work again.