2D Perlin噪音

我已经完全掌握了3D中Perlin Noise的艺术,现在我正在尝试将相同的实现用于2D算法。 问题似乎在于选择我的渐变方向。 在3D中,我在均匀分布的方向上使用16个渐变,这非常有用。 在2D我想我会使用8个渐变。 上,下,左,右和四个对角线方向。

这是我得到的:

在此处输入图像描述

噪声的一般外观总是正确的,但是正方形的边缘并不完全匹配。 我也尝试使用其他渐变或更少的渐变,但得到类似的结果。 在另一个例子中,你可以看到边缘确实有时匹配,结果在那个区域很好 –

在此处输入图像描述

当我不使用渐变而只是在4个角中的每个角落随机拾取的值之间进行插值时,我得到了正确的结果,这就是让我觉得渐变部分会弄乱它的原因。

这是我的代码:

//8 different gradient directions private Point[] grads = new Point[] { new Point(0, 1), new Point(1, 1), new Point(1, 0), new Point(1, -1), new Point(0, -1), new Point(-1, -1), new Point(-1, 0), new Point(-1, 1),}; //takes the dot product of a gradient and (x, y) private float dot2D(int i, float x, float y) { return grads[i].X * x + grads[i].Y * y; } public float Noise2D(float x, float y) { int ix = (int)(x), iy = (int)(y); x = x - ix; y = y - iy; float fx = fade(x), fy = fade(y); ix &= 255; iy &= 255; // here is where i get the index to look up in the list of // different gradients. // hashTable is my array of 0-255 in random order int g00 = hashTable[ix + hashTable[iy ]], g10 = hashTable[ix + 1 + hashTable[iy ]], g01 = hashTable[ix + hashTable[iy + 1]], g11 = hashTable[ix + 1 + hashTable[iy + 1]]; // this takes the dot product to find the values to interpolate between float n00 = dot2D(g00 & 7, x, y), n10 = dot2D(g10 & 7, x, y), n01 = dot2D(g01 & 7, x, y), n11 = dot2D(g11 & 7, x, y); // lerp() is just normal linear interpolation float y1 = lerp(fx, n00, n10), y2 = lerp(fx, n01, n11); return lerp(fy, y1, y2); } 

我有点匆忙,但这可能会有所帮助。 我将Perlin的参考实现改编为C#。 对于2D,只需使用具有固定z参数的3D Noise()函数。 ( public static float Noise(float x, float y, float z)朝向类的末尾。)

 using System; using System.Collections.Generic; using System.Linq; using System.Text; using Microsoft.Xna.Framework; using System.Diagnostics; namespace GoEngine.Content.Entities { public class NoiseMaker { /// adapted from http://cs.nyu.edu/~perlin/noise/ // JAVA REFERENCE IMPLEMENTATION OF IMPROVED NOISE - COPYRIGHT 2002 KEN PERLIN. private static int[] p = new int[512]; private static int[] permutation = { 151,160,137,91,90,15, 131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23, 190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33, 88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166, 77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244, 102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196, 135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123, 5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42, 223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9, 129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228, 251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107, 49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254, 138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180 }; static NoiseMaker() { CalculateP(); } private static int _octaves; private static int _halfLength = 256; public static void SetOctaves(int octaves) { _octaves = octaves; var len = (int)Math.Pow(2, octaves); permutation = new int[len]; Reseed(); } private static void CalculateP() { p = new int[permutation.Length * 2]; _halfLength = permutation.Length; for (int i = 0; i < permutation.Length; i++) p[permutation.Length + i] = p[i] = permutation[i]; } public static void Reseed() { var random = new Random(); var perm = Enumerable.Range(0, permutation.Length).ToArray(); for (var i = 0; i < perm.Length; i++) { var swapIndex = random.Next(perm.Length); var t = perm[i]; perm[i] = perm[swapIndex]; perm[swapIndex] = t; } permutation = perm; CalculateP(); } public static float Noise(Vector3 position, int octaves, ref float min, ref float max) { return Noise(position.X, position.Y, position.Z, octaves, ref min, ref max); } public static float Noise(float x, float y, float z, int octaves, ref float min, ref float max) { var perlin = 0f; var octave = 1; for (var i = 0; i < octaves; i++) { var noise = Noise(x * octave, y * octave, z * octave); perlin += noise / octave; octave *= 2; } perlin = Math.Abs((float)Math.Pow(perlin,2)); max = Math.Max(perlin, max); min = Math.Min(perlin, min); //perlin = 1f - 2 * perlin; return perlin; } public static float Noise(float x, float y, float z) { int X = (int)Math.Floor(x) % _halfLength; int Y = (int)Math.Floor(y) % _halfLength; int Z = (int)Math.Floor(z) % _halfLength; if (X < 0) X += _halfLength; if (Y < 0) Y += _halfLength; if (Z < 0) Z += _halfLength; x -= (int)Math.Floor(x); y -= (int)Math.Floor(y); z -= (int)Math.Floor(z); var u = Fade(x); var v = Fade(y); var w = Fade(z); int A = p[X] + Y, AA = p[A] + Z, AB = p[A + 1] + Z, // HASH COORDINATES OF B = p[X + 1] + Y, BA = p[B] + Z, BB = p[B + 1] + Z; // THE 8 CUBE CORNERS, return MathHelper.Lerp( MathHelper.Lerp( MathHelper.Lerp( Grad(p[AA], x, y, z) // AND ADD , Grad(p[BA], x - 1, y, z) // BLENDED , u ) , MathHelper.Lerp( Grad(p[AB], x, y - 1, z) // RESULTS , Grad(p[BB], x - 1, y - 1, z) , u ) , v ) , MathHelper.Lerp( MathHelper.Lerp( Grad(p[AA + 1], x, y, z - 1) // CORNERS , Grad(p[BA + 1], x - 1, y, z - 1) // OF CUBE , u ) , MathHelper.Lerp( Grad(p[AB + 1], x, y - 1, z - 1) , Grad(p[BB + 1], x - 1, y - 1, z - 1) , u ) , v ) , w ); } static float Fade(float t) { return t * t * t * (t * (t * 6 - 15) + 10); } static float Grad(int hash, float x, float y, float z) { int h = hash & 15; // CONVERT LO 4 BITS OF HASH CODE float u = h < 8 ? x : y, // INTO 12 GRADIENT DIRECTIONS. v = h < 4 ? y : h == 12 || h == 14 ? x : z; return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v); } } } 

更新

好的,我设法创建了一个有效的2D版本。 这是class级:

 /// implements improved Perlin noise in 2D. /// Transcribed from http://www.siafoo.net/snippet/144?nolinenos#perlin2003 ///  public static class Noise2d { private static Random _random = new Random(); private static int[] _permutation; private static Vector2[] _gradients; static Noise2d() { CalculatePermutation(out _permutation); CalculateGradients(out _gradients); } private static void CalculatePermutation(out int[] p) { p = Enumerable.Range(0, 256).ToArray(); /// shuffle the array for (var i = 0; i < p.Length; i++) { var source = _random.Next(p.Length); var t = p[i]; p[i] = p[source]; p[source] = t; } } ///  /// generate a new permutation. ///  public static void Reseed() { CalculatePermutation(out _permutation); } private static void CalculateGradients(out Vector2[] grad) { grad = new Vector2[256]; for (var i = 0; i < grad.Length; i++) { Vector2 gradient; do { gradient = new Vector2((float)(_random.NextDouble() * 2 - 1), (float)(_random.NextDouble() * 2 - 1)); } while (gradient.LengthSquared() >= 1); gradient.Normalize(); grad[i] = gradient; } } private static float Drop(float t) { t = Math.Abs(t); return 1f - t * t * t * (t * (t * 6 - 15) + 10); } private static float Q(float u, float v) { return Drop(u) * Drop(v); } public static float Noise(float x, float y) { var cell = new Vector2((float)Math.Floor(x), (float)Math.Floor(y)); var total = 0f; var corners = new[] { new Vector2(0, 0), new Vector2(0, 1), new Vector2(1, 0), new Vector2(1, 1) }; foreach (var n in corners) { var ij = cell + n; var uv = new Vector2(x - ij.X, y - ij.Y); var index = _permutation[(int)ij.X % _permutation.Length]; index = _permutation[(index + (int)ij.Y) % _permutation.Length]; var grad = _gradients[index % _gradients.Length]; total += Q(uv.X, uv.Y) * Vector2.Dot(grad, uv); } return Math.Max(Math.Min(total, 1f), -1f); } } 

这样叫:

 private void GenerateNoiseMap(int width, int height, ref Texture2D noiseTexture, int octaves) { var data = new float[width * height]; /// track min and max noise value. Used to normalize the result to the 0 to 1.0 range. var min = float.MaxValue; var max = float.MinValue; /// rebuild the permutation table to get a different noise pattern. /// Leave this out if you want to play with changing the number of octaves while /// maintaining the same overall pattern. Noise2d.Reseed(); var frequency = 0.5f; var amplitude = 1f; var persistence = 0.25f; for (var octave = 0; octave < octaves; octave++) { /// parallel loop - easy and fast. Parallel.For(0 , width * height , (offset) => { var i = offset % width; var j = offset / width; var noise = Noise2d.Noise(i*frequency*1f/width, j*frequency*1f/height); noise = data[j * width + i] += noise * amplitude; min = Math.Min(min, noise); max = Math.Max(max, noise); } ); frequency *= 2; amplitude /= 2; } if (noiseTexture != null && (noiseTexture.Width != width || noiseTexture.Height != height)) { noiseTexture.Dispose(); noiseTexture = null; } if (noiseTexture==null) { noiseTexture = new Texture2D(Device, width, height, false, SurfaceFormat.Color); } var colors = data.Select( (f) => { var norm = (f - min) / (max - min); return new Color(norm, norm, norm, 1); } ).ToArray(); noiseTexture.SetData(colors); } 

请注意,我使用了几个XNA结构(Vector2和Texture2D),但它们应该非常清楚它们的作用。

如果您想要更高频率(更“嘈杂”)的内容以及更少的八度音阶,请增加八度音程循环中使用的初始频率值。

此实现使用“改进的”Perlin噪声,它应该比标准版本快一点。 您可能还会看一下Simplex噪音,它在更高的尺寸上要快得多。

我不得不改变这个:

  n00 = dot2D(g00 & 7, x, y), n10 = dot2D(g10 & 7, x, y), n01 = dot2D(g01 & 7, x, y), n11 = dot2D(g11 & 7, x, y); 

对此:

  n00 = dot2D(g00 & 7, x , y ), n10 = dot2D(g10 & 7, x - 1, y ), n01 = dot2D(g01 & 7, x , y - 1), n11 = dot2D(g11 & 7, x - 1, y - 1); 

基本上只需要从x和y中减去1。

如果你将z的零值插入到你的3D方程中,只需按照数学运算,删除项,你就会看到最终得到一个更简单的方程式。

您的实现看起来与我正在使用的实现有所不同。

这是我正在使用的3D和2Dfunction的比较(在JavaScript中):

 noise3d: function(x, y, z) { // Find unit cube that contains point. var X = Math.floor(x) & 255, Y = Math.floor(y) & 255, Z = Math.floor(z) & 255; // Find relative x,y,z of point in cube. x -= Math.floor(x); y -= Math.floor(y); z -= Math.floor(z); // Compute fade curves for each of x,y,z. var u = fade(x), v = fade(y), w = fade(z); // Hash coordinates of the corners. var A = p[X ] + Y, AA = p[A] + Z, AB = p[A + 1] + Z, B = p[X + 1] + Y, BA = p[B] + Z, BB = p[B + 1] + Z; // Add blended results from 8 corners of cube. return scale( lerp( w, lerp( v, lerp( u, grad(p[AA], x, y, z), grad(p[BA], x - 1, y, z) ), lerp( u, grad(p[AB], x, y - 1, z), grad(p[BB], x - 1, y - 1, z) ) ), lerp( v, lerp( u, grad(p[AA + 1], x, y, z - 1), grad(p[BA + 1], x - 1, y, z - 1) ), lerp( u, grad(p[AB + 1], x, y - 1, z - 1), grad(p[BB + 1], x - 1, y - 1, z - 1) ) ) ) ); } 

2D版本涉及较少的计算。

 noise2d: function(x, y) { // Find unit square that contains point. var X = Math.floor(x) & 255, Y = Math.floor(y) & 255; // Find relative x,y of point in square. x -= Math.floor(x); y -= Math.floor(y); // Compute fade curves for each of x,y. var u = fade(x), v = fade(y); // Hash coordinates of the corners. var A = p[X ] + Y, AA = p[A], AB = p[A + 1], B = p[X + 1] + Y, BA = p[B], BB = p[B + 1]; // Add blended results from the corners. return scale( lerp( v, lerp( u, grad(p[AA], x, y, 0), grad(p[BA], x - 1, y, 0) ), lerp( u, grad(p[AB], x, y - 1, 0), grad(p[BB], x - 1, y - 1, 0) ) ) ); }