Saturday, August 9, 2025

c++ – How can I tile Perlin noise to extra precisely characterize a world map?

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

Perlin Noise Diagram via http://www.angelcode.com/dev/perlin/perlin.html

So, to get clear tiling, we have to:

  1. 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)

  2. 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)

Diagram of remapping scheme

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)

Animated comparison of noise with & without wrapping

So, how can we do this?

As an example we’re given as enter a 2D floating level vector, patternsuch that 0 <= pattern.x <= 2 * topand 0 <= pattern.y <= topthe 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:

Example world map

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Latest Articles