1
\$\begingroup\$

I've done a research and tried implementing for months, but I've gotten nowhere trying to make seamless/tileable Perlin noise.

Here is my current Perlin noise file without seamless implementation. In my program, I am calling FractionalBrownianMotion to generate.

Each of the research articles have given me issues. In FractionalBrownianMotion I change the call from Perlin to PerlinSeamless.

Research Articles

How to make one-axis tileable simplex noise? Produces broken noise using x2, y2 as period; xsize, ysize as map size. Also tried vice-versa.

Added code:

public float PerlinSeamless(float x, float y)
{
    int width = gradientArray.GetLength(0);
    int length = gradientArray.GetLength(1);
    
    // map size 250
    float x1 = 0, x2 = 50;
    float y1 = 0, y2 = 50;
    float dx = x2 - x1;
    float dy = y2 - y1;

    //Sample noise at smaller intervals
    float s = x / width;
    float t = y / length;

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

    return Perlin3D(nx, ny, nz);
}

public float Perlin3D(float x, float y, float z)
{
    y += 1;
    z += 2;
    float xy = perlin3DFixed(x, y);
    float xz = perlin3DFixed(x, z);
    float yz = perlin3DFixed(y, z);
    float yx = perlin3DFixed(y, x);
    float zx = perlin3DFixed(z, x);
    float zy = perlin3DFixed(z, y);
    return xy * xz * yz * yx * zx * zy;
}
private float perlin3DFixed(float a, float b)
{
    return Mathf.Sin(Mathf.PI * Perlin(a, b));
}

x2, y2 period. xsize, ysize map s

How do you generate tileable Perlin noise? hashed = perm[perm[int(gridX)%per] + int(gridY)%per] goes OOB.

3D Perlin Noise Technique does not replicate 2D noise when z value always 0, but using this method for 3D Perlin noise assuming that's the way it's supposed to be

I have also tried various implementations of non-answer accepted comments in these articles to see only broken noise.

\$\endgroup\$
1
\$\begingroup\$

To make the noise tile, all you need is to ensure that the gradient you select for integer point (x, y) is the same as the gradient point you select for (x + j*tileFrequency, y + k*tileFrequency), where tileFrequency is the number of grid cells across your tile.

(See this answer for a bit more illustration of how this gets us seamless tiling)

Inside your Perlin noise function, you'll usually have something that looks like this:

float PerlinNoise(float x, float y) {

    int xFloor = Mathf.FloorToInt(x);
    int yFloor = Mathf.FloorToInt(y);

    Vector2 g1 = GetPerlinGradient(xFloor, yFloor);
    Vector2 g2 = GetPerlinGradient(xFloor + 1, yFloor);
    Vector2 g3 = GetPerlinGradient(xFloor + 1, yFloor + 1);
    Vector2 g4 = GetPerlinGradient(xFloor, yFloor + 1);

   /...

What we need to do is wrap around those indices where we're fetching the gradients, something like this:

int Wrap(int v, int period) {
    if(v < 0)
        return (v % period) + period;
    return v % period;
}

(If coordinates don't go negative in your situation, you can skip the if — I suspect not handling this case is why you were seeing out of bounds errors)

This wrapping means one corner of our tile is always at zero, no matter how we've shifted our inputs. For a single octave that's fine, but if we want to stack octaves this can show visible correlations as the pattern of gradients near zero repeats. So we'll add some parameters to shift our window after wrapping, which also gives us more ways to vary up our noise tile based on an input seed. :)

float TiledPerlinNoise(float x, float y, int period, int xShift, int yShift) {

    int xFloor = Mathf.FloorToInt(x);
    int yFloor = Mathf.FloorToInt(y);

    int left = Wrap(xFloor, period) + xShift;
    int right = Wrap(xFloor + 1, period) + xShift;

    int bottom = Wrap(yFloor, period) + yShift;
    int top = Wrap(yFloor + 1, period) + yShift;

    Vector2 g1 = GetPerlinGradient(left, bottom);
    Vector2 g2 = GetPerlinGradient(right, bottom);
    Vector2 g3 = GetPerlinGradient(right, top);
    Vector2 g4 = GetPerlinGradient(left, top);

    // ...

Now we can use this with a collection of octave amplitudes and frequencies to generate tiling turbulence:

float TiledTurbulence(float x, float y, Octave[] octaves) {
    float sum = 0f;
    foreach (var octave in octaves) {

        sum += octave.amplitude 
               * TiledPerlinNoise(
                    x * octave.frequency,
                    y * octave.frequency,
                    octave.frequency,
                    octave.xShift,
                    octave.yShift);
    }
    return sum;
}

Here the input x & y are in "tilespace", so 1.0 is one tile width.

Note that if your periods are always powers of two, you can use a bitmask instead of the modulo operator to do your wrapping, for a little extra efficiency. :)

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.