Comment générez-vous du bruit Perlin tiled?

127

Apparenté, relié, connexe:

Je voudrais générer du bruit Perlin tiled. Je travaille à partir des fonctions de Paul Bourke PerlinNoise*() , qui sont comme ceci:

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

Utiliser un code comme:

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

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

Donne ciel comme

irrécupérable

Ce qui n'est pas tilé.

Les valeurs de pixel sont 0-> 256 (largeur et hauteur), et pixel (0,0) utilise (x, y) = (0,0) et pixel (256,256) utilise (x, y) = (1,1)

Comment puis-je le rendre tiled?

bobobobo
la source
14
Juste pour votre information, ce que vous avez là n’est pas du bruit Perlin; c'est du bruit fractal. Le bruit Perlin est probablement la fonction "noise2" générant chaque octave du bruit fractal.
Nathan Reed

Réponses:

80

Faire un bruit fBm parfaitement mélangeable comme ceci se fait en deux temps. Tout d’abord, vous devez rendre la fonction de bruit Perlin elle-même exploitable. Voici un code Python pour une fonction de bruit Perlin simple qui fonctionne avec toutes les périodes allant jusqu'à 256 (vous pouvez l'étendre trivialement autant que vous le souhaitez en modifiant la première 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))

Le bruit de Perlin est généré à partir d'une somme de petits "surflets" qui sont le produit d'un gradient orienté de manière aléatoire et d'une fonction de réduction polynomiale séparable. Cela donne une région positive (jaune) et une région négative (bleue)

Noyau

Les surflets ont une étendue de 2x2 et sont centrés sur les points du réseau entier, de sorte que la valeur du bruit Perlin à chaque point de l'espace est produite en faisant la somme des surplets aux coins de la cellule occupée.

Addition

Si vous faites en sorte que les directions du dégradé soient ajustées avec une certaine période, le bruit lui-même sera alors appliqué de manière transparente avec la même période. C'est pourquoi le code ci-dessus prend la coordonnée du réseau modulo la période avant de la hacher à travers la table de permutation.

L’autre étape est que, lors de la somme des octaves, vous souhaiterez redimensionner la période avec la fréquence de l’octave. Essentiellement, vous voudrez que chaque octave insère une seule fois l'image entière dans son intégralité, plutôt que plusieurs fois:

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

Mettez cela ensemble et vous obtenez quelque chose comme ça:

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

Bruit tamisable

Comme vous pouvez le constater, cela ne fait aucun doute:

fBm Noise, Carrelée

Avec quelques petites retouches et un mappage de couleurs, voici une image de nuage en mosaïque 2x2:

Des nuages!

J'espère que cela t'aides!

Boojum
la source
3
Je ne suis pas un gars de python, alors je demande, comment se x*2**oconvertit en C? est-ce: x*pow(2,o)ou pow(x*2,o)?
idev
7
x*pow(2, o), puisque l’exponentiation a une priorité supérieure à la multiplication.
John Calsbeek
1
quelqu'un pourrait-il convertir ceci en C? J'ai d'énormes problèmes pour comprendre ce code, car je n'ai jamais rien fait avec Python. par exemple, quelle est la avaleur? et je ne sais pas comment les fonctions sont converties en C ... j’obtiens uniquement des lignes droites en sortie.
idev
1
C’est définitivement la meilleure solution pour autant que le domaine de votre bruit soit lié à la forme de votre mosaïque. Par exemple, cela n'autorise pas les rotations arbitraires. Mais si vous n'en avez pas besoin, c'est la réponse idéale.
John Calsbeek
1
Remarque: si vous souhaitez générer une taille autre que 128, NE MODIFIEZ PAS les valeurs numériques de la ligne im.putdata(data, 128, 128). (Pour ceux qui ne sont pas familiers avec Python ou PIL: ils signifient échelle et décalage, pas la taille de l'image.)
Antti Kissaniemi
87

Voici une manière plutôt intelligente d'utiliser le bruit 4D Perlin.

Fondamentalement, mappez la coordonnée X de votre pixel sur un cercle 2D et la coordonnée Y de votre pixel sur un second cercle 2D, puis placez ces deux cercles orthogonaux l'un par rapport à l'autre dans l'espace 4D. La texture obtenue est facile à dorer, ne présente pas de distorsion évidente et ne se répète pas comme le ferait une texture en miroir.

Copier-coller du code de l'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
John Calsbeek
la source
3
C'est vraiment la bonne réponse. Ajouter des dimensions est un vieux truc de mathématicien. Olinde Rodrigues docet (Sir WR Hamilton aussi, mais un peu moins)
FxIII
@FxIII, savez-vous comment ce Noise4D () devrait être implémenté? Je voudrais essayer ceci mais je n'ai aucune idée de la façon dont ce Noise4D () devrait fonctionner.
idev
4
Vous pouvez utiliser n'importe quelle fonction de bruit 4D. Le bruit simplex serait ma recommandation. webstaff.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf
John Calsbeek 10/02/2012
2
merci john! ça marche, ma douce! personne ne l'a dit, mais: les x1, y1, x2, y2 semblent être une sorte de mise à l'échelle, la distance plus grande, le bruit détaillé. si cela aide quelqu'un.
idev
5
Notez que cela correspond topologiquement à la réponse de bobobobo: votre cartographie incorpore un tore à 2 dans un, ce qui est possible sans les distorsions métriques que vous obtenez inévitablement lorsque vous l'incorporez à ℝ³.
leftaroundabout
22

OK j'ai compris. La solution consiste à marcher dans un tore dans un bruit 3D, en générant une texture 2D.

tore enroule 2 dirs

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

Résultats:

Une fois que:

ciel carrossable

Et en mosaïque:

montrant les carreaux

bobobobo
la source
6
Cela fonctionne un peu, mais on dirait que vous obtenez beaucoup de distorsions dues à la courbure du tore.
Nathan Reed
1
vous pouvez vraiment modifier la position, mais j'aime toutes les réponses géniales / créatives à cette question. Autant de façons différentes de faire la même chose.
J'ai remarqué que vous ne souhaitiez pas utiliser les valeurs 0-1, mais 0-0.9999 ... valeurs! vous utiliseriez donc: x / width, y / height, etc. sinon les coutures ne correspondent pas (rend les bords opposés exactement identiques aux pixels). En outre, il semble que la fonction PerlinNoise3D () ait également besoin d'être limitée pour la valeur du résultat ou que certaines valeurs de pixel dépassent.
idev
@ Jonathan, savez-vous comment résoudre la distorsion?
idev
2
@idev Je pense que la solution à la distorsion consiste à utiliser la méthode 4D dans la réponse principale à cette question. ;)
Nathan Reed
16

Une façon simple de penser serait de prendre la sortie de la fonction de bruit et de l'inverser / la retourner dans une image deux fois plus grande. C'est difficile à expliquer alors voici une image: entrez la description de l'image ici

Maintenant, dans ce cas, il est assez évident que vous ayez fait cela. Je peux penser à deux façons (éventuellement:) de résoudre ce problème:

  1. Vous pouvez prendre cette image plus grande et ensuite générer un peu plus de bruit, mais (et je ne suis pas sûr que cela soit possible) focalisée vers le milieu (les bords restent donc identiques). Cela pourrait ajouter un peu plus de différence qui ferait penser à votre cerveau que ce n'est pas simplement des images en miroir.

  2. (Je ne suis pas sûr non plus que ce soit possible) Vous pouvez essayer de manipuler les entrées de la fonction de bruit pour générer l'image initiale différemment. Vous devez le faire par essais et erreurs, mais recherchez les caractéristiques qui attirent votre regard lorsque vous le dupliquez / reproduisez-le et que vous essayez ensuite de ne pas générer.

J'espère que cela t'aides.

Richard Marskell - Drackir
la source
3
Très sympa mais trop symétrique!
bobobobo
1
@bobobobo C'est ce que je pensais que les autres étapes allaient atténuer. Donc, vous pouvez générer une "base" en utilisant cette méthode, puis ajouter quelques détails supplémentaires sur l'ensemble pour donner l'impression que ce n'est pas (donc) en miroir.
Richard Marskell - Drackir
Vous commencez à avoir des schémas étranges lorsque vous faites ce genre de chose. Celui-ci en particulier ressemble un peu à un papillon. Solution facile, cependant.
Notlesh
C’était aussi ma première approche, mais il ya un problème visible ici: dl.dropbox.com/u/6620757/noise_seam.png Lorsque vous franchissez une limite de retournement, vous causez une incohérence dans la fonction de bruit en inversant instantanément la pente du une fonction. Même si vous appliquez une deuxième fonction de bruit en haut, celle-ci peut toujours être visible dans la sortie.
Jherico
Bonne idée. Cela peut être facilement fait dans un pixel shader en utilisant la fonction d' onde triangulaire :tex2d(abs(abs(uv.x)%2.0-1.0), abs(abs(uv.y)%2.0-1.0))
tigrou
10

La première version de cette réponse était en fait fausse, je l'ai mise à jour

Une méthode que j'ai utilisée avec succès est de créer un domaine de bruit en mosaïque. En d’autres termes, définissez votre noise2()fonction de base comme périodique. Si noise2()est périodique et betaentier, le bruit résultant aura la même période que noise2().

Comment pouvons-nous faire noise2()périodique? Dans la plupart des implémentations, cette fonction utilise une sorte de bruit de réseau. C'est-à-dire qu'il obtient des nombres aléatoires aux coordonnées entières et les interpole. Par exemple:

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

Cette fonction peut être modifiée de manière triviale pour devenir périodique avec une période entière. Ajoutez simplement une ligne:

integer_X = integer_X % Period

avant de calculer v1et v2. Ainsi, les valeurs aux coordonnées entières répèteront chaque unité de période et l’interpolation garantira une fonction fluide.

Notez cependant que cela ne fonctionne que lorsque Period est supérieur à 1. Donc, pour l'utiliser dans la création de textures homogènes, vous devez échantillonner un carré Period x Period, et non 1x1.

Ça ne fait rien
la source
Mais comment faites-vous noise2périodique (avec une période courte telle que 1 unité)? Je pense que c'est ce que la question se pose finalement. Le bruit Perlin standard est périodique avec une période de 256 sur chaque axe, mais vous souhaitez un bruit modifié avec une période plus courte.
Nathan Reed
@Nathan Reed Si vous appelez noise2comme suggéré, vous allez obtenir des résultats périodiques, si la fonction elle - même est périodique ou non. Parce que les arguments tournent autour de chaque unité.
Nevermind
1
Mais alors vous obtenez des coutures aux lignes de la grille, n'est-ce pas? Comme rien ne garantit que noise2 (0, 0,999) est proche de noise2 (0, 0), à moins d'avoir oublié quelque chose.
Nathan Reed
1
@ Jonathan Reed C'est un bon point. En fait, je viens de revérifier mon ancien code et il s’avère que j’ai eu tort. Je vais modifier la réponse maintenant.
Nevermind
Génial! C'est en fait une bonne réponse maintenant. +1 :)
Nathan Reed
6

Une autre alternative consiste à générer du bruit à l'aide de bibliothèques libnoise. Vous pouvez générer du bruit de manière transparente sur une quantité d'espace infinie théorique.

Jetez un oeil à ce qui suit: http://libnoise.sourceforge.net/tutorials/tutorial3.html#tile

Il existe également un port XNA de ce qui précède à l’ adresse suivante : http://bigblackblock.com/tools/libnoisexna

Si vous utilisez le port XNA, vous pouvez procéder comme suit:

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 est la fonction à appeler pour obtenir les sections dans chaque direction qui se connecteront de manière transparente au reste des textures.

Bien sûr, cette méthode est plus coûteuse que d'avoir simplement une seule texture pouvant être utilisée sur plusieurs surfaces. Si vous cherchez à créer des textures tilables au hasard, cela peut vous intéresser.

jgallant
la source
6

Bien que certaines réponses puissent fonctionner, la plupart d’entre elles sont compliquées, lentes et problématiques.

Tout ce que vous avez à faire est d’utiliser une fonction de génération de bruit périodique . C'est ça!

Une excellente implémentation du domaine public basée sur l'algorithme de bruit "avancé" de Perlin peut être trouvée ici . La fonction dont vous avez besoin est pnoise2. Le code a été écrit par Stefan Gustavson, qui a commenté ici précisément ce problème et comment d'autres ont adopté une approche erronée. Écoutez Gustavson, il sait de quoi il parle.

En ce qui concerne les diverses projections sphériques suggérées par certains ici: bien, elles fonctionnent essentiellement (lentement), mais elles produisent également une texture 2D qui est une sphère aplatie, de sorte que les arêtes se condensent davantage, produisant probablement un effet indésirable. Bien sûr, si vous prévoyez pour votre texture 2D à projeter sur une sphère, qui est le chemin à parcourir, mais ce n'est pas ce qui était demandé.

Tal Liron
la source
4

Voici un moyen beaucoup plus simple de faire du bruit en mosaïque:

bruit perlin de pavage du code shadertoy

Vous utilisez un enveloppement modulaire pour chaque échelle de bruit. Celles-ci s'adaptent aux bords de la zone, quelle que soit l'échelle de fréquence utilisée. Il suffit donc d'utiliser un bruit 2D normal, ce qui est beaucoup plus rapide. Voici le code WebGL en direct qui peut être trouvé sur ShaderToy: https://www.shadertoy.com/view/4dlGW2

Les trois fonctions principales effectuent tout le travail et fBM reçoit un vecteur x / y compris entre 0.0 et 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
la source
1
Votre lien d'image est mort. J'ai pris une meilleure hypothèse et l'ai remplacée par une capture d'écran de la sortie du code shadertoy que vous avez posté. Si ce n'est pas le cas, veuillez télécharger à nouveau l'image souhaitée directement sur le serveur Stack Exchange.
Pikalek
3

J'avais des résultats assez mauvais en interpolant près des bords de la mosaïque (bordés), mais cela dépend de l'effet que vous essayez d'obtenir et des paramètres de bruit exacts. Fonctionne très bien pour les bruits quelque peu flous, mais pas si bien avec les épis fins / fins.

KaoD
la source
0

Je vérifiais ce fil à la recherche d'une réponse à un problème similaire, puis le développeur de ce code python m'a proposé une solution propre et compacte pour générer du bruit fractal à partir du bruit perlin / simplex. Le code mis à jour est fourni dans ce numéro (fermé) et peut être repris en définissant des gradients pour le côté droit du "générateur" égaux à ceux du côté gauche (et identiques pour les côtés supérieur et inférieur), comme dans

# 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]

Cela ressemble à une solution élégante et propre, j'évite de copier le code entier ici (car ce n'est pas ma propre solution), mais il est disponible au lien indiqué ci-dessus. J'espère que cela peut être utile à quelqu'un qui cherche à produire une image 2D fractale comme celle dont j'ai besoin, sans artefacts ni distorsions.

terrain fractal carrossable

Stefano
la source