Friday, December 4, 2015

c++ - How can I tile Perlin noise to more accurately represent a world map?


I'm currently working on my game. I would like to generate the world map with Perlin noise, and wrap it just like a real world map. I've found a algorithm to create a map wrapped along X and Y axis:


Procedurally Generating Wrapping World Maps


Wrapped map


The problem is that this map have no linear equator and poles. If you cross the north pole, you will end up in the south. A realistic map should be like this:


enter image description here


So when you cross the north pole you end up at the opposite longitude. So when you get an arial view of this flat map, you get something like this:


enter image description here


The problem is that I don't know how to generate a perlin noise that repeat itself like this. In the website I linked, they use a 4D Perlin noise. But I still can't figure out how to use it for my case. Here is the code used in the link:



for (var x = 0; x < Width; x++) {
for (var y = 0; y < Height; y++) {

// Noise range
float x1 = 0, x2 = 2;
float y1 = 0, y2 = 2;
float dx = x2 - x1;
float dy = y2 - y1;

// Sample noise at smaller intervals

float s = x / (float)Width;
float t = y / (float)Height;

// Calculate our 4D coordinates
float nx = x1 + Mathf.Cos (s*2*Mathf.PI) * dx/(2*Mathf.PI);
float ny = y1 + Mathf.Cos (t*2*Mathf.PI) * dy/(2*Mathf.PI);
float nz = x1 + Mathf.Sin (s*2*Mathf.PI) * dx/(2*Mathf.PI);
float nw = y1 + Mathf.Sin (t*2*Mathf.PI) * dy/(2*Mathf.PI);

float heightValue = (float)HeightMap.Get (nx, ny, nz, nw);


mapData.Data[x,y] = heightValue;
}
}

I'm currently using spherical coordinates, but the map is distorded at the poles.



Answer



For this particular mapping, I'd stick to 2D Perlin noise.


To recap, 2D Perlin noise works by...




  • dividing space into square cells, and examining the one cell our sample point falls inside

  • picking a pseudorandom gradient vector at each corner of the square cell
    (in a consistent way, so repeated samples in the same vicinity all agree)

  • interpolating the four gradient vectors according to our sample position inside the cell


Perlin noise diagram via http://www.angelcode.com/dev/perlin/perlin.html


So, to get clean tiling, we need to:




  1. ensure our sampling grid is aligned with the edges of the map (ie. there's an integer number of sampling grid cells across the map vertically & horizontally)





  2. choose our gradient vectors in a way that's consistent across tile seams




After that, we can let the rest of Perlin noise run as normal. If the inputs are consistent across the tile seams, then the outputs will be consistent too.


To get consistent gradients across tile seams, we'll insert a remapping step into the gradient selection process, so each corner of our grid gets mapped to a canonical point that matches its counterpart on the other side of the seam. (Note that for the particular mapping scheme shown in the question, we also need to vertically flip the gradients shown in red)


Diagram of remapping scheme


Here I'm choosing to treat the left half of the map as canonical, and remap the top-right, right, and bottom-right edges to match their counterparts on the left.


You can see the effect in this animation that shows a single octave of Perlin noise, cycling between the normal gradient lookup (shows seams at the edges), our corrected wrapping (seamless), and the difference between the two (highlights where the gradients change along the right edge of the tile)



Animated comparison of noise with & without wrapping


So, how do we do that?


Let's say we're given as input a 2D floating point vector, sample, such that 0 <= sample.x <= 2 * height, and 0 <= sample.y <= height, where height is the vertical sampling frequency of the current octave of noise (how many grid cells we want between the top & bottom edges of the map)


Our closest four corners are then initially (clockwise from the bottom-left):


a = (floor(sampleX), floor(sampleY))
b = ( a.x , a.y + 1 )
c = ( a.x + 1 , a.y + 1 )
d = ( a.x + 1 , a.y )

An ordinary Perlin Noise would then look something like this:



// Get our sample position within the cell, in the range 0 <= x,y < 1.
Vector2 fraction = sample - a;

// Look up our four pseudo-random vectors for the surrounding corners.
Vector2 gradientA = GetPerlinGradient(a);
Vector2 gradientB = GetPerlinGradient(b);
Vector2 gradientC = GetPerlinGradient(c);
Vector2 gradientD = GetPerlinGradient(d);

// Compute an intensity according to each corner.

float dotA = Dot(gradientA, fraction);
float dotB = Dot(gradientB, fraction - Vector2(0, 1));
float dotC = Dot(gradientC, fraction - Vector2(1, 1));
float dotD = Dot(gradientD, fraction - Vector2(1, 0));

// Interpolate the four intensities according to our sample position:
float bottom = Interpolate( dotA, dotD, sample.x);
float top = Interpolate( dotB, dotC, sample.x);
float middle = Interpolate(bottom, top, sample.y);


return middle;

We're going to modify this by sticking a wrapping "adapter" function in place of the GetPerlinGradient(a/b/c/d) calls above:


Vector2 GetWrappedPerlinGradient(int2 corner, int height) {

// We'll use this to track whether we're sampling a mirrored point.
float flipY = 1f;

if(corner.x >= height) {
if(corner.x == 2 * height) {

corner.x = 0;
} else if(corner.y == 0 || corner.y == height) {
corner.x -= height;
flipY = -1f;
}
}

Vector2 gradient = GetPerlinGradient(corner);
// If you like, you can apply an offset here GetPerlinGradient(corner + seed);
// to generate different maps from the same hash function.


gradient.y *= flipY;
return gradient;
}

Now samples on both sides of the seam will use gradients that are compatible at the edges, resulting in the mapping you're looking for.


Here's an example of terrain generated world map with seamless tiling using this method:


Example world map


No comments:

Post a Comment

Simple past, Present perfect Past perfect

Can you tell me which form of the following sentences is the correct one please? Imagine two friends discussing the gym... I was in a good s...