Spectral Synthesis Noise for Creating Terrain
by druid- (sfranke@usc.edu) - (1/7/00)

Introduction

Everyone seems to like generating and rendering terrain. I got lots of email about my old fractal program as well as the article I wrote a couple years ago. The fractal program used the Mandlebrot set to generate the height data, so it had pretty limited usefullness. The algorithm used in this article and program is much more flexible and can generate very realistic and usable terrain.

The code in this project is all from the final project in my CSCI580 class I took Fall 1999. The project was on generating ecosystems, but this article only deals with generating terrain. The executable for the entire project is linked at the bottom of this page.

Before you go any further, go read my article on Procedural Textures if you haven't already, or if you aren't familiar with that term.

Spectral Synthesis & Procedural Functions

The spectral synthesis algorithm is taken directly out of Darwyn Peachey's chapter in Texturing & Modeling: A Procedural Approach by Ebert, Musgrave, Peachey, Perlin, and Worley. It's an exceptionally good book, I highly recommend it to anyone interested in learning about procedural techniques. The detailed description begins on p82 and I also use the spline interpolation function on p31. If you can, read that chapter over as they probably explain it better than I do as I'm trying not to plagerise too much.

Spectral Synthesis is a noise function - which means it doesn't fit any easily noticable pattern. Noise functions are extremely useful, almost anything in nature can be approximated by some combination of pattern and noise. The trick is knowing how to combine them. Consider how powerful a relatively simple function like is outlined here can be. High end & photorealistic renderers use many procedural functions for generating realistic textures and models. Pixar's Renderman rendering system is designed around taking advantage of the power of procedural functions. Besides being able to generate lots of varying patterns, procedural textures hardly require any storage space because they can be generated at run time (or on the fly) instead of being stored as a 24-bit texture on disk. Procedural textures have not been taken advantage of much in the game industry - except probably in generating textures in Adobe Photoshop, or another paint program. Now that video cards are more powerful, games can produce incredibly detailed scenes, and procedural textures and models are going to be a requirement to generate the massive amounts of data necessary for a highly detailed world.

The Algorithm

Well, I hope I've given you some appreciation for how cool this stuff is - let's take a look at the algortihm.

Spectral synthesis is just slightly more complicated than throwing a bunch of random numbers into a 2d array. The difference is in how the 2d array is treated. Instead of just treating the 2d array as the texture itself (aliased white noise), it smooths it (with a spline function in this case) to produce some sort of continuity in the data.

But that still wouldn't generate a very convincing or useful (except in some cases) texture. The real cool part comes in when you start adding multiple passes. In this way, spectral synthesis is a sort of fractal function. It iterates a function with varying parameters over an array that accumulates the values. The varying parameters in this case are wavelength and amplitude. Here's an example (forgive the low quality of the images, they're taken from the app, which wasn't made for looking at the 2d data).

ss1.jpg ss2.jpg
1 pass 2 passes

ss3.jpg ss4.jpg
3 pass 4 passes

ss5.jpg ss50.jpg
5 pass 50 passes

I only used 4 passes for my project, but you can see how well it works at higher number of passes. Now you wouldn't get these results if you just kept accumulating smoothed random noise - the higher frequency data would determine too much of the texture. What you want is for the lowest frequency pass to define the basic shape and further higher frequency passes to add detail. So there is an added scale for each pass related to the pass number. The above images were generated with a scaling factor of 0.8 per pass, while I used 0.4 in my project.

The fracSynthPass(...) function:

/* * fracSynthPass(...) * * generate basic points * interpolate along spline & scale * add to existing hbuffer */ void fracSynthPass( float *hbuf, float freq, float zscale, int xres, int zres ) { int i; int x, z; float *val; int max; float dfx, dfz; float *zknots, *knots; float xk, zk; float *hlist; float *buf; // how many to generate (need 4 extra for smooth 2d spline interpolation) max = freq + 2; // delta x and z - pixels per spline segment dfx = xres / (freq-1); dfz = zres / (freq-1); // the generated values - to be equally spread across buf val = (float*)calloc( sizeof(float)*max*max, 1 ); // intermediately calculated spline knots (for 2d) zknots = (float*)calloc( sizeof(float)*max, 1 ); // horizontal lines through knots hlist = (float*)calloc( sizeof(float)*max*xres, 1 ); // local buffer - to be added to hbuf buf = (float*)calloc( sizeof(float)*xres*zres, 1 ); // start at -dfx, -dfz - generate knots for( z=0; z < max; z++ ) { for( x=0;x < max;x++ ) { val[z*max+x] = SRANDOM; } } // interpolate horizontal lines through knots for( i=0;i < max;i++ ) { knots = &val[i*max]; xk = 0; for( x=0;x < xres;x++ ) { hlist[i*xres+x] = spline( xk/dfx, 4, knots ); xk += 1; if( xk >= dfx ) { xk -= dfx; knots++; } } } // interpolate all vertical lines for( x=0;x < xres;x++ ) { zk = 0; knots = zknots; // build knot list for( i=0;i < max;i++ ) { knots[i] = hlist[i*xres+x]; } for( z=0;z < zres;z++ ) { buf[z*xres+x] = spline( zk/dfz, 4, knots ) * zscale; zk += 1; if( zk >= dfz ) { zk -= dfz; knots++; } } } // update hbuf for( z=0;z < zres;z++ ) for( x=0;x < xres;x++ ) hbuf[z*xres+x] += buf[z*xres+x]; free( val ); free( buf ); free( hlist ); free( zknots ); }

Adding some water

What I used to add water is very simple. It interpolates a spline with 4 colinear nodes - two on two of the edges, and two outside of the area. It then offsets them by some random values to make the river a little interesting. As the spline is interpolated, the height field is displaced - like shoveling out the dirt to make a bed for the river. It also stores data in an buffer I call the water buffer. It uses this to determine if a point is underwater, and if it's not how much water is available for plants at the given point.

Conclusion

This is a more powerful way to generate terrain than my previous programs. But I also hope this article helped illustrate the power of procedural methods for generating textures and models. Remember, I barely touched on the broad capabilities of procedural methods. Play around with it.

Links

Files

Discuss this article in the forums


Date this article was posted to GameDev.net: 1/11/2000
(Note that this date does not necessarily correspond to the date the article was written)

See Also:
Landscapes and Terrain

© 1999-2011 Gamedev.net. All rights reserved. Terms of Use Privacy Policy
Comments? Questions? Feedback? Click here!