Jump to content

  • Log In with Google      Sign In   
  • Create Account

Making a Terrain Generator

Problems...

Posted by , 31 May 2013 - - - - - - · 1,281 views

Hi! So I wrote a little bit more. And this time I have pictures! Shiny pictures! Have in mind that I might jump around a lot since its kinda hard to summarize everything and have in mind I haven't finished this project so every decision I made it might be subject to change in the future.

Also, sometimes the links look black on my end so it might be hard to distinguish them from normal text.

Problems...

One of the various things that either the paper doesn't describes properly or I just don't understand them (4 page long paper? It's a trap!) is the secondary ridge line generation.

After some steps (say, 20 advance() method calls), the ridge particles are supposed to spawn two more ridge lines with opposite directions, with half the deviation from the main one in perpendicular direction to the main ridge that spawns them.

Spawning them isn't difficult. If the main ridge direction is (X,Y), their two child ridge particles would have (-Y,X) direction and (Y,-X) direction. They would proceed just like the main ridge particles but with half (one quarter in my case) the deviation of its parent.

This is the heightmap with the main and secondary ridge lines:

Attached Image

The paper mentions "blending" the ridge lines to compute the river network. It doesn't says with what criteria, nor if it means just blending the ridge lines or blurring all the height values, to end up with an image like the paper shows. I choose to do a gaussian blur pass over the heights to blend them all together.

Blurry vision

Plain ridge lines don't look right and it isn't enough height information for the next steps, we need to spread them around:

Attached Image

I found a very nice library for image filters Jhlabs.com. It has all sort of filters, though we're interested in the blurring ones.

The Java library operates on top of BufferedImage objects. Which means first and foremost that they're very easy to use. The first blurring passes I did were made with that library by "transforming" the height array into a BufferedImage, blurring it, then bringing out the new height values again to a float array.

It worked pretty well at first but after I while I had some problems. Pixel range is quite... limited. I could go only from 0 to 255 on fixed integer steps. So no matter how detailed were my height calculations, after some integer conversions they looked quite bad.

Say that you want to have 1m resolution, so one pixel is one meter. Thats quite great, you could compute a heightmap with a 2km x 2km surface and have some fun with it in no time. But on the height side, you're limited to 0-255 values (255m tall mountain isn't the definition of scary precisely) if you decide to go for a grayscale heightmap using the previous library.

So in the end I had to knock off my own gaussian blur pass which would work with the float array directly. Turns out not only works quite well, but it also takes half the time to compute than doing a filter pass over the BufferedImage. While integer operations on pixel colors should be quite cheap, there is a lot of bitshifting around plus many integer to float conversions (and viceversa), thus the blurring pass over the float array was a better option.

Family, Ridges, and their limits

Something that isn't very clear (to me at least) is that if the ridge lines really should stop only when they reach "null" height (I guess the author meant 0.0f ?). See, if the ridges are supposed to reach 0.0f, which would be the lowest point of your heightmap, then the river lines wouldn't be able to carve their way around when you spawn them later. They'd carve their way around the mountain side, then reach 0.0f immediately after and stop since you can't carve a river line anymore.

If you start your heightmap with a middle value, say, your heights go from 0.0f to 1000.0f, and you set all the initial height values to 500.0f. As it is, the ridge lines would carve their way all down to 0.0f before stopping, that is, the mountains would go under your landscape. Which not only looks bad, but it produces plain pools of river particles later on, which isn't helpful. Though I don't discard the possibility of doing canyons this way sometime in the future, it should work with some tweaking I guess.

For now, and as you might see on the previous pictures, I decided to set up an initial height value for all the map, the ridge lines go from their highest points all down until they reach the initial height of the height map instead of the lowest point. This leaves a lot of terrain for the river particles to produce more interesting lines. I made a few helper classes to map back the height values to BufferedImages so I can visualize the results at each stage, only generating the terrain without doing this in the middle should be quite fast.

So for now the ridge particles stop on any of these 3 conditions:
  • They reached the "bottom" of the heightmap, which is an arbitrary value, one quarter of the maximum height in my case.
  • They collided with another ridge particle (first one to discover it has collided stops, the other keeps going). Collisions within the same particle aren't considered.
  • They reached the limits of the heightmap.
The child ridge line generation follows the same rules. In some rare cases child ridge particles they might collide with their parent's right after their first advance() method invocation since the child particles actually aren't "child" particles. They're normal ridge particles, thus they don't know their parent. Tragic story isn't it?

Umm...

So that does it for now Posted Image There may be some ridge particle stuff that I forgot, I'll add it up to the next journal entry if I remember something valuable. Bye! Posted Image


How It Started

Posted by , 19 May 2013 - - - - - - · 1,057 views
java, procedural terrain
Introduction

Hi! Posted Image I decided to write up what I was doing because: a. I like to write. b. Writing about I'm doing helps me to understand what I'm actually doing and c. Someone might benefit from it. So behold my random ramblings about my current project, a random (hopefully) believable terrain generator. You probably won't get any deep insight from me since I'm no expert (probably written less than 20k total lines of code in all my projects + uni assignments) and I don't promise to finish up this project sometime in the future but you might find the links/info useful Posted Image

Beginnings

So, I set aside a renderer I was working on (Java + LWJGL) because I got interested in procedurally generated stuff in general. I managed to make a random map generator thanks to Perlin's noise reference implementation (well, not exactly that one, a faster one I found somewhere which wasn't as fast as I thought it was once I measured it Posted Image) and rendering it with LWJGL.

I took many shortcuts by using Java's ImageBuffer class which simplified storing the heightmap instead of doing everything by hand (tried once to figure out JDK's PNG decoder, never went there again). But while it was fun at first, the results were lackluster in the sense that it was just a massive blob of noise rather than actually believable terrain.

The next step was trying to spice it up a bit...

Erosion

I started to research water erosion in terrain because it seemed the most sensible way to get a pretty terrain out of a random heightmap.

Found several papers, most of them revolving about placing water particles in a terrain, with mass and a direction, then simulating their movements through the terrain while calculating how much sediment they'were transporting/depositing. Here is a link to some slides filled with references Bedrich Benes - Hydraulic Erosion for Computer Graphics (this particular link sometimes works and sometimes it doesn't, here is the site where it comes from) And most of those papers cross reference each other anyway.

Seemed pretty fun but I never got around implementing any of those algorithms.

"Fractalish" Terrain Generation

After a while I found this paper Ridges and Rivers, A Bottom Up Approach (University of Paris site) which sounded not too complex and tackled the issue from another perspective. Instead of bringing random features to the "believable landscape" land, it tried to start with actually believable features (ridges and rivers) and then filling up the gaps with a fractal method (midpoint displacement, also known as the Diamond Square algorithm).

It was also interesting that according to the paper, the method could generate a 512x512 heightmap in 250ms, but made no mention about the hardware it was being used. After a while I found a cross-reference (that I can't find right now) mentioning that Belhadj and Audibert used a Pentium 4 (3,2Ghz I believe). Since P4s weren't Intel's best CPUs precisely, it sounded promising.

Rivers and Ridges, Bottom Up Approach

This method has 3 distinctive phases.

First one is generating a ridge network by just tracing ridge "particles" through a semi random path with their heights following a normal distribution. I'd say that the deviation for the normal distribution depends on the specific normal distribution function that you use (there are many). The (1/4 * heightmap_width) described in the paper looked too high in my case, I used 1/8 of the width instead.

Second one is generating a river network with particles starting at the top of the ridge lines and simulating their movement through the terrain. Movement follows what is described in this paper An Erosion Model Based on Velocity Fields, but I found it better explained in this one which is based on the same method Hydraulic Erosion of Soil and Terrain.

Third one is filling up the gaps of the terrain in two steps, one "inverse" diamond-square algorithm pass and then one regular diamond-square algorithm pass.

Starting With the Implementation, Ridge Time!

Implementing the ridge network generation wasn't as hard as I thought at first. I'll detail what isn't that obvious from what the paper described.

The ridge generation uses two functions for controlling its movement. The height is described by a normal distribution (which I know from Statistics course... which I failed Posted Image) and its lateral movement is described by a Fractional Brownian Motion.

I don't have the slightest idea what a FBM is but it kinda looked like I could make something similar by having a direction vector along which the Ridge Particle moves, and rotating it randomly in a defined angle range (ie, -30º to 30º) after a certain ammount of "steps" have been completed. Always rotating by the opposite direction of the previous rotation to (hopefully) avoid advancing in circles.

The particle would have an "advance()" method that would advance its Position by its Direction vector (normalized so it advances one length unit). A Ridge Network Generator would check the particle's length and modify its course after a certain amount of steps have been made. By "modifying its course" I mean a two dimensional direction vector, a 2x2 transformation (rotation) matrix and some matrix/vector multiplications.

Besides drawing the ridge lines on the heightmap, you also need to mind their collisions with other ridge lines. I made it so they would collide with each other if the ridge particles were different (ridges don't care to collide with themselves) as long their heights were different. So if two different ridge lines cross each other and they have the same height at that point, they continue with their paths normally.

Its important that the ridge particles don't check for collisions with themselves because I'm using floating point values for their movements, which don't map directly to a heightmap which is a regular grid of points. If somehow the ridge particle advances and it ends up in the same pixel/height vertex/etc it was before, it would collide with itself and stop immediately otherwise.

Problems...

I'll details the problems I found and how I dealt with them in the next entry. This one is large enough as it is Posted Image Bye!





May 2013 »

S M T W T F S
   1234
567891011
12131415161718
19202122232425
262728293031 

Recent Entries

Recent Comments