15.2. Noise Textures

The programmability offered by the OpenGL Shading Language lets us use values stored in texture memory in new and unique ways. We can precompute a noise function and save it in a 1D, 2D, or 3D texture map. We can then access this texture map (or texture maps) from within a shader. Because textures can contain up to four components, we can use a single texture map to store four octaves of noise or four completely separate noise functions.

Listing 15.1 shows a C function that generates a 3D noise texture. This function creates an RGBA texture with the first octave of noise stored in the red texture component, the second octave stored in the green texture component, the third octave stored in the blue component, and the fourth octave stored in the alpha component. Each octave has twice the frequency and half the amplitude as the previous one.

This function assumes the existence of a noise3 function that can generate 3D noise values in the range [1,1]. If you want, you can start with Perlin's C implementation (available from http://www.texturingandmodeling.com/CODE/PERLIN/PERLIN.C). John Kessenich made some small changes to this code (adding a setNoiseFrequency function) to produce noise values that wrap smoothly from one edge of the array to the other. This means we can use the texture with the wrapping mode set to GL_REPEAT, and we won't see any discontinuities in the function when it wraps. The revised version of the code is in a program from 3Dlabs called GLSLdemo, and the source code for this example program can be downloaded from the 3Dlabs Web site at http://developer.3dlabs.com.

This function computes noise values for four octaves of noise and stores them in a 3D RGBA texture of size 128 x 128 x 128. This code also assumes that each component of the texture is stored as an 8-bit integer value. The first octave has a frequency of 4 and an amplitude of 0.5. In the innermost part of the loop, we call the noise3 function to generate a noise value based on the current value of ni. The noise3 function returns a value in the range [1,1], so by adding 1, we end up with a noise value in the range [0,2]. Multiplying by our amplitude value of 0.5 gives a value in the range [0,1]. Finally, we multiply by 128 to give us an integer value in the range [0,128] that can be stored in the red component of a texture. (When accessed from within a shader, the value is a floating-point value in the range [0,0.5].

The amplitude value is cut in half and the frequency is doubled in each pass through the loop. The result is that integer values in the range [0,64] are stored in the green component of the noise texture, integer values in the range [0,32] are stored in the blue component of the noise texture, and integer values in the range [0,16] are stored in the alpha component of the texture. We generated the images in Figure 15.5 by looking at each of these channels independently after scaling the values by a constant value that allowed them to span the maximum intensity range (i.e., integer values in the range [0,255] or floating-point values in the range [0,1]).

After the values for the noise texture are computed, the texture can be provided to the graphics hardware with the code in Listing 15.2. First, we pick a texture unit and bind to it the 3D texture we've created. We set up its wrapping parameters so that the texture wraps in all three dimensions. This way, we always get a valid result for our noise function no matter what input values are used. We still have to be somewhat careful to avoid using the texture in a way that makes obvious repeating patterns. The next two lines set the texture filtering modes to linear because the default is mipmap linear and we're not using mipmap textures here. (Using a mipmap texture might be appropriate in some circumstances, but we are controlling the scaling factors from within our noise shaders, so a single texture is sufficient.) When all the parameters are set up, we can download the noise texture to the hardware by using the glTexImage3D function.

This is an excellent approach if the period of repeatability can be avoided in the final rendering. One way to avoid it is to make sure that no texture value is accessed more than once when the target object is rendered. For instance, if a 128 x 128 x 128 texture is being used and the position on the object is used as the input to the noise function, the repeatability won't be visible if the entire object fits within the texture.