How do you generate tileable Perlin noise?

by bobobobo   Last Updated August 22, 2018 15:13 PM

Related:

I'd like to generate tileable Perlin noise. I'm working from Paul Bourke's PerlinNoise*() functions, which are like this:

// alpha is the "division factor" (how much to damp subsequent octaves with (usually 2))
// beta is the factor that multiplies your "jump" into the noise (usually 2)
// n is the number of "octaves" to add in
double PerlinNoise2D(double x,double y,double alpha,double beta,int n)
{
   int i;
   double val,sum = 0;
   double p[2],scale = 1;

   p[0] = x;
   p[1] = y;
   for (i=0;i<n;i++) {
      val = noise2(p);
      sum += val / scale;
      scale *= alpha;
      p[0] *= beta;
      p[1] *= beta;
   }
   return(sum);
}

Using code like:

real val = PerlinNoise2D( x,y, 2, 2, 12 ) ; // test

return val*val*skyColor + 2*val*(1-val)*gray + (1-val)*(1-val)*cloudColor ;

Gives sky like

nontileable

Which isn't tileable.

The pixel values are 0->256 (width and height), and pixel (0,0) uses (x,y)=(0,0) and pixel (256,256) uses (x,y)=(1,1)

How can I make it tileable?



Answers 9


One simple way I can think of would be to take the output of the noise function and mirror/flip it into an image that's twice the size. It's difficult to explain so here's an image: enter image description here

Now, in this case, it's pretty obvious what you did when you look at this. I can think of two ways to (possibly :-) ) resolve this:

  1. You could take that larger image and then generate some more noise on top of it but (and I'm not sure if this is possible) focused towards the middle (so the edges stay the same). It could add the extra bit of difference that would make your brain think it's not just mirror images.

  2. (I'm also not sure if this is possible) You could try fiddling with the inputs to the noise function to generate the initial image differently. You'd have to do this by trial and error, but look for features that draw your eye when you tile/mirror it and then try and get it not to generate those.

Hope this helps.

Richard Marskell - Drackir
Richard Marskell - Drackir
February 10, 2012 03:37 AM

I had some not-bad results interpolating near the edges of the tile (edge-wrapped), but it depends on what effect you're trying to achieve and the exact noise parameters. Works great for somewhat blurry noise, not so good with spikey/fine-grained ones.

kaoD
kaoD
February 10, 2012 04:21 AM

First version of this answer was actually wrong, I've updated it

A method I used successfully is make noise domain tiled. In other words, make your base noise2() function periodical. If noise2() is periodic and beta is integer, resulting noise will have the same period as noise2().

How can we make noise2() periodic? In most implementations, this function uses some kind of lattice noise. That is, it gets random numbers at integer coordinates, and interpolates them. For example:

function InterpolatedNoise_1D(float x)

  integer_X    = int(x)
  fractional_X = x - integer_X

  v1 = SmoothedNoise1(integer_X)
  v2 = SmoothedNoise1(integer_X + 1)

  return Interpolate(v1 , v2 , fractional_X)

end function

This function can be trivially modified to become periodic with integer period. Simply add one line:

integer_X = integer_X % Period

before calculating v1 and v2. This way, values at integer coordinates will repeat every Period units, and interpolation will ensure that resulting function is smooth.

Note, however, that this only works when Period is more than 1. So, to actually use this in making seamless textures, you'd have to sample a Period x Period square, not 1x1.

Nevermind
Nevermind
February 10, 2012 06:29 AM

Here's one rather clever way that uses 4D Perlin noise.

Basically, map the X coordinate of your pixel to a 2D circle, and the Y coordinate of your pixel to a second 2D circle, and place those two circles orthogonal to each other in 4D space. The resulting texture is tileable, has no obvious distortion, and doesn't repeat in the way that a mirrored texture would.

Copy-pasting code from the article:

for x=0,bufferwidth-1,1 do
    for y=0,bufferheight-1,1 do
        local s=x/bufferwidth
        local t=y/bufferheight
        local dx=x2-x1
        local dy=y2-y1

        local nx=x1+cos(s*2*pi)*dx/(2*pi)
        local ny=y1+cos(t*2*pi)*dy/(2*pi)
        local nz=x1+sin(s*2*pi)*dx/(2*pi)
        local nw=y1+sin(t*2*pi)*dy/(2*pi)

        buffer:set(x,y,Noise4D(nx,ny,nz,nw))
    end
end
John Calsbeek
John Calsbeek
February 10, 2012 08:02 AM

Another alternative is to generate noise using libnoise libraries. You can generate noise over a theoretical infinite amount of space, seamlessly.

Take a look at the following: http://libnoise.sourceforge.net/tutorials/tutorial3.html#tile

There is also an XNA port of the above at: http://bigblackblock.com/tools/libnoisexna

If you end up using the XNA port, you can do something like this:

Perlin perlin = new Perlin();
perlin.Frequency = 0.5f;                //height
perlin.Lacunarity = 2f;                 //frequency increase between octaves
perlin.OctaveCount = 5;                 //Number of passes
perlin.Persistence = 0.45f;             //
perlin.Quality = QualityMode.High;
perlin.Seed = 8;

//Create our 2d map
Noise2D _map = new Noise2D(CHUNKSIZE_WIDTH, CHUNKSIZE_HEIGHT, perlin);

//Get a section
_map.GeneratePlanar(left, right, top, down);

GeneratePlanar is the function to call to get the sections in each direction that will connect seamlessly with the rest of the textures.

Of course, this method is more costly than simply having a single texture that can be used across multiple surfaces. If you are looking to create some random tileable textures, this may be something that interests you.

jgallant
jgallant
February 10, 2012 11:43 AM

Ok, I got it. The answer is to walk in a torus in 3D noise, generating a 2D texture out of it.

torus wraps 2 dirs

Code:

Color Sky( double x, double y, double z )
{
  // Calling PerlinNoise3( x,y,z ),
  // x, y, z _Must be_ between 0 and 1
  // for this to tile correctly
  double c=4, a=1; // torus parameters (controlling size)
  double xt = (c+a*cos(2*PI*y))*cos(2*PI*x);
  double yt = (c+a*cos(2*PI*y))*sin(2*PI*x);
  double zt = a*sin(2*PI*y);
  double val = PerlinNoise3D( xt,yt,zt, 1.5, 2, 12 ) ; // torus

  return val*val*cloudWhite + 2*val*(1-val)*gray + (1-val)*(1-val)*skyBlue ;
}

Results:

Once:

tilable sky

And tiled:

showing it tiles

bobobobo
bobobobo
February 10, 2012 21:10 PM

There's two parts to making seamlessly tileable fBm noise like this. First, you need to make the Perlin noise function itself tileable. Here's some Python code for a simple Perlin noise function that works with any period up to 256 (you can trivially extend it as much as you like by modifying the first section):

import random
import math
from PIL import Image

perm = range(256)
random.shuffle(perm)
perm += perm
dirs = [(math.cos(a * 2.0 * math.pi / 256),
         math.sin(a * 2.0 * math.pi / 256))
         for a in range(256)]

def noise(x, y, per):
    def surflet(gridX, gridY):
        distX, distY = abs(x-gridX), abs(y-gridY)
        polyX = 1 - 6*distX**5 + 15*distX**4 - 10*distX**3
        polyY = 1 - 6*distY**5 + 15*distY**4 - 10*distY**3
        hashed = perm[perm[int(gridX)%per] + int(gridY)%per]
        grad = (x-gridX)*dirs[hashed][0] + (y-gridY)*dirs[hashed][1]
        return polyX * polyY * grad
    intX, intY = int(x), int(y)
    return (surflet(intX+0, intY+0) + surflet(intX+1, intY+0) +
            surflet(intX+0, intY+1) + surflet(intX+1, intY+1))

Perlin noise is generated from a summation of little "surflets" which are the product of a randomly oriented gradient and a separable polynomial falloff function. This gives a positive region (yellow) and negative region (blue)

Kernel

The surflets have a 2x2 extent and are centered on the integer lattice points, so the value of Perlin noise at each point in space is produced by summing the surflets at the corners of the cell that it occupies.

Summation

If you make the gradient directions wrap with some period, the noise itself will then wrap seamlessly with the same period. This is why the code above takes the lattice coordinate modulo the period before hashing it through the permutation table.

The other step, is that when summing the octaves you will want to scale the period with the frequency of the octave. Essentially, you will want each octave to tile the entire just image once, rather than multiple times:

def fBm(x, y, per, octs):
    val = 0
    for o in range(octs):
        val += 0.5**o * noise(x*2**o, y*2**o, per*2**o)
    return val

Put that together and you get something like this:

size, freq, octs, data = 128, 1/32.0, 5, []
for y in range(size):
    for x in range(size):
        data.append(fBm(x*freq, y*freq, int(size*freq), octs))
im = Image.new("L", (size, size))
im.putdata(data, 128, 128)
im.save("noise.png")

Tileable fBm Noise

As you can see, this does indeed tile seamlessly:

fBm Noise, Tiled

With some small tweaking and color mapping, here's a cloud image tiled 2x2:

Clouds!

Hope this helps!

Boojum
Boojum
February 11, 2012 07:57 AM

Though there are some answers here that would work, most of them are complicated, slow and problematic.

All you really need to do is use a periodic noise generation function. That's it!

An excellent public domain implementation based on Perlin's "advanced" noise algorithm can be found here. The function you need is pnoise2. The code was written by Stefan Gustavson, who has made a pointed comment here about exactly this issue, and how others have taken the wrong approach. Listen to Gustavson, he knows what he's talking about.

Regarding the various spherical projections some here have suggested: well, they in essence work (slowly), but they also produce a 2D texture that is a flattened sphere, so that the edges would more condensed, likely producing an undesired effect. Of course, if you intend for your 2D texture to be projected onto a sphere, that's the way to go, but that's not what was being asked for.

Tal Liron
Tal Liron
March 05, 2013 05:01 AM

Here's a much much simpler way to do tiled noise:

tiling perlin noise from shadertoy code

You use a modular wrap around for each scale of the noise. These fit the edges of the area no matter what frequency scale you use. So you only have to use normal 2D noise which is a lot faster. Here is the live WebGL code which can be found at ShaderToy: https://www.shadertoy.com/view/4dlGW2

The top three functions do all the work, and fBM is passed a vector x/y in a 0.0 to 1.0 range.

// Tileable noise, for creating useful textures. By David Hoskins, Sept. 2013.
// It can be extrapolated to other types of randomised texture.

#define SHOW_TILING
#define TILES 2.0

//----------------------------------------------------------------------------------------
float Hash(in vec2 p, in float scale)
{
    // This is tiling part, adjusts with the scale...
    p = mod(p, scale);
    return fract(sin(dot(p, vec2(35.6898, 24.3563))) * 353753.373453);
}

//----------------------------------------------------------------------------------------
float Noise(in vec2 x, in float scale )
{
    x *= scale;

    vec2 p = floor(x);
    vec2 f = fract(x);
    f = f*f*(3.0-2.0*f);
    //f = (1.0-cos(f*3.1415927)) * .5;
    float res = mix(mix(Hash(p,                  scale),
        Hash(p + vec2(1.0, 0.0), scale), f.x),
        mix(Hash(p + vec2(0.0, 1.0), scale),
        Hash(p + vec2(1.0, 1.0), scale), f.x), f.y);
    return res;
}

//----------------------------------------------------------------------------------------
float fBm(in vec2 p)
{
    float f = 0.4;
    // Change starting scale to any integer value...
    float scale = 14.0;
    float amp = 0.55;
    for (int i = 0; i < 8; i++)
    {
        f += Noise(p, scale) * amp;
        amp *= -.65;
        // Scale must be multiplied by an integer value...
        scale *= 2.0;
    }
    return f;
}

//----------------------------------------------------------------------------------------
void main(void)
{
    vec2 uv = gl_FragCoord.xy / iResolution.xy;

#ifdef SHOW_TILING
    uv *= TILES;
#endif

    // Do the noise cloud (fractal Brownian motion)
    float bri = fBm(uv);

    bri = min(bri * bri, 1.0); // ...cranked up the contrast for no reason.
    vec3 col = vec3(bri);

#ifdef SHOW_TILING
    vec2 pixel = (TILES / iResolution.xy);
    // Flash borders...
    if (uv.x > pixel.x && uv.y > pixel.y                                        // Not first pixel
    && (fract(uv.x) < pixel.x || fract(uv.y) < pixel.y) // Is it on a border?
    && mod(iGlobalTime-2.0, 4.0) < 2.0)                 // Flash every 2 seconds
    {
        col = vec3(1.0, 1.0, 0.0);
    }
#endif
    gl_FragColor = vec4(col,1.0);
}
Krondike
Krondike
September 21, 2013 14:55 PM

Related Questions


Applying Perlin Noise to Tiles

Updated February 03, 2018 16:13 PM

Where to start with perlin noise?

Updated August 25, 2018 15:13 PM

Zooming in on procedural generated 2D terrain? (LOD)

Updated October 05, 2017 13:13 PM


Smooth 1D terrain generation

Updated April 07, 2016 08:05 AM