Interpolation

How to 3D

Chapter 2: Triangles and Transformations

Interpolation

In the last chapter we mentioned that vertex properties are interpolated across a primitive. Now that we're rasterizing triangles, interpolation becomes critical as the card fills in the many pixels between their vertices.

Suppose we give each vertex of a triangle a different color. The pixels in the middle will be given a weighted blend of the colors at the three vertices, like this:

The graphics card performs linear interpolation, which means that the blending formula is a linear equation. Suppose we are rasterizing the fragments between two endpoints on a line segment. Let's give names to the following quantities:

The interpolated value at proportion \(t\) is computed with this equation:

$$\text{interpolate}(a, b, t) = a \times (1 - t) + b \times t$$

For example, if we are 75% of the way through the segment, then we'll blend 75% of \(b\) with 25% of \(a\). If we are 10% of the way through, we'll blend 90% of \(a\) with 10% of \(b\). The nearer endpoint has a stronger influence on the blend.

When the graphics card interpolates between vertices, it is guessing what the intermediate values might be. Linear interpolation is just one guessing strategy. It is popular because it's fast and simple. However, it also produces sharp discontinuities when two interpolated ramps meet at a shared edge, as seen in this renderer:

Other interpolation strategies achieve smoother transitions by taking into account more than two neighboring values.

To get linear interpolation in a renderer, we must do the following:

This code adds a color attribute to the VBO:

const positions = [
  -0.5, -0.5, 0,  // 0
   0.5, -0.5, 0,  // 1
  -0.5,  0.5, 0,  // 2
];

const colors = [
  1, 0, 0,        // 0
  0, 1, 0,        // 1
  0, 0, 1,        // 2
];

attributes = new VertexAttributes();
attributes.addAttribute('position', 3, 3, positions);
attributes.addAttribute('color', 3, 3, colors);
const positions = [
  -0.5, -0.5, 0,  // 0
   0.5, -0.5, 0,  // 1
  -0.5,  0.5, 0,  // 2
];

const colors = [
  1, 0, 0,        // 0
  0, 1, 0,        // 1
  0, 0, 1,        // 2
];

attributes = new VertexAttributes();
attributes.addAttribute('position', 3, 3, positions);
attributes.addAttribute('color', 3, 3, colors);

The vertex shader receives the color just like it receives the position, as an in variable. The color isn't employed in any meaningful computation in the vertex shader, but it is copied to an out variable named mixColor:

in vec3 position;
in vec3 color;
out vec3 mixColor;

void main() {
  gl_Position = vec4(position, 1.0);
  mixColor = color;
}
in vec3 position;
in vec3 color;
out vec3 mixColor;

void main() {
  gl_Position = vec4(position, 1.0);
  mixColor = color;
}

There's no standard naming convention for interpolated variables, but perhaps there should be. We use the prefix mix.

The graphics card automatically performs interpolation on all of a vertex shader's out variables and assigns the blend to a fragment shader's corresponding in variables. We can use the interpolated in variables like any other variable. Here it is used to assign the fragment color:

in vec3 mixColor;
out vec4 fragmentColor;

void main() {
  fragmentColor = vec4(mixColor, 1.0);
}
in vec3 mixColor;
out vec4 fragmentColor;

void main() {
  fragmentColor = vec4(mixColor, 1.0);
}

Any numeric type can be interpolated. Once we add lighting and textures, we'll interpolate spatial and material properties. For now, we'll stick to just color.

← Indexed TrianglesUniforms →