Generating Textures

How to 3D

Chapter 9: Textures

Generating Textures

Procedural generation means our programs generate the textures on the fly, reducing the download size of our renderers. The generation algorithms may scale the texture complexity or resolution according to the capabilities of the host computer.

When exploring heightmaps, we saw that manipulating images in the browser is clunky. An image element doesn't give us direct access to pixels, so we had to first write the image to a canvas element and then read the elevation data from it. To procedurally generate our own images, we could do the inverse: algorithmically draw on a canvas element and then populate an image element with the canvas's pixel data. This is exactly what we would do if we needed to insert the image into the webpage.

However, we want to send the image to WebGL, not the webpage. We don't have to deal with HTMLImageELement at all. WebGL accepts pixel data from more than just image elements. One alternate source is Uint8ClampedArray, which clamps its values to [0, 255] instead of just masking out the lower-order bits like Uint8Array. In this function, we create an instance of Uint8ClampedArray and draw into it a swatch of orange:

function generateRgbaImage(width: number, height: number) {
  const n = width * height * 4;
  const pixels = new Uint8ClampedArray(n);

  for (let r = 0; r < height; ++r) {
    for (let c = 0; c < width; ++c) {
      let i = (r * width + c) * 4;
      pixels[i + 0] = 255;
      pixels[i + 1] = 128;
      pixels[i + 2] = 0;
      pixels[i + 3] = 255;
    }
  }

  return pixels;
}
function generateRgbaImage(width: number, height: number) {
  const n = width * height * 4;
  const pixels = new Uint8ClampedArray(n);

  for (let r = 0; r < height; ++r) {
    for (let c = 0; c < width; ++c) {
      let i = (r * width + c) * 4;
      pixels[i + 0] = 255;
      pixels[i + 1] = 128;
      pixels[i + 2] = 0;
      pixels[i + 3] = 255;
    }
  }

  return pixels;
}

We can use any algorithm we want to decide how to color pixels. Here's an example in which we randomly generate the red, green, and blue intensities of a 256×256 image:

Try procedurally generating the following images, using the r and c values as necessary:

The image generated by this routine doesn't need to be written to a file. WebGL can load the returned Uint8ClampedArray directly.

We don't always need each pixel to an RGBA tuple. Sometimes we just need a scalar value, like in a heightmap. On such occasions we're better off generating a grayscale image. It takes up less space and has fewer elements to initialize.

← Reading TexturesTexture Setup →