Hay dos partes para hacer un ruido fBm perfectamente enlosable como este. Primero, debe hacer que la función de ruido Perlin sea enlosable. Aquí hay un código de Python para una función de ruido Perlin simple que funciona con cualquier período de hasta 256 (puede ampliarlo trivialmente tanto como desee modificando la primera sección):
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))
El ruido de Perlin se genera a partir de una suma de pequeñas "surflets" que son el producto de un gradiente orientado aleatoriamente y una función de caída polinómica separable. Esto da una región positiva (amarillo) y una región negativa (azul)
Las surflets tienen una extensión de 2x2 y se centran en los puntos de la red entera, por lo que el valor del ruido de Perlin en cada punto del espacio se produce sumando las surflets en las esquinas de la celda que ocupa.
Si hace que las direcciones de degradado se envuelvan con un período, el ruido en sí se envolverá sin problemas con el mismo período. Esta es la razón por la cual el código anterior toma el módulo de coordenadas de red el período antes de pasarlo a través de la tabla de permutación.
El otro paso es que al sumar las octavas querrás escalar el período con la frecuencia de la octava. Esencialmente, querrás que cada octava encuadre toda la imagen justa una vez, en lugar de varias veces:
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
Júntalo y obtendrás algo como esto:
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 puede ver, esto realmente se combina perfectamente:
Con algunos pequeños ajustes y mapeo de colores, aquí hay una imagen de nube en mosaico 2x2:
¡Espero que esto ayude!