Prev: Adding a User Interface Next: Texture2D and Texture3D Classes

5. An Antialiased Texture

Here is another image of a sphere being textured with the Stripes2 texture. In this case, the mapping between object space and texture space has been set to make the stripes very thin.

As you can see, it looks terrible. The stripes are blocky and jagged. Clearly something is going wrong. What is happening, and what can we do to fix it?

The problem is known as aliasing, and it is the bane of every computer artist's existence. It comes from the fact that we are only evaluating the surface properties at a single, infinitely small point, and using those properties to determine the color of an entire pixel. That works fine when the surface properties only change very slowly, but at sharp edges, it creates obvious artifacts. Go back and look at the image in chapter 3. You will see that there, too, the stripes had jagged edges. The very thin stripes shown above just make the effect more obvious.

To solve this problem, we need to average the surface properties over the entire region of the surface which is contained within a single pixel. There are many possible ways of doing this. One simple solution is for the Renderer to divide the pixel into pieces, separately determine the color of each piece, and then average them all together to get the final color for the pixel. This is called supersampling, and can be a very effective way of solving aliasing problems. You can do this in the Raytracer, for example, by telling it to send out multiple rays per pixel.

Supersampling has one major disadvantage: it is slow. In most cases, it is much faster (though more complicated) if the Texture class can average itself, and return the average properties over a region of the surface.

In the case of our Stripes texture, this averaging is quite easy to do. The surface properties do not depend on y or z, so we only need to do averaging along the x direction. We determine what fraction of the pixel is in each color, then calculate the diffuse color as a weighted average of the two.

Here is the implementation of getTextureSpec() in our third version of the Stripes texture. The complete source code is in Stripes3.java.

public void getTextureSpec(TextureSpec spec, double x, double y, double z, double xsize, double ysize, double zsize, double t, float param[])
{
  double halfsize = 0.5*xsize, fract1, fract2;
  
  if (xsize == 0.0)
    fract1 = (x-Math.floor(x) < width) ? 1.0 : 0.0;
  else
    fract1 = (totalStripeWidth(x+halfsize) - totalStripeWidth(x-halfsize))/xsize;
  fract2 = 1.0-fract1;
  
  spec.diffuse.setRGB((float) (fract1*color1.getRed() + fract2*color2.getRed()),
      (float) (fract1*color1.getGreen() + fract2*color2.getGreen()),
      (float) (fract1*color1.getBlue() + fract2*color2.getBlue()));
  spec.specular.setRGB(0.0f, 0.0f, 0.0f);
  spec.transparent.setRGB(0.0f, 0.0f, 0.0f);
  spec.emissive.setRGB(0.0f, 0.0f, 0.0f);
  spec.bumpGrad.set(0.0, 0.0, 0.0);
  spec.roughness = spec.cloudiness = 0.0;
}

double totalStripeWidth(double x)
{
  double xi = Math.floor(x), xf = x-xi;
  
  if (xf < width)
    return xi*width + xf;
  return xi*width + width;
}
totalStripeWidth() calculates the total width of color1 between 0 and x. Mathematically speaking, it is the indefinite integral of a function which is equal to 1 in the color1 stripes, and 0 in the color2 stripes. We then calculate the definite integral over a region of width xsize and divide by xsize to get the total fraction of the pixel which is color1.

Here is the resulting texture. As you can see, the results are vastly improved.

Of course, this is an exceptionally simple texture. Most textures are more difficult to average analytically. In many cases it is impossible, and you will have to use various approximations. Needless to say, this makes your code much more complicated, and increases the time required to develop procedural textures.

Remember that you can always resort to supersampling to solve aliasing problems, and this sometimes is a perfectly reasonable choice. If you only plan to include a texture in one or two images and then never use it again, supersampling is probably the way to go. The extra rendering time it will require is more than offset by the time you will save in creating the texture in the first place. On the other hand, if you expect to use a texture for a large number of images, you will be far better off in the long run if you take the time to build antialiasing into it from the start. And if you have any plans to give your texture to other people, you should regard antialiasing as an absolute requirement.

Prev: Adding a User Interface Next: The Texture Class