地形的 Perlin 噪声生成

发布于 2024-10-12 23:17:50 字数 552 浏览 10 评论 0原文

我正在尝试实现一些在线找到的源代码来生成高度使用柏林噪声绘制地图。我已经成功地使用 Noise3 函数获取了高度图,其中第三个坐标是随机“种子”,以允许随机高度图。

我的问题是生成的地形相当沉闷 - 我想要山脉,但我想要连绵起伏的草地。我已经阅读了一些关于 Perlin Noise 的文章(主要是此处)。由于我发现源代码显然没有考虑到可读性,而且我对柏林噪声的概念掌握很弱,我无法弄清楚我需要在代码中调整什么(幅度和频率?)创建更激烈的地形。

有关使用柏林噪声、一般柏林噪声生成高度图的更多信息,甚至一些更容易理解的代码也将受到欢迎。

编辑:我了解(某种程度上)柏林噪声的工作原理,例如,关于幅度和频率,我只是想知道在我上面链接的代码中要更改哪些变量,这些变量用于这些两个方面。

I'm trying to implement some source code I found online to generate a height map using Perlin Noise. I've successfully managed to get the height map using the noise3 function, with the third coordinate being a random "seed", to allow for random height maps.

My problem is that the terrain generated is rather dull - I want mountains and I'm getting rolling grassland. I've done some reading up on Perlin Noise (mostly here). Due to the source code I've found obviously not written with readability in mind and my weak grasp on the concept of Perlin Noise in general, I can't figure out what I need to tweak in the code (amplitude and frequency?) to create more drastic terrain.

Some more info on generating height maps using Perlin Noise, Perlin Noise in general, or even some more decipherable code would also be welcome.

EDIT: I understand (kind of) how Perlin Noise works, e.g., with respect to amplitude and frequency, I'm just wondering what variables to change in the code I linked above, which are used for these two aspects.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

半世蒼涼 2024-10-19 23:17:50

Perlin 噪声完全由您设置的不同变量控制,即幅度、频率和持久性。八度的数量有一点变化,但变化不大。在我过去编写的代码中,我只是调整了频率和持久性的数量级,直到得到我需要的东西。如果需要的话我可以尝试找到我的旧来源。

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

Perlin noise is completely controlled by the different variables you set, i.e. amplitude, frequency and persistance. The amount of octaves has a little change, but not much. In code that I have written in the past I have just played around with the order of magnitude of the frequency and persistance until I have gotten what I needed. I can try to find my old source if needed.

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);
}
故笙诉离歌 2024-10-19 23:17:50

一位朋友刚刚将我与这个问题联系起来,我想我应该尝试澄清已接受的答案中未解决的一些问题。

Elias 的有趣且有用的文章使用“价值噪声”而不是“Perlin 噪声”。值噪声涉及随机点的曲线拟合。梯度噪声(Perlin 噪声是其中的一个主要示例)创建 0 值点的网格,并为每个点提供随机梯度。他们经常互相混淆!

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

其次,使用第三个值作为一粒种子很贵。如果您想要随机地形,请考虑将原点平移随机量。 3D 调用比 2D 调用昂贵得多(例如,更喜欢 getNoise2D(x + XSEED, y + YSEED) 优于 getNoise3D( x, y, ZSEED))。假设 z 值保持不变,您所做的就是使用 z 值来选择 2D 噪声的特定切片。

第三,直接函数调用将返回总体上相当平滑和滚动的值,而不是像真实地形那样崎岖不平,因为它的随机性仅限于单个频率。为了获得更崎岖的地形,一个好的技术是将在不同频率下穿过噪声空间的多个调用相加,通常设置“分形”值。

因此,例如,将噪声(x, y) + (1/2)(噪声(x*2, y*2) + (1/4)(噪声(x*4, y*4) 相加...

得到的总和可能经常超出 -1 到 1 的范围,因此在这些值有用之前您必须对结果进行标准化。保持在 [-1, 1] 范围内,例如,根据您使用的“八度音阶”数量进行渐进加权(但我不知道这是否确实是最有效的方法。)

四个八度音阶的示例: (1/15)(噪声(x, y) + (2/15)(噪声(2x, 2y) + (4/15)(噪声(4x, 4y) + (8/15)(噪声( 8x, 8y)

第四,当将结果(范围从-1到1)映射到更常用的颜色值或颜色图(0到1)时,Ken Perlin描述了两种算法。名称“smooth”,其中映射值通过简单的翻译算法进行操作:

f(x) = (x + 1) / 2

另一个名称为“turbulent”,其中映射值计算如下:

f(x) = | x |;

对于前者,结果值将在颜色范围内变化范围内,极端人口稀少。对于后者,结果值将在颜色范围或颜色图的一端“折叠”。这种折叠将在折叠点处给地形带来角脊,而不是平滑滚动。 (这假设将八度音阶总和保持在 -1 到 1 的范围内,并且如果使用自定义颜色映射,则颜色范围在映射过程中平滑进展。这些条件都不是不过,它是“必需的”,并且可以用来获得有趣的效果。)

我正在开发 SimplexNoise 可视化工具... [编辑:现在在 GitHub 上:SiVi:基于 Java 的 2D 梯度噪声可视化工具] ...作为 Java 项目。可以找到可视化工具的初稿...[编辑:我正在删除旧 java-gaming.org 站点的无效链接。该网站已迁移至 jvm.gaming.org。如果您访问 jvm-gaming,请注意,对 SiVi 的较旧引用也往往有死链接。]

关于 SimplexNoise 如何工作的精彩文章(以及 Perlin 与 Gradient 背景):
http://staffwww.itn.liu.se/~stegu/simplexnoise/ simplexnoise.pdf

Stefan Gustavson 在这方面做得非常好!

A friend just linked me to this question, and I thought I'd try and clear up a couple things that aren't addressed in the accepted answer.

Elias' interesting and helpful article uses "value noise" not "Perlin noise". Value noise involves curve-fitting of randomized points. Gradient noise (of which Perlin noise is a primary example) creates a lattice of 0-value points and gives each one a random gradient. They are frequently confused with one another!

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

Secondly, using a 3rd value as a seed is expensive. If you want random terrain, consider translating your origin a random amount instead. 3D calls are significantly more expensive than 2D calls (e.g., prefer getNoise2D(x + XSEED, y + YSEED) over getNoise3D(x, y, ZSEED)). Assuming the z value remains constant, all you are doing is using the z value to select a particular slice of 2D noise.

Thirdly, the straight function call is going to return values that are fairly smooth and rolling overall, not as craggy as real terrain, since it's randomness is limited to a single frequency. To get craggier terrain, a good technique is to sum together multiple calls that progress through the noise space at differing frequencies, usually set a "fractal" values.

Thus, for example, sum together noise(x, y) + (1/2)(noise(x*2, y*2) + (1/4)(noise(x*4, y*4)...

The resulting sum will probably often be outside the range -1 to 1, so you will have to normalize the result before the values are useful. I'd like to suggest setting up a series that is guaranteed to remain within [-1, 1], for example, by progressive weighting depending upon how many 'octaves' you use. (But I don't know if this is truly the most efficient way to do this.)

Example with four octaves: (1/15)(noise(x, y) + (2/15)(noise(2x, 2y) + (4/15)(noise(4x, 4y) + (8/15)(noise(8x, 8y)

Fourthly, when mapping the results, which range from -1 to 1, to the normalization more often used with color values or color maps (0 to 1), Ken Perlin described two algorithms. One was given the name "smooth", where the mapped values are operated on by a simple translation algorithm:

f(x) = (x + 1) / 2

The other was given the name "turbulent", where the mapped values are computed as follows:

f(x) = | x |;

With the former, the resulting values will range over the color range, with sparser population at the extremes. With the latter, the resulting values will "fold" at one end of the color range or color map. This folding will give the terrain angular ridges at the fold point, as opposed to being smoothly rolling. (This assumes that one is keeping the sum of the octaves within the range -1 to 1, and that if a custom color mapping is being used, the color ranges progress smoothly over the course of the map. Neither of these conditions is "required" though, and can be played with for interesting effects.)

I'm working on a SimplexNoise visualizer... [Edit: now up at GitHub: SiVi: A Java-based 2D Gradient Noise Visualizer] ... as a Java project. A first draft of the visualizer can be found ... [Edit: I am deleting a dead link to the old java-gaming.org site. The site has been migrated to jvm.gaming.org. If you go to jvm-gaming, be warned that older references to SiVi tend to have dead links, as well.]

Great article on how SimplexNoise works (and Perlin vs Gradient background):
http://staffwww.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf

Stefan Gustavson did a really nice job of this!

请爱~陌生人 2024-10-19 23:17:50

振幅控制地形的高/低,频率,其流动程度,频率越低,流动性越强。

因此,如果您想要锯齿状的山地景观,则需要同时提升两者。

Amplitude controls how high/low the terrain is, the frequency how flowing it is, with lower frequency being more flowing.

So if you want a jagged mountainous landscape you need to up both.

倒数 2024-10-19 23:17:50

这是我不久前使用 3D Perlin Noise 在 JavaScript 中编写的表面生成示例。由于表面体素要么存在,要么不存在,我只需在计算柏林噪声立方体后应用阈值。在该示例中,所有维度的噪声概率都是相等的。当您增加朝向地面的随机值并减少朝向天空的随机值时,您可以获得更真实的景观。

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

必须启用 WebGL。在撰写本文时,我建议使用 Chrome 以获得最佳性能。

Here's an example of surface generation I wrote a while ago in JavaScript using 3D Perlin Noise. Since in a surface voxels are either present or not I simply apply a threshold after calculating the Perlin Noise cube. In the example the noise probability is equal for all dimensions. You can get a more realistic landscape when you increase the random values towards the ground and reduce it towards the sky.

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

WebGL must be enabled. At the time of writing this I recommend to use Chrome for best performance.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文