Simple and fast high quality antialiased lines with OpenGL

The standard way

glEnable(GL_LINE_SMOOTH);
glEnable(GL_BLEND);
glDepthMask(false);
glLineWidth(lw);
glDrawArrays(GL_LINE_STRIP, offset, count);
  1. Enable line smoothing by calling glEnable(GL_LINE_SMOOTH). In hardware it works by adjusting pixels’ alpha values. That’s why you have to do the next step.
  2. Enable blending with glEnable(GL_BLEND);
  3. Disable writing to the depth buffer with glDepthMask(false). When rendering lines, especially thick ones, one pixel can be drawn several times with different alpha and z values. And sometimes a more dark pixel may appear below a lighter pixel because of the Z fighting. And if the GL_DEPTHTEST is enabled (usually you want to have it be enabled) the dark pixel won’t be drawn and the final image will have a visible gap in this place. That’s why you have to disable writing to the depth buffer.
  4. Set the line width by calling glLineWidth(lw).
  5. Draw the lines.

Problems with the standard way

If you follow these steps, the quality of AA lines at least on NVidia is pretty decent, but there are some problems.

  1. The quality depends on the hardware implementation. So it might be good on NVidia, bad on Intel, you never know.
  2. Poor performance on latest NVidia GeForce cards. It might be a marketing trick to make people buy professional Quadro cards, but it is how it is, the time of rendering antialiased lines with NVidia GeForce OpenGL is dramatically longer (50-100 times maybe) than one of the alised lines.

The cool shader way

There are plenty of ways to draw lines with OpenGL. One popular approach is to draw degenerate quad strips instead of line strips, dynamically thicken them in the vertex shader and smooth in the fragment shader (e.g. http://jcgt.org/published/0002/02/08/). It’s a very flexible approach but not very simple and it requires considerably more memory for the vertex and attribute buffers compared to the standard way of drawing lines.

But there is a much simpler way. I borrowed the idea from this article http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter22.html and simplified it even further so almost nothing has to be changed in the client OpenGL code. The idea is to draw ‘fat’ aliased lines and filter them in the fragment shader to produce nice antialiased lines.

Here is the fragment shader code

uniform float uLineWidth;
uniform vec4 uColor;
uniform float uBlendFactor; //1.5..2.5
varying vec2 vLineCenter;
void main(void)
{
      vec4 col = uColor;        
      double d = length(vLineCenter-gl_FragCoord.xy);
      double w = uLineWidth;
      if (d>w)
        col.w = 0;
      else
        col.w *= pow(float((w-d)/w), uBlendFactor);
      gl_FragColor = col;
};

and the vertex shader code

uniform vec2 uViewPort; //Width and Height of the viewport
varying vec2 vLineCenter;
void main(void)
{
  vec4 pp = gl_ModelViewProjectionMatrix * gl_Vertex;
  gl_Position = pp;
  vec2 vp = uViewPort;
  vLineCenter = 0.5*(pp.xy + vec2(1, 1))*vp;
};

Filtering is made in the fragment shader by adjusting the fragment’s alpha value based on the distance to the drawn line. To calculate the distance from a fragment to the line I introduce the interpolated line center point attribute vLineCenter. I calculate it in the vertex shader by simply transforming the normalized projected vertex position pp to the viewport space. The rest of the work does the hardware rasterizer by interpolating vLineCenter between the first and the second vertexes of the line the same way it interpolates other attributes, like color (see gradient lines for example).

To calculate the fragment’s alpha value I just compare the  fragment’s distance to the line d with the current linewidth uLineWidth and apply the power function to the normalized difference between those two to achieve desired blurriness of the line.

Results

The rendering performance on my NVidia GTX 650 is on par with the performance of rendering simple aliased lines (100x speedup over the standard GeForce antialiasing). The quality of the image is decent too. No gaps, no jags, smooth lines in the range of 0.5..10 pixels width.

image00

Advertisements
Simple and fast high quality antialiased lines with OpenGL

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s