Perlin Генерация шума для местности - PullRequest
37 голосов
/ 21 января 2011

Я пытаюсь реализовать некоторый исходный код, который я нашел онлайн , чтобы сгенерировать карту высот, используя Perlin Noise. Мне успешно удалось получить карту высот, используя функцию noise3, третья координата которой является случайным «семенем», чтобы можно было использовать случайные карты высот.

Моя проблема в том, что сгенерированная местность довольно скучная - я хочу горы, и я получаю катящиеся луга. Я немного читал о Perlin Noise (в основном здесь ). Из-за исходного кода, который я обнаружил явно не написанным для удобства чтения, и из-за моего слабого понимания концепции Perlin Noise в целом, я не могу понять, что мне нужно настроить в коде (амплитуду и частоту?), Чтобы создать более резкую местность.

Будет также приветствоваться дополнительная информация о создании карт высот с использованием Perlin Noise, Perlin Noise в целом или даже более расшифрованного кода.

РЕДАКТИРОВАТЬ: Я понимаю (вроде), как работает Perlin Noise, например, в отношении амплитуды и частоты, мне просто интересно, какие переменные нужно изменить в коде, который я связал выше, которые используются для этих двух аспектов.

Ответы [ 4 ]

55 голосов
/ 21 января 2011

Шум Перлина полностью контролируется различными переменными, которые вы устанавливаете, то есть амплитудой, частотой и устойчивостью. Количество октав немного изменилось, но не сильно. В коде, который я написал в прошлом, я просто играл с порядком величины частоты и стойкости, пока не получил то, что мне было нужно. Я могу при необходимости найти свой старый источник.

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);
}
20 голосов
/ 27 сентября 2012

Друг только что связал меня с этим вопросом, и я подумал, что попытаюсь прояснить пару вещей, которые не учтены в принятом ответе.

Интересная и полезная статья Элиаса использует «значение шума», а не «шум Перлина». Значение шума включает в себя подгонку кривой случайных точек. Градиентный шум (основным примером которого является шум Перлина) создает решетку из точек с 0 значениями и дает каждому случайный градиент. Их часто путают друг с другом!

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

Во-вторых, использование 3-го значения в качестве семени дорого. Если вы хотите случайный рельеф, вместо этого рассмотрите перевод случайного количества вашего происхождения. 3D-звонки будут стоить дороже, чем 2D-звонки. И все, что вы делаете, это используете значение z, чтобы выбрать определенный фрагмент 2D-шума.

В-третьих, прямой вызов функции будет возвращать значения, которые являются достаточно плавными и в целом скользящими, а не такими грубыми, как реальная местность, поскольку ее случайность ограничена одной частотой. Чтобы получить более сложный рельеф, хорошей техникой является суммирование нескольких вызовов, проходящих через шумовое пространство на разных частотах, обычно устанавливающих «фрактальные» значения.

Таким образом, например, сумма вместе noise(x, y) + (1/2)(noise(x*2, y*2) + (1/4)(noise(x*4, y*4) ...

Полученная сумма, вероятно, будет часто выходить за пределы диапазона от -1 до 1, поэтому вам придется нормализовать результат, прежде чем значения будут полезны. Я хотел бы предложить установить ряд факторов (1, 1/2, 1/4 и т. Д.), Чтобы вы гарантированно оставались в пределах [-1, 1], что может быть достигнуто путем прогрессивного взвешивания в зависимости от того, сколько «октавы» вы используете. (Но я не знаю, действительно ли это самый эффективный способ сделать это.)

Пример с четырьмя октавами: (1/15)(noise(x, y) + (2/15)(noise(2x, 2y) + (4/15)(noise(4x, 4y) + (8/15)(noise(8x, 8y)

Затем используйте нормализацию "турбулентного шума", взяв сумму и сделав ее = |sum| (т. е. используя функцию abs). Это даст рельефу местности определенные угловые гребни долины, а не плавные движения.

Я работаю над визуализатором SimplexNoise, надеюсь со временем открыть его на GitHub, как проект Java. Первый черновик визуализатора можно найти и запустить через этот пост на java-gaming.org: http://www.java -gaming.org / темы / симплекс-шумовые эксперименты с целью достижения-процессуального поколения / 27163 / view.html Акцент на первом проекте более учебный, с примерами сгенерированного кода (но они на Java).

Отличная статья о том, как работает SimplexNoise (и Perlin vs Gradient background): http://staffwww.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf

Стефан Густавсон проделал отличную работу!

5 голосов
/ 21 января 2011

Амплитуда контролирует, насколько высоко / низко местность, частоту, как она течет, а более низкая частота - больше.

Так что, если вы хотите неровный горный пейзаж, вам нужно поднять оба.

2 голосов
/ 27 августа 2013

Вот пример генерации поверхности, которую я написал недавно в JavaScript с использованием 3D Perlin Noise.Поскольку на поверхности присутствуют воксели или нет, я просто применяю порог после вычисления куба Perlin Noise.В этом примере вероятность шума одинакова для всех измерений.Вы можете получить более реалистичный ландшафт, увеличивая случайные значения до уровня земли и уменьшая его до уровня неба.

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

WebGL должен быть включен.На момент написания этой статьи я рекомендовал использовать Chrome для лучшей производительности.

...