Original Post
Aaahhhh... my finals are over at last (HOORAY!!!
), and so I finally have time to continue discussing planet rendering techniques. Before I begin, I'd like to ask those of you who are not familiar with this series to read the introduction in the previous thread. You may also want to go over the rest of that thread, as the discussion in this thread will rely on the points that were brought up there (this also applies to those of you who are familiar with the series, as you may want to refresh your memory a bit).
References: Planet Rendering Part 1 - The Basics [gamedev thread] The 1st thread in this series. A Real-Time Procedural Universe, Part One - Generating Planetary Bodies [article] A Real-Time Procedural Universe, Part Two - Rendering Planetary Bodies [article] We're particularly interested in the optimization sections. Fractal Models of Natural Phenomena [article] A good overview of fractal algorithms. Perlin Noise [tutorial] Impoving Noise [paper] A few improvements to the original Perlin Noise, by Ken Perlin. Making Noise [web slideshow] The history of Perlin noise.
This thread is about generating the height data for a planet in the context of the data structures that were discussed in part 1. The general process will probably go along the following lines:
Also, does anybody here have any experience with plate tectonics models?
Anyway, this leaves us with the following issues to discuss (not necessarily in that order):
We come in peace... surrender or die! [edited by - technobot on July 23, 2003 5:01:25 PM]
References: Planet Rendering Part 1 - The Basics [gamedev thread] The 1st thread in this series. A Real-Time Procedural Universe, Part One - Generating Planetary Bodies [article] A Real-Time Procedural Universe, Part Two - Rendering Planetary Bodies [article] We're particularly interested in the optimization sections. Fractal Models of Natural Phenomena [article] A good overview of fractal algorithms. Perlin Noise [tutorial] Impoving Noise [paper] A few improvements to the original Perlin Noise, by Ken Perlin. Making Noise [web slideshow] The history of Perlin noise.
This thread is about generating the height data for a planet in the context of the data structures that were discussed in part 1. The general process will probably go along the following lines:
- Pregenerate low-detail data using a simplified plate tectonics simulation.
- Save the pregenerated data to a file. Alternatively, use a ready global elevation data file (e.g. from NASA).
- At runtime, load that file, or simply generate a preliminary low-detail GQT with your usual fractal routine.
- Identify the relevant nodes, and subdivide them to the desired level of detail, while generating mipmap patches as necessary, using a fractal routine.
- As the camera moves, split/merge GQT nodes and update the mipmap patches as necessary.
- Simplified plate tectonics simulation.
- Erosion simulatiom (also simplified - accurate models tend to be slow AFAIK).
- A good fractal function.
- Generating the mipmap patches.
- Normals calculation.
- Optimisation strategies.
- The output should be in the range -1..1 (this is standard).
- The function should be fully deterministic, meaning that the same input should always give the same output.
- The function should be as fast as possible.
- The function should give interesting, more or less realistically looking output.
function fBm(coords, num_octaves, initial_altitude=0, first_octave=0, roughness=0.5, lacunarity=(e-0.6))
begin
amplitude = power(roughness, first_octave);
frequency = power(lacunarity, first_octave);
total_amplitude = 1;
result = initial_altitude;
for (i=first_octave; i<num_octaves; i++)
begin
result += noise(coords * frequency) * amplitude;
total_amplitude += abs(amplitude);
amplitude *= roughness;
frequency *= lacunarity;
end
return (result / total_amplitude);
end
coords is a normalized coordinates vector, specifying the unit vector from the planet's center to the vertex for which we need a height value. num_octaves is the number of octaves that should be used to generate the height value. This is usually determined by the level-of-detail the vertex blongs to. initial_altitude is there to support getting the first few octaves from an external source. It is a (normalized) value in the range -1..1. first_octave is also there to support getting the first few octaves from an external source. It specifies how many octaves are represented by initial_altitude. roughness is the factor by which the amplitude is scaled from one octave to the next. The lower the value, the smoother the output. Typical values are around 0.5. Finally, lacunarity is the factor by which our samplinge rate is increased from one octave to the next, it should generally be left around the value of 2.0.
The next step is to adjust that function to the Ridged Perlin Noise and what I call "Cracked Perlin Noise". The latter is aceived by simply taking the absolute value of the noise function's return value (i.e. abs(noise(...)) ) at each octave, before multiplying it by the octave's amplitude and adding it to result. I'd like to see if this gives the nice smooth "cracks" tipical of eroded mountains. The ridged noise is then done by taking 1 minus that absolute value: result += (1 - abs(noise(coords * frequency))) * amplitude;. Hmm... or are the abs() and 1-abs() done on the sum of the octaves? I suppose we could try both...
Next, we go into actual multi-fractals by making the roughness depend on the accumulated altitude. I.e. the line amplitude *= roughness; will be replaced by: amplitude *= roughness * abs(result / total_amplitude);, and the line amplitude = power(roughness, first_octave); will be replaced by: amplitude = power(roughness * abs(initial_altitude), first_octave); (the latter is just an estimate, but it's the best we can do, I think...). This serves to smooth out the areas that are near sea level (i.e. near altitude=0).
Finally, we modulate the roughness by another fractal octave, to make sure its not exactly the same in all places with a given altitude. Our resulting function looks like this:
function MultiFractal(coords, num_octaves, initial_altitude=0, first_octave=0, roughness=0.7, lacunarity=(e-0.6))
begin
roughness *= (0.5 * noise(coords.swizzle)) + 0.5;
amplitude = power(roughness * abs(initial_altitude), first_octave);
frequency = power(lacunarity, first_octave);
total_amplitude = 1;
result = initial_altitude;
for (i=first_octave; i<num_octaves; i++)
begin
result += noise(coords * frequency) * amplitude;
total_amplitude += abs(amplitude);
amplitude *= roughness * abs(result / total_amplitude);
frequency *= lacunarity;
end
return (result / total_amplitude);
end
We swizzle (i.e. shuffle, e.g. from xyz to zxy) the coordinates that we pass to the modulation octave, in order to make sure that the resulting noise is different from all octaves that are used for the amplitude generation. The swizzling should not be random (if it is, we loose the determinism)! We would probably want to use a slightly higher roughness values, since all the multiplications tend to reduce the roughness a little.
The last two functions can also come in the ridged and cracked forms, and it is possible to play with the different parameters to see what looks better. Does anyone have any other adjustment to try? I'll try to finish that demo ASAP, so that we can compare the different functions...
Anyone has anything to add/ask/comment/etc.?
Michael K.,
Co-designer and Graphics Programmer of "The Keepers"
We come in peace... surrender or die! [edited by - technobot on July 23, 2003 5:01:25 PM]