Curves should be rendered in curve space


(N.B. I was too lazy to draw diagrams, so this will probably not be too comprehensible unless you already have a pretty good idea of what I’m talking about. Sorry!)

My high-performance 2-D curve renderer, ff2, is closest in intellectual lineage to Slug. That is to say, it is purely fragment-oriented, and does not flatten; curves are evaluated per fragment. I believe this to be the best way to render vector graphics (it is work- and span-optimal and robust and admits arbitrarily good antialiasing), but I mention it because the technique family seems to be somewhat passé (e.g. Mark Kilgard’s polar stroking flattens, and apparently piet does too). Nevertheless, if you are developing a curve renderer, you may find this helpful.

One thing that confused me greatly as I was in the initial planning and design phases for ff2 was that Slug, seemingly, does a lot of extraneous work; I could not figure out why. I took a look at Pathfinder, and it seems to do the same work too. I think I’ve now figured out where this extraneous work is coming from and why I am able to avoid it.

The basic problem is as follows: we have a curve, which resides in curve-space, and a pixel, which resides in screen-space; we want to make them kiss. We could bring the pixel into curve-space, or we could bring the curve into screen-space. I argue that it is usually better to bring the pixel into curve-space. Why? Because the exact shape of the curve will be known ahead of time. The rendering equation can therefore be partially evaluated and simplified, allowing for a significant amount of work to be done up-front.

Of course, there are no free lunches. The cost here is to ‘antialiasing’. We would like to measure screenspace coverage. If, as is the fashion, we use (or approximate) a rect filter, then in screenspace, we need only to measure the coverage of a square. However, if the rendering may be subject to affine0 transformations, then this screenspace square may turn into a trapezoid in curve-space, whose coverage may be more difficult to calculate.

I offer two pieces of relief. First, affine transformations preserve relative distance. That means that distance-based antialiasing heuristics have a straightforward adaptation: find the closest point on the curve in curve-space, map back to screen-space, and find the distance there.1 Second, quality antialiasing comes at a cost. If operating in curve-space allows for the use of a higher-precision (but lower-accuracy) antialiasing method, at the same performance budget, overall visual fidelity may nevertheless be improved.


  1. (back)
    It may also be interesting to support arbitrary domain transformations—and this is one of the advantages of the fragment-oriented approach. But it’s probably reasonable to give up on perfect antialiasing at this point; affine transformations make it easy to reason about antialiasing because they preserve relative area.

  2. (back)
    Loop and Blinn have a clever trick, here: they operate in screen-space, but they take advantage of screenspace derivatives in order to compute their distance function. It’s not yet clear to me quite how this compares to the methods I’ve proposed.