For this explicit mapping, I would persist with 2D Perlin noise.
To recap, 2D Perlin noise works by…
- dividing area into sq. cells, and analyzing the one cell our pattern level falls inside
- selecting a pseudorandom gradient vector at every nook of the sq. cell
(in a constant manner, so repeated samples in the identical neighborhood all agree) - interpolating the 4 gradient vectors in response to our pattern place contained in the cell
So, to get clear tiling, we have to:
-
guarantee our sampling grid is aligned with the sides of the map (ie. there’s an integer variety of sampling grid cells throughout the map vertically & horizontally)
-
select our gradient vectors in a manner that is constant throughout tile seams
After that, we will let the remainder of Perlin noise run as regular. If the inputs are constant throughout the tile seams, then the outputs might be constant too.
To get constant gradients throughout tile seams, we’ll insert a remapping step into the gradient choice course of, so every nook of our grid will get mapped to a canonical level that matches its counterpart on the opposite facet of the seam. (Notice that for the actual mapping scheme proven within the query, we additionally have to vertically flip the gradients proven in purple)
Right here I am selecting to deal with the left half of the map as canonical, and remap the top-right, proper, and bottom-right edges to match their counterparts on the left.
You’ll be able to see the impact on this animation that exhibits a single octave of Perlin noise, biking between the traditional gradient lookup (exhibits seams on the edges), our corrected wrapping (seamless), and the distinction between the 2 (highlights the place the gradients change alongside the correct fringe of the tile)
So, how can we do this?
As an example we’re given as enter a 2D floating level vector, pattern
such that 0 <= pattern.x <= 2 * top
and 0 <= pattern.y <= top
the place top
is the vertical sampling frequency of the present octave of noise (what number of grid cells we would like between the highest & backside edges of the map)
Our closest 4 corners are then initially (clockwise from the bottom-left):
a = (flooring(sampleX), flooring(sampleY))
b = ( a.x , a.y + 1 )
c = ( a.x + 1 , a.y + 1 )
d = ( a.x + 1 , a.y )
An extraordinary Perlin Noise would then look one thing like this:
// Get our pattern place throughout the cell, within the vary 0 <= x,y < 1.
Vector2 fraction = pattern - a;
// Search for our 4 pseudo-random vectors for the encircling corners.
Vector2 gradientA = GetPerlinGradient(a);
Vector2 gradientB = GetPerlinGradient(b);
Vector2 gradientC = GetPerlinGradient(c);
Vector2 gradientD = GetPerlinGradient(d);
// Compute an depth in response to every nook.
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));
// Quintic interpolation, primarily based on Inigo Quilez's code right here:
// https://iquilezles.org/articles/gradientnoise/
float2 interpolant = fraction*fraction*fraction
* (fraction * (fraction * 6 - 15) + 10);
// Interpolate the 4 intensities in response to our pattern place:
float backside = Lerp( dotA, dotD, interpolant.x);
float prime = Lerp( dotB, dotC, interpolant.x);
float center = Lerp(backside, prime, interpolant.y);
return center;
We’ll modify this by sticking a wrapping “adapter” perform instead of the GetPerlinGradient(a/b/c/d)
calls above:
Vector2 GetWrappedPerlinGradient(int2 nook, int top) {
// We'll use this to trace whether or not we're sampling a mirrored level.
float flipY = 1f;
if(nook.x >= top) {
if(nook.x == 2 * top) {
nook.x = 0;
} else if(nook.y == 0 || nook.y == top) {
nook.x -= top;
flipY = -1f;
}
}
Vector2 gradient = GetPerlinGradient(nook);
// In the event you like, you may apply an offset right here GetPerlinGradient(nook + seed);
// to generate completely different maps from the identical hash perform.
gradient.y *= flipY;
return gradient;
}
Now samples on either side of the seam will use gradients which can be suitable on the edges, ensuing within the mapping you are in search of.
This is an instance of terrain generated world map with seamless tiling utilizing this technique: