Bezproblemowo kafelkowy hałas fBm składa się z dwóch części. Najpierw musisz ustawić funkcję szumu Perlina jako kafelkową. Oto kod Pythona dla prostej funkcji szumu Perlina, która działa z dowolnym okresem do 256 (możesz w prosty sposób rozszerzyć go tak, jak chcesz, modyfikując pierwszą sekcję):
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))
Hałas Perlina powstaje ze zsumowania małych „surfletów”, które są wynikiem losowo zorientowanego gradientu i oddzielnej funkcji wielomianu opadania. Daje to region dodatni (żółty) i region ujemny (niebieski)

Surflety mają zasięg 2x2 i są wyśrodkowane na całkowitych punktach siatki, więc wartość hałasu Perlina w każdym punkcie przestrzeni jest wytwarzana przez zsumowanie surfletów w rogach zajmowanej przez nie komórki.

Jeśli kierunki gradientu zostaną zawinięte z pewnym okresem, sam szum zostanie następnie płynnie zawinięty z tym samym okresem. Dlatego powyższy kod pobiera modulo współrzędnej sieci przed okresem mieszania przez tabelę permutacji.
Drugim krokiem jest to, że sumując oktawy, będziesz chciał skalować okres z częstotliwością oktawy. Zasadniczo będziesz chciał, aby każda oktawa ułożyła cały obraz tylko jeden raz, a nie wiele razy:
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
Złóż to razem, a otrzymasz coś takiego:
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")

Jak widać, to rzeczywiście kafelki płynnie:

Z kilkoma drobnymi poprawkami i mapowaniem kolorów, oto obraz chmury w kafelkach 2x2:

Mam nadzieję że to pomoże!