Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

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?

share|improve this question
12  
Just FYI, what you have there isn't Perlin noise; it's fractal noise. Perlin noise is likely the "noise2" function generating each octave of the fractal noise. –  Nathan Reed Feb 10 '12 at 6:49

9 Answers 9

up vote 50 down vote accepted

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!

share|improve this answer
    
im not a python guy, so i ask, how does x*2**o convert to C? is it: x*pow(2,o) or pow(x*2,o) ? –  idev Feb 11 '12 at 14:49
4  
x*pow(2, o), since exponentiation has higher precedence than multiplication. –  John Calsbeek Feb 11 '12 at 19:05
    
could someone convert this to C? i have huge problems understanding this code, as i have never done anything with python. for example what is a value? and im not sure how the functions convert to C... i get straight lines in output only. –  idev Feb 12 '12 at 15:06
1  
This is definitely the best solution so long as you're fine with the domain of your noise being tied to the shape of your tile. For example, this doesn't allow arbitrary rotations. But if you don't need any such thing, this is the ideal answer. –  John Calsbeek Feb 12 '12 at 22:49
    
@idev Stefen Gustavson implemented it as Noise1234::pnoise here –  bobobobo Jun 22 '13 at 21:04

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
share|improve this answer
3  
This is definitely the right answer. Add dimensions is an old mathematician trick. Olinde Rodrigues docet (Sir W.R. Hamilton docet too but slightly less) –  FxIII Feb 10 '12 at 15:18
    
@FxIII, do you know how this Noise4D() should be implemented? i would like to try this but i have no clue how this Noise4D() should work. –  idev Feb 10 '12 at 19:44
4  
You can use any 4D noise function. Simplex noise would be my recommendation. webstaff.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf –  John Calsbeek Feb 10 '12 at 20:20
1  
thanks john! got it working, sweet! nobody said it, but: the x1,y1,x2,y2 seems to be some sort of scaling, the larger distance, the detailed noise. if this helps anyone. –  idev Feb 10 '12 at 21:02
3  
Note that this is topologically equivalent to bobobobo's answer: your mapping embeds a 2-torus into an ℝ⁴, which is possible without the metric distortions you invevitably get when embedding it into ℝ³. –  leftaroundabout Feb 11 '12 at 13:21

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.

share|improve this answer
3  
Very nice but too symmetrical! –  bobobobo Feb 10 '12 at 18:18
1  
@bobobobo That's what I was thinking the other steps would alleviate. So, you could generate a "base" using this method, and then add some more details over the whole thing to make it look like it's not (so) mirrored. –  Richard Marskell - Drackir Feb 10 '12 at 18:29
    
You start to get some weird patterns when you do this kind of thing. This one in particular looks kind of like a butterfly. Easy solution, though. –  stephelton Feb 10 '12 at 21:04
    
This was my first approach too, but it has a problem, visible here: dl.dropbox.com/u/6620757/noise_seam.png As you cross a flip boundary you cause a disjoint in the noise function by instantly inverting the slope of the function. Even if you apply a second noise function on top, that may still be visible in the output. –  Jherico Nov 25 '12 at 22:57

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

share|improve this answer
4  
It kinda works, but it looks like you're getting a bunch of distortion due to the curvature of the torus. –  Nathan Reed Feb 10 '12 at 21:51
    
you can really just modulo the position, but I love all the awesome/creative answers to this question. So many different ways to do the same thing. –  NeuroFuzzy Feb 11 '12 at 8:57
    
i noticed you actually dont want to use 0-1 values, but 0-0.9999... values! so you would use: x/width, y/height etc. otherwise the seams doesnt match (makes the opposite edges exact same pixels). also it looks like the PerlinNoise3D() function needs clamping for the result value too, or some pixel values overflow. –  idev Feb 11 '12 at 15:52
    
@Nathan, do you know how to fix the distortion ? –  idev Feb 11 '12 at 15:53
2  
@idev I believe the way to fix the distortion is to use the 4D method in the top answer of this question. ;) –  Nathan Reed Feb 11 '12 at 16:24

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.

share|improve this answer
    
But how do you make noise2 periodic (with a short period such as 1 unit)? I think that's what the question is ultimately asking. Standard Perlin noise is periodic with a period of 256 on each axis but you want a modified noise with a smaller period. –  Nathan Reed Feb 10 '12 at 6:52
    
@Nathan Reed If you call noise2 as suggested, you will get periodic results, whether the function itself is periodic or not. Because the arguments wrap around every 1 unit. –  Nevermind Feb 10 '12 at 7:01
    
But then you get seams at the grid lines, don't you? Since there's no guarantee that noise2(0, 0.999) is anything near noise2(0, 0), unless I've missed something. –  Nathan Reed Feb 10 '12 at 17:30
    
@Nathan Reed That's a good point. In fact, I just re-checked my old code and it turns out I was wrong. I'll edit the answer now. –  Nevermind Feb 10 '12 at 18:57
    
Great! This is actually a good answer now. +1 :) –  Nathan Reed Feb 10 '12 at 19:31

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.

share|improve this answer

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.

share|improve this answer

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.

share|improve this answer

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

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);
}
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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