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)

The surflets have a 2×2 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.

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")
```

As you can see, this does indeed tile seamlessly:

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

Hope this helps!

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
```

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

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:

And tiled: