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:
- static like the example, but with one or more of the intensities set to 0
- grayscale static instead of colored static
- an image in which each pixel is colored proportional to its distance from the center
- a black-and-white checkerboard
- an image in which each pixel is colored by its row xor'ed with its column
-
an undulating pattern made from
Math.sin
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.