Existem duas partes para produzir um ruído fBm perfeitamente inclinável como este. Primeiro, é necessário tornar a função de ruído Perlin inclinável. Aqui está um código Python para uma função de ruído Perlin simples que funciona com qualquer período de até 256 (você pode estendê-lo trivialmente o quanto quiser, modificando a primeira seção):
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))
O ruído Perlin é gerado a partir de um somatório de pequenas "excedentes", que são o produto de um gradiente orientado aleatoriamente e uma função polinomial de queda separável. Isso fornece uma região positiva (amarela) e uma região negativa (azul)

As sobras têm uma extensão de 2x2 e estão centradas nos pontos inteiros da rede, de modo que o valor do ruído Perlin em cada ponto no espaço é produzido pela soma das sobras nos cantos da célula que ocupa.

Se você definir as direções do gradiente com um certo período, o ruído será reduzido com o mesmo período. É por isso que o código acima leva o módulo de coordenadas da treliça o período antes de colocá-lo na tabela de permutação.
O outro passo é que, ao somar as oitavas, você desejará escalar o período com a frequência da oitava. Basicamente, você desejará que cada oitava agrupe toda a imagem apenas uma vez, e não várias vezes:
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
Coloque isso junto e você terá algo como isto:
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")

Como você pode ver, isso realmente combina perfeitamente:

Com alguns pequenos ajustes e mapeamento de cores, aqui está uma imagem de nuvem lado a lado 2x2:

Espero que isto ajude!