Analysis of Gradient Line Anti-Aliasing Technique

In-depth analysis of why GRLAA is so effective

To correctly apply the appropriate level of blending, the algorithm needs to know how far it is from the edges of the road as this affects the intensity of the blending. This means that for each vertex in the data set there must be some extra vertex data appended, which must be one of two constant values: -1 or 1. The assigned value should alternate between the odd and even vertices, this means that one edge of the road will receive the base value of -1 and the other will receive the base value of 1. This data will be uploaded to the graphics hardware as vertex data for use in the fragment shader to help calculate the final alpha value.

The base values assigned to the vertices distinguish between each edge of the road i.e. left and right-hand-sides. As the vertex data is being used in the fragment shader they will be automatically interpolated by the hardware.



The interpolated base values are then used to calculate the relative distance from the current fragment to the edge of the road. For example, the centre of the road is the furthest a fragment can be from either edge, meaning its relative distance will be 1, and will effectively receive no blending. As a fragment moves closer to the edge of the road, its relative distance will approach 0 and as a result, it will gradually receive more blending.

 float distance = 1.0 – abs(roadBaseVal);
            

Now that the relative distance from the edge of the road relative to the road width is known, it needs to be determined how much of the road outline must be blended. The second part of the algorithm involves calculating what percentage of the fragments need to be blended.

The base interpolated value from the vertex data is used as a parameter to the standard GLSL partial derivative (gradient) functions. The functions ‘dFdX’ and ‘dFdY’ are typically used to calculate the rate of change of a given value in screen space along the X and Y axis respectively – this is typically determined over a small grid (2 x 2) of fragments.

This calculation gives the rate of change of the relative distance from the current fragment to the edge of the road, based on the partial derivative of the current fragment with relation to its neighbouring fragments. Based on this rate of change for any given fragment it is possible to calculate the appropriate alpha value for it. The resulting value determines the percentage of fragments that define the edges of the road, and consequently how many fragments overall will be blended in order to give a smooth outline.

#define ANTIALIAS_STRENGTH 2 // This constant affects how much AA will be applied, a higher value 
                             // will increase blurring at the cost of reduced detail, while a 
                             // lower value will reduce the amount of blurring at the cost of more aliasing. 

float blend_range = ANTIALIAS_STRENGTH * sqrt(dFdx(roadBaseVal) * dFdx(roadBaseVal) + dFdy(roadBaseVal) * dFdy(roadBaseVal));
float blend_halfrng = 0.5 * blend_range;
        

This rate of change value is very important as it enables the algorithm to be completely independent of the road’s scale and size on the screen. Using the partial derivative functions makes it possible to determine whether the object is taking up a larger or smaller percentage of the screen, for example:

  • If the object is small on the screen (‘zoomed out’) the number of pixels needed to blend in order to achieve a smooth edge equate to a larger percentage of the total number of pixels that define the road geometry. This is because the rate of change will be high, as the base values will be interpolated over a small number of fragments. As a result, it will start blending fragments which are further from the edge of the road. If a smaller percentage of fragments were blended, jaggies would start appearing.
  • Conversely, if the object is large on the screen (‘zoomed in’) then the pixels that are needed to blend are a small percentage of the total number of pixels that define the road geometry. This is because the rate of change will be low, as the base values will be interpolated over many fragments. As a result, it will start blending fragments which are closer to the edge of the road. If a larger than needed percentage of fragments were blended, the road would start to appear blurry.

With all the components in place, the final colour of the fragment can be calculated. The RGB component is calculated by performing an interpolation between the outline colour (in this case simply black) and the colour of the road (passed in via a uniform) based on the interpolant. The interpolant is derived from the relative distance and rate of change. The alpha component is calculated in a similar way, but does not require any interpolation.

#define OUTLINE_WIDTH 0.25 // How large our outline will be relative to the road width

float outline_distance = clamp(((distance – OUTLINE_WIDTH – blend_halfrng) / blend_range) + 0.5, 0.0, 1.0);
float blend_distance = clamp(((distance – blend_halfrng) / blend_range) + 0.5, 0.0, 1.0);

oColour.rgb = mix(vec3(0.0, 0.0, 0.0), roadColour.rgb, outline_distance);
oColour.a = blend_distance;