web-dev-qa-db-fra.com

Perlin génération de bruit pour le terrain

J'essaie d'implémenter du code source que j'ai trouvé en ligne pour générer une carte de hauteur en utilisant Perlin Noise. 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 j'obtiens des prairies vallonnées. J'ai fait un peu de lecture sur Perlin Noise (principalement ici ). En raison du code source que j'ai trouvé évidemment non écrit avec une lisibilité à l'esprit et ma faible compréhension du concept de Perlin Noise en général, je ne peux pas comprendre ce que je dois modifier dans le code (amplitude et fréquence?) Pour créer un terrain plus drastique.

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.

EDIT: Je comprends (en quelque sorte) comment fonctionne Perlin Noise, par exemple, en ce qui concerne l'amplitude et la fréquence, je me demande simplement dans quelles variables changer le code que j'ai lié ci-dessus, qui sont utilisés pour ces deux aspects.

37
ecoutu

Le bruit Perlin est entièrement contrôlé par les différentes variables que vous définissez, à savoir l'amplitude, la fréquence et la persistance. La quantité d'octaves a un peu changé, mais pas beaucoup. Dans le code que j'ai écrit dans le passé, je viens de jouer 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 besoin.

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);
}
55
Nick Banks

Un ami vient de me lier à cette question, et j'ai pensé que j'essaierais de clarifier 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 courbe de points randomisés. Le bruit de gradient (dont le bruit de Perlin est un exemple principal) crée un réseau de points de valeur 0 et donne à chacun un gradient aléatoire. Ils sont souvent confondus les uns avec les autres!

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

Deuxièmement, l'utilisation d'une troisième valeur comme graine coûte cher. Si vous voulez un terrain aléatoire, pensez à traduire votre origine à la place. Les appels 3D vont coûter plus cher que les appels 2D. Et tout ce que vous faites est d'utiliser la valeur z pour sélectionner une tranche particulière de bruit 2D.

Troisièmement, l'appel de fonction droite va retourner des valeurs assez lisses et glissantes dans l'ensemble, pas aussi escarpées que le terrain réel, car son caractère aléatoire est limité à une seule fréquence. Pour obtenir un terrain plus escarpé, une bonne technique consiste à additionner plusieurs appels qui progressent à travers l'espace de bruit à des fréquences différentes, en définissant généralement des valeurs "fractales".

Ainsi, par exemple, additionnez 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 de -1 à 1, vous devrez donc normaliser le résultat avant que les valeurs soient utiles. Je voudrais suggérer de configurer la série de facteurs (1, 1/2, 1/4, etc.) afin que vous soyez assuré de rester dans [-1, 1], ce qui peut être fait par pondération progressive en fonction du nombre "octaves" que vous utilisez. (Mais je ne sais pas si c'est vraiment le moyen le 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 "bruit turbulent" pour prendre la somme et la faire = |sum| (c'est-à-dire en utilisant la fonction abs). Cela donnera au terrain défini angular crêtes de vallée par opposition à rouler en douceur.

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

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

Stefan Gustavson a fait du très bon travail!

20
Phil Freihofner

L'amplitude contrôle la hauteur/la baisse du terrain, la fréquence de son écoulement, la fréquence la plus basse étant plus fluide.

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

5
wich

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

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

WebGL doit être activé. Au moment d'écrire ces lignes, je recommande d'utiliser Chrome pour de meilleures performances.

2
oyophant