38 votes

Génération de bruit de Perlin pour le terrain

J'essaie d'implémenter un code source que j'ai trouvé en ligne pour générer une carte de hauteur en utilisant le bruit de Perlin. J'ai réussi à obtenir la carte de hauteur en utilisant la fonction noise3, la troisième coordonnée étant une "graine" aléatoire, pour permettre des cartes de hauteur aléatoires.

Mon problème est que le terrain généré est plutôt terne - je veux des montagnes et je n'obtiens que des prairies ondulées. J'ai fait quelques lectures sur le bruit de Perlin (principalement aquí ). Comme le code source que j'ai trouvé n'est manifestement pas écrit dans un souci de lisibilité et que je ne maîtrise pas le concept de bruit de Perlin en général, je n'arrive pas à trouver ce que je dois modifier dans le code (amplitude et fréquence ?) pour créer un terrain plus radical.

Des informations supplémentaires sur la génération de cartes de hauteur à l'aide de Perlin Noise, Perlin Noise en général, ou même un code plus déchiffrable seraient également les bienvenues.

EDITAR: Je comprends (plus ou moins) comment fonctionne le bruit de Perlin, par exemple en ce qui concerne l'amplitude et la fréquence, je me demande simplement quelles variables changer dans le code que j'ai lié ci-dessus, qui sont utilisées pour ces deux aspects.

1 votes

Le simple bruit de Perlin ("1 couche de bruit") est ennuyeux. Ce que vous recherchez est communément appelé "bruit de mouvement brownien fractal" (fBm), où vous additionnez des bruits de Perlin de différentes fréquences. Voir ici pour de grandes définitions de lacune, fréquence, octave par rapport au bruit fractal de Perlin

0 votes

Pour ceux qui viennent après : Les concepts qu'Eric essayait d'implémenter sur la base de l'article d'Elias ne sont pas réellement du bruit Perlin mais du bruit de valeur, superposé à du bruit fractal (rose).

55voto

gamernb Points 1943

Le bruit de Perlin est entièrement contrôlé par les différentes variables que vous définissez, c'est-à-dire l'amplitude, la fréquence et la persistance. Le nombre d'octaves change un peu, mais pas beaucoup. Dans le code que j'ai écrit dans le passé, j'ai simplement joué avec l'ordre de grandeur de la fréquence et de la persistance jusqu'à ce que j'obtienne ce dont j'avais besoin. Je peux essayer de retrouver mon ancienne source si nécessaire.

PerlinNoise.h

#pragma once

class PerlinNoise
{
public:

  // Constructor
    PerlinNoise();
    PerlinNoise(double _persistence, double _frequency, double _amplitude, int _octaves, int _randomseed);

  // Get Height
    double GetHeight(double x, double y) const;

  // Get
  double Persistence() const { return persistence; }
  double Frequency()   const { return frequency;   }
  double Amplitude()   const { return amplitude;   }
  int    Octaves()     const { return octaves;     }
  int    RandomSeed()  const { return randomseed;  }

  // Set
  void Set(double _persistence, double _frequency, double _amplitude, int _octaves, int _randomseed);

  void SetPersistence(double _persistence) { persistence = _persistence; }
  void SetFrequency(  double _frequency)   { frequency = _frequency;     }
  void SetAmplitude(  double _amplitude)   { amplitude = _amplitude;     }
  void SetOctaves(    int    _octaves)     { octaves = _octaves;         }
  void SetRandomSeed( int    _randomseed)  { randomseed = _randomseed;   }

private:

    double Total(double i, double j) const;
    double GetValue(double x, double y) const;
    double Interpolate(double x, double y, double a) const;
    double Noise(int x, int y) const;

    double persistence, frequency, amplitude;
    int octaves, randomseed;
};

PerlinNoise.cpp

#include "PerlinNoise.h"

PerlinNoise::PerlinNoise()
{
  persistence = 0;
  frequency = 0;
  amplitude  = 0;
  octaves = 0;
  randomseed = 0;
}

PerlinNoise::PerlinNoise(double _persistence, double _frequency, double _amplitude, int _octaves, int _randomseed)
{
  persistence = _persistence;
  frequency = _frequency;
  amplitude  = _amplitude;
  octaves = _octaves;
  randomseed = 2 + _randomseed * _randomseed;
}

void PerlinNoise::Set(double _persistence, double _frequency, double _amplitude, int _octaves, int _randomseed)
{
  persistence = _persistence;
  frequency = _frequency;
  amplitude  = _amplitude;
  octaves = _octaves;
  randomseed = 2 + _randomseed * _randomseed;
}

double PerlinNoise::GetHeight(double x, double y) const
{
  return amplitude * Total(x, y);
}

double PerlinNoise::Total(double i, double j) const
{
    //properties of one octave (changing each loop)
    double t = 0.0f;
    double _amplitude = 1;
    double freq = frequency;

    for(int k = 0; k < octaves; k++) 
    {
        t += GetValue(j * freq + randomseed, i * freq + randomseed) * _amplitude;
        _amplitude *= persistence;
        freq *= 2;
    }

    return t;
}

double PerlinNoise::GetValue(double x, double y) const
{
    int Xint = (int)x;
    int Yint = (int)y;
    double Xfrac = x - Xint;
    double Yfrac = y - Yint;

  //noise values
  double n01 = Noise(Xint-1, Yint-1);
  double n02 = Noise(Xint+1, Yint-1);
  double n03 = Noise(Xint-1, Yint+1);
  double n04 = Noise(Xint+1, Yint+1);
  double n05 = Noise(Xint-1, Yint);
  double n06 = Noise(Xint+1, Yint);
  double n07 = Noise(Xint, Yint-1);
  double n08 = Noise(Xint, Yint+1);
  double n09 = Noise(Xint, Yint);

  double n12 = Noise(Xint+2, Yint-1);
  double n14 = Noise(Xint+2, Yint+1);
  double n16 = Noise(Xint+2, Yint);

  double n23 = Noise(Xint-1, Yint+2);
  double n24 = Noise(Xint+1, Yint+2);
  double n28 = Noise(Xint, Yint+2);

  double n34 = Noise(Xint+2, Yint+2);

    //find the noise values of the four corners
    double x0y0 = 0.0625*(n01+n02+n03+n04) + 0.125*(n05+n06+n07+n08) + 0.25*(n09);  
    double x1y0 = 0.0625*(n07+n12+n08+n14) + 0.125*(n09+n16+n02+n04) + 0.25*(n06);  
    double x0y1 = 0.0625*(n05+n06+n23+n24) + 0.125*(n03+n04+n09+n28) + 0.25*(n08);  
    double x1y1 = 0.0625*(n09+n16+n28+n34) + 0.125*(n08+n14+n06+n24) + 0.25*(n04);  

    //interpolate between those values according to the x and y fractions
    double v1 = Interpolate(x0y0, x1y0, Xfrac); //interpolate in x direction (y)
    double v2 = Interpolate(x0y1, x1y1, Xfrac); //interpolate in x direction (y+1)
    double fin = Interpolate(v1, v2, Yfrac);  //interpolate in y direction

    return fin;
}

double PerlinNoise::Interpolate(double x, double y, double a) const
{
    double negA = 1.0 - a;
  double negASqr = negA * negA;
    double fac1 = 3.0 * (negASqr) - 2.0 * (negASqr * negA);
  double aSqr = a * a;
    double fac2 = 3.0 * aSqr - 2.0 * (aSqr * a);

    return x * fac1 + y * fac2; //add the weighted factors
}

double PerlinNoise::Noise(int x, int y) const
{
    int n = x + y * 57;
    n = (n << 13) ^ n;
  int t = (n * (n * n * 15731 + 789221) + 1376312589) & 0x7fffffff;
    return 1.0 - double(t) * 0.931322574615478515625e-9;/// 1073741824.0);
}

3 votes

Pour mémoire, malgré son nom, le code présenté ici est une implémentation de valeur le bruit, superposé dans fractale le bruit. Il est no Le bruit de Perlin, qui est un type de bruit de gradient ( fr.wikipedia.org/wiki/Gradient_noise ).

21voto

Phil Freihofner Points 1841

Un ami vient de me mettre en relation avec cette question, et je me suis dit que j'allais essayer d'éclaircir quelques points qui ne sont pas abordés dans la réponse acceptée.

L'article intéressant et utile d'Elias utilise le "bruit de valeur" et non le "bruit de Perlin". Le bruit de valeur implique l'ajustement de la courbe de points aléatoires. Le bruit de gradient (dont le bruit de Perlin est un exemple primaire) crée un treillis de points de valeur 0 et donne à chacun un gradient aléatoire. On les confond souvent l'un avec l'autre !

http://en.wikipedia.org/wiki/Gradient_noise

Deuxièmement, l'utilisation d'une troisième valeur comme semence est coûteuse. Si vous voulez un terrain aléatoire, envisagez plutôt de translater votre origine de façon aléatoire. Les appels 3D vont être plus coûteux que les appels 2D. Et tout ce que vous faites, c'est utiliser la valeur z pour sélectionner une tranche particulière de bruit 2D.

Troisièmement, l'appel direct à la fonction va renvoyer des valeurs assez lisses et roulantes dans l'ensemble, et non pas aussi accidentées que le terrain réel, puisque le caractère aléatoire est limité à une seule fréquence. Pour obtenir un terrain plus accidenté, une bonne technique consiste à additionner plusieurs appels qui progressent dans l'espace de bruit à des fréquences différentes, généralement définies par des valeurs "fractales".

Ainsi, par exemple, on additionne noise(x, y) + (1/2)(noise(x*2, y*2) + (1/4)(noise(x*4, y*4) ...

La somme résultante sera probablement souvent en dehors de la plage -1 à 1, vous devrez donc normaliser le résultat avant que les valeurs ne soient utiles. J'aimerais suggérer de configurer la série de facteurs (1, 1/2, 1/4, etc.) de manière à garantir que vous restiez dans la fourchette [-1, 1], ce qui peut être fait par une pondération progressive en fonction du nombre d'"octaves" que vous utilisez. (Mais je ne sais pas si c'est vraiment la manière la plus efficace de le faire).

Exemple avec quatre octaves : (1/15)(noise(x, y) + (2/15)(noise(2x, 2y) + (4/15)(noise(4x, 4y) + (8/15)(noise(8x, 8y)

Ensuite, utilisez la normalisation du "bruit turbulent" en prenant la somme et en la transformant en = |sum| (c'est-à-dire en utilisant la fonction abs). Cela donnera au terrain des crêtes de vallée anguleuses, au lieu d'être lisse et roulant.

Je travaille sur un visualiseur SimplexNoise, j'espère l'ouvrir sur GitHub, en tant que projet Java. Une première version du visualiseur peut être trouvée et exécutée via ce post à java-gaming.org : http://www.java-gaming.org/topics/simplex-noise-experiments-towards-procedural-generation/27163/view.html La première version met davantage l'accent sur le tutoriel, avec des exemples de code générés (mais ils sont en Java).

Excellent article sur le fonctionnement de SimplexNoise (et sur le fond Perlin vs Gradient) : http://staffwww.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf

Stefan Gustavson a fait un très bon travail à ce sujet !

0 votes

Le visualiseur que j'ai mentionné ci-dessus se trouve sur github.com/philfrei/SiVi. J'aimerais que certaines personnes m'aident à ajouter de nouvelles fonctionnalités, si cela vous intéresse.

5voto

wich Points 7276

L'amplitude contrôle l'intensité du terrain, la fréquence sa fluidité, les basses fréquences étant plus fluides.

Donc, si vous voulez un paysage montagneux déchiqueté, vous devez monter les deux.

0 votes

Il peut vouloir des chiffres approximatifs pour commencer - si c'est le cas : 2-6 pour la fréquence, c'est bien pour les petits terrains, octaves 1-16 c'est bien, l'ampli est juste un champ normalisé. Je génère une ville et choisis les bâtiments en fonction de celle-ci pour une grille de 32x32, et je trouve que ces gammes sont bien... freq 5+ me donne de meilleurs résultats pour ce que je fais.

2voto

oyophant Points 318

Voici un exemple de génération de surface que j'ai écrit il y a quelque temps en JavaScript en utilisant 3D Perlin Noise. Puisque dans une surface les voxels sont soit présents ou non, j'applique simplement un seuil après avoir calculé le cube de bruit de Perlin. Dans l'exemple, la probabilité du bruit est égale pour toutes les dimensions. Vous pouvez obtenir un paysage plus réaliste en augmentant les valeurs aléatoires vers le sol et en les réduisant vers le ciel.

http://kirox.de/test/Surface.html

WebGL doit être activé. Au moment de la rédaction de ce document, je recommande d'utiliser Chrome pour de meilleures performances.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X