u/z, v/z, w/z: A natural way to look at interpolation in rasterizers

· Post Teenage Deepity

Introduction

This is a short article describing what I think is a natural, intuitive way to look at so called ”perspective correct” interpolation in rasterizers. I’ve never seen this presentation anywhere else, and imho it is much more intuitive than any other presentation I’ve seen. Having said that, the conclusions of this article are very old news indeed :) This article assumes you’re familiar with perspective correct interpolation, and how a triangle rasterizer works.

As you are aware, in a triangle filler that supports z-buffering, texture mapping or Gouraud shading we must interpolate 1/z, not z, along the sides of the triangle. We must also interpolate u/z and v/z along the sides of the triangle when texture-mapping, instead of interpolating u and v. The same applies to the colour values in Gouraud shading, we interpolate c/z. We can easily recover u, v, and c from these interpolations if we have, say, 1/z handy. A perfectly good justification of the above can be found at

Despite the previous link, I think a more natural approach appears in this article.

If you want to contact me about this article, use nashn ”at” tcd ”dot” ie, oh, and this article is somewhat carelessly written, so sorry for lack of clarity or mistakes.

Perspective Transformation

Rasterization occurs after perspective transformation and perspective division. We ignore what is usually called the viewport transformation here, because it just rescales coordinates. To perform rasterization correctly, we must understand how it affects the objects that we defined in the pre-perspective transformed space, i.e. what we think of as model space, or world space.

So what is the perspective transform? The function of the perspective transform is typically to transform the view-frustum into an origin centered unit cube. The first thing we want to do to accomplish this, is to make the sides of the frustum parallel. If we follow the usual convention of writing our points as (x, y, z, w) with w != 0, using the w-coordinate to project onto the points (x, y, z, 1) on the affine plane when necessary. Assuming the frustum is along the z-axis (viewer at the origin), then to make its sides parallel, we just want to perform a varying scaling along the z-axis, shrinking it more as we advance along the z-axis. This is easily accomplished, by making use of the 4th coordinate. If we have some point (x, y, z, 1), then we might try:

(x, y, z, 1) ----> (x, y, z, z)

When we homogenize, we get a flat plane (x/z, y/z, 1, 1) rather than a cuboid. The problem can obviously be overcome by setting the z-coordinate to some constant. 1 will do fine. This means we get a projective transformation that has the affect:

(x, y, z, 1) ----> (x, y, 1, z)

The remainder of the perpsective transformation is affine (i.e. translation and scaling), to get an origin centered unit cube from this cuboid. We ignore this. (Aside: the perpsective transformation of any point in 3D projective space is just (x, y, w, z))

Unshaded, non-texture-mapped rasterization

If we aren’t doing any shading or texture mapping, we now rasterize our projected triangle by first dividing by its 4th coordinate (the so called ”perspective divide”) and then linearly interpolating its coordinates along the y-axis (assuming we’re going to draw it scanline by scanline). Now, looking back at our perspective transformation of the previous section, this means we’re linearly interpolating 1/z with respect to y/z — this has come about naturally.

The usual approach I’ve seen to this explains that it is incorrect to linearly interpolate z with respect to y/z, as though this is surprising, it is absolutely not surprising, doing so would correspond to defining a perspective transform like this (assume z >= 0):

(x, y, z, 1) ----> (x, y, z^2, z) 

Which certainly does *not* transform a frustum into a cuboid. This transformation is so strange it isn’t even a projective transformation on our 3D projective space, there is no 4x4 matrix describing it.

Shaded, texture-mapped rasterization

When shading or texture-mapping a line segment (a triangle is essentially the same) we associate the values of one or more functions that are linear along the line segment in world space or model space (i.e. any affine transformation of the space we defined the line segment in) with the line-segment. We can specify the values of these functions everywhere by simply specifying the value of the function at the end-points of the line-segment.

Now, the question is, if these parameters vary linearly along the line segment in world space, how do they vary in our post-perspective transformed space? Assume that we are interpolating colour, referred to as c in world/model space. The idea is that, since after projection we use the fact that x/z varies linearly with y/z, then since both x and c vary linearly with y, and x/z varies linearly with y/z then c/z varies linearly with y/z. To prove the correctness of this properly, we basically just note that any line segment (x1, y1, z1), (x2, y2, z2) with colour c1, c2 at its end-points corresponds to a correctly drawn line segment (c1, y1, z1), (c2, y2, z2) with colours x1, x2 at its end-points.

The only slight modification to the fact that we can now essentially view colour (or texture coordinates etc) as a spatial dimension, is that we generally undo the perpsective transform on colours just before rendering. This is easily accomplished since we have 1/z handy.