¿Cómo se genera ruido de Perlin enlosable?

127

Relacionado:

Me gustaría generar ruido de Perlin enlosable. Estoy trabajando desde las PerlinNoise*() funciones de Paul Bourke , que son así:

// 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);
}

Usando código como:

real val = PerlinNoise2D( x,y, 2, 2, 12 ) ; // test

return val*val*skyColor + 2*val*(1-val)*gray + (1-val)*(1-val)*cloudColor ;

Da cielo como

no enlosables

Lo cual no es enlosables.

Los valores de píxel son 0-> 256 (ancho y alto), y el píxel (0,0) usa (x, y) = (0,0) y el píxel (256,256) usa (x, y) = (1,1)

¿Cómo puedo hacerlo enlosable?

bobobobo
fuente
14
Solo para tu información, lo que tienes allí no es ruido de Perlin; Es ruido fractal. Es probable que el ruido de Perlin sea la función "noise2" que genera cada octava del ruido fractal.
Nathan Reed

Respuestas:

80

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)

Núcleo

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.

Suma

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

Ruido enlosable fBm

Como puede ver, esto realmente se combina perfectamente:

Ruido fBm, mosaico

Con algunos pequeños ajustes y mapeo de colores, aquí hay una imagen de nube en mosaico 2x2:

Nubes

¡Espero que esto ayude!

Boojum
fuente
3
No soy un tipo de Python, así que pregunto, ¿cómo se x*2**oconvierte a C? es: x*pow(2,o)o pow(x*2,o)?
idev
77
x*pow(2, o), ya que la exponenciación tiene mayor precedencia que la multiplicación.
John Calsbeek
1
¿Alguien podría convertir esto a C? Tengo grandes problemas para entender este código, ya que nunca he hecho nada con Python. por ejemplo, ¿qué es el avalor? y no estoy seguro de cómo las funciones se convierten a C ... obtengo líneas rectas solo en la salida.
idev
1
Esta es definitivamente la mejor solución siempre que esté bien con el dominio de su ruido vinculado a la forma de su mosaico. Por ejemplo, esto no permite rotaciones arbitrarias. Pero si no necesita tal cosa, esta es la respuesta ideal.
John Calsbeek
1
Nota: si desea generar otro tamaño que no sea 128, NO cambie los valores numéricos en la línea im.putdata(data, 128, 128). (Para aquellos que no están familiarizados con Python o PIL: significan escala y desplazamiento, no tamaño de imagen.)
Antti Kissaniemi
87

Aquí hay una forma bastante inteligente que utiliza el ruido 4D Perlin.

Básicamente, asigne la coordenada X de su píxel a un círculo 2D, y la coordenada Y de su píxel a un segundo círculo 2D, y coloque esos dos círculos ortogonales entre sí en el espacio 4D. La textura resultante es enlosable, no tiene una distorsión obvia y no se repite como lo haría una textura reflejada.

Copiar y pegar código del artículo:

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
John Calsbeek
fuente
3
Esta es definitivamente la respuesta correcta. Agregar dimensiones es un viejo truco matemático. Olinde Rodrigues docet (Sir WR Hamilton docet también pero un poco menos)
FxIII
@FxIII, ¿sabes cómo se debe implementar Noise4D ()? Me gustaría probar esto, pero no tengo idea de cómo debería funcionar este Noise4D ().
idev
44
Puede usar cualquier función de ruido 4D. El ruido simple sería mi recomendación. webstaff.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf
John Calsbeek
2
gracias john! Lo tengo funcionando, dulce! nadie lo dijo, pero: x1, y1, x2, y2 parece ser algún tipo de escala, la distancia más grande, el ruido detallado. si esto ayuda a alguien
idev
55
Tenga en cuenta que esto es topológicamente equivalente a la respuesta de bobobobo: su mapeo incrusta un 2-toro en un ℝ⁴, lo cual es posible sin las distorsiones métricas que se obtienen de forma inevitable al incrustarlo en ℝ³.
Leftaroundabout
22

Ok, lo tengo La respuesta es caminar en un toro en ruido 3D, generando una textura 2D a partir de él.

torus envuelve 2 dirs

Código:

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 ;
}

Resultados:

Una vez:

cielo disponible

Y azulejos:

mostrándole azulejos

bobobobo
fuente
66
Funciona un poco, pero parece que estás obteniendo un montón de distorsión debido a la curvatura del toro.
Nathan Reed
1
Realmente puedes modular la posición, pero me encantan todas las respuestas increíbles / creativas a esta pregunta. Tantas formas diferentes de hacer lo mismo.
Me di cuenta de que en realidad no quieres usar valores 0-1, pero 0-0.9999 ... ¡valores! entonces usaría: x / ancho, y / altura, etc. de lo contrario las costuras no coinciden (hace que los bordes opuestos tengan exactamente los mismos píxeles). También parece que la función PerlinNoise3D () también necesita sujeción para el valor del resultado, o algunos valores de píxeles se desbordan.
idev
@Nathan, ¿sabes cómo solucionar la distorsión?
idev
2
@idev Creo que la forma de corregir la distorsión es usar el método 4D en la respuesta superior de esta pregunta. ;)
Nathan Reed
16

Una forma simple en la que puedo pensar sería tomar la salida de la función de ruido y reflejarla / voltearla en una imagen que sea dos veces el tamaño. Es difícil de explicar, así que aquí hay una imagen: ingrese la descripción de la imagen aquí

Ahora, en este caso, es bastante obvio lo que hiciste al mirar esto. Se me ocurren dos formas de (posiblemente :-)) resolver esto:

  1. Podrías tomar esa imagen más grande y luego generar algo más de ruido encima, pero (y no estoy seguro de si esto es posible) enfocado hacia el medio (para que los bordes permanezcan igual). Podría agregar la diferencia adicional que haría que su cerebro piense que no se trata solo de imágenes especulares.

  2. (Tampoco estoy seguro de si esto es posible) Podría intentar jugar con las entradas a la función de ruido para generar la imagen inicial de manera diferente. Tendría que hacer esto por prueba y error, pero busque características que le llamen la atención cuando lo coloque en mosaico / espejo y luego intente que no se generen.

Espero que esto ayude.

Richard Marskell - Drackir
fuente
3
¡Muy bonito pero demasiado simétrico!
bobobobo
1
@bobobobo Eso es lo que estaba pensando que los otros pasos aliviarían. Por lo tanto, podría generar una "base" utilizando este método, y luego agregar algunos detalles más sobre todo para que parezca que no está (tan) reflejado.
Richard Marskell - Drackir
Comienzas a tener algunos patrones extraños cuando haces este tipo de cosas. Este en particular se parece a una mariposa. Solución fácil, sin embargo.
notlesh
Este fue mi primer enfoque también, pero tiene un problema, visible aquí: dl.dropbox.com/u/6620757/noise_seam.png Cuando cruzas un límite invertido , provocas una disyunción en la función de ruido al invertir instantáneamente la pendiente del función. Incluso si aplica una segunda función de ruido en la parte superior, aún puede verse en la salida.
Jherico
Gran idea. Esto se puede hacer fácilmente en un sombreador de píxeles utilizando la función de onda triangular :tex2d(abs(abs(uv.x)%2.0-1.0), abs(abs(uv.y)%2.0-1.0))
tigrou
10

La primera versión de esta respuesta fue realmente incorrecta, la actualicé

Un método que utilicé con éxito es hacer mosaico del dominio de ruido . En otras palabras, haga que su base noise2()funcione periódicamente. Si noise2()es periódico y betaes entero, el ruido resultante tendrá el mismo período que noise2().

¿Cómo podemos hacer noise2()periódicos? En la mayoría de las implementaciones, esta función utiliza algún tipo de ruido de red. Es decir, obtiene números aleatorios en coordenadas enteras y las interpola. Por ejemplo:

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

Esta función se puede modificar trivialmente para que sea periódica con un período entero. Simplemente agregue una línea:

integer_X = integer_X % Period

antes de calcular v1y v2. De esta manera, los valores en coordenadas enteras se repetirán en todas las unidades del período, y la interpolación asegurará que la función resultante sea fluida.

Sin embargo, tenga en cuenta que esto solo funciona cuando el Período es mayor que 1. Por lo tanto, para usar esto realmente en la creación de texturas perfectas, tendría que muestrear un cuadrado Período x Período, no 1x1.

No importa
fuente
Pero, ¿cómo se hace noise2periódico (con un período corto como 1 unidad)? Creo que eso es lo que finalmente se hace la pregunta. El ruido estándar de Perlin es periódico con un período de 256 en cada eje, pero desea un ruido modificado con un período más pequeño.
Nathan Reed
@ Nathan Reed si se llama noise2como se sugiere, que va a obtener resultados periódicos, ya sea la propia función es periódica o no. Porque los argumentos se envuelven alrededor de cada 1 unidad.
importa
1
Pero luego tienes costuras en las líneas de la cuadrícula, ¿no? Dado que no hay garantía de que el ruido2 (0, 0.999) sea algo cercano al ruido2 (0, 0), a menos que me haya perdido algo.
Nathan Reed
1
@Nathan Reed Ese es un buen punto. De hecho, acabo de volver a verificar mi código anterior y resulta que estaba equivocado. Editaré la respuesta ahora.
importa
¡Excelente! Esta es realmente una buena respuesta ahora. +1 :)
Nathan Reed
6

Otra alternativa es generar ruido utilizando las bibliotecas libnoise. Puede generar ruido sobre una cantidad teórica infinita de espacio, sin problemas.

Eche un vistazo a lo siguiente: http://libnoise.sourceforge.net/tutorials/tutorial3.html#tile

También hay un puerto XNA de lo anterior en: http://bigblackblock.com/tools/libnoisexna

Si termina usando el puerto XNA, puede hacer algo como esto:

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 es la función a la que se llama para obtener las secciones en cada dirección que se conectarán sin problemas con el resto de las texturas.

Por supuesto, este método es más costoso que simplemente tener una sola textura que se puede usar en múltiples superficies. Si está buscando crear algunas texturas en mosaico al azar, esto puede ser algo que le interese.

jgallant
fuente
6

Aunque hay algunas respuestas aquí que funcionarían, la mayoría de ellas son complicadas, lentas y problemáticas.

Todo lo que realmente necesita hacer es usar una función periódica de generación de ruido. ¡Eso es!

Aquí se puede encontrar una excelente implementación de dominio público basada en el algoritmo de ruido "avanzado" de Perlin . La función que necesita es pnoise2. El código fue escrito por Stefan Gustavson, quien ha hecho un comentario puntual aquí sobre exactamente este problema y cómo otros han tomado el enfoque equivocado. Escucha a Gustavson, él sabe de lo que está hablando.

Con respecto a las diversas proyecciones esféricas, algunos aquí han sugerido: bueno, en esencia funcionan (lentamente), pero también producen una textura 2D que es una esfera aplanada, de modo que los bordes se condensarían más, probablemente produciendo un efecto no deseado. Por supuesto, si tiene la intención de que su textura 2D se proyecte en una esfera, ese es el camino a seguir, pero eso no es lo que se le pidió.

Tal Liron
fuente
4

Aquí hay una forma mucho más simple de hacer ruido en mosaico:

embaldosado perlin ruido del código sombreado

Utiliza una envoltura modular para cada escala del ruido. Estos se ajustan a los bordes del área sin importar la escala de frecuencia que use. Por lo tanto, solo tiene que usar ruido 2D normal, que es mucho más rápido. Aquí está el código WebGL en vivo que se puede encontrar en ShaderToy: https://www.shadertoy.com/view/4dlGW2

Las tres funciones principales hacen todo el trabajo, y fBM pasa un vector x / y en un rango de 0.0 a 1.0.

// 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);
}
Krondike
fuente
1
Su enlace de imagen se cortó. Adiviné mejor y lo reemplacé con una captura de pantalla de la salida del código de sombreado que publicaste. Si eso no es correcto, vuelva a cargar la imagen deseada directamente en el servidor de Stack Exchange.
Pikalek
3

Tuve algunos resultados no malos que se interpolaron cerca de los bordes del mosaico (envuelto en el borde), pero depende del efecto que intente lograr y los parámetros de ruido exactos. Funciona muy bien para el ruido algo borroso, no tan bueno con los puntiagudos / de grano fino.

kaoD
fuente
0

Estaba revisando este hilo en busca de una respuesta a un problema similar, luego obtuve una solución limpia y compacta del desarrollador de este código python para generar ruido fractal a partir del ruido perlin / simplex. El código actualizado se proporciona en este tema (cerrado) y se puede reanudar al configurar los gradientes para el lado derecho del "generador" iguales a los del lado izquierdo (e igual para arriba y abajo), como en

# Gradients
angles = 2*np.pi*np.random.rand(res[0]+1, res[1]+1)
gradients = np.dstack((np.cos(angles), np.sin(angles)))
# Make the noise tileable
gradients[-1,:] = gradients[0,:]
gradients[:,-1] = gradients[:,0]

Parece una solución elegante y limpia, evito copiar todo el código aquí (ya que no es mi propia solución), pero está disponible en el enlace anterior. Espero que esto pueda ser útil para alguien que busque producir una imagen fractal en mosaico 2D como esta que necesitaba, sin artefactos ni distorsiones.

terreno fractal enlosable

Stefano
fuente