【问题标题】:2D Perlin Noise2D 柏林噪声
【发布时间】:2012-01-29 08:51:38
【问题描述】:

我已经完全掌握了 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);
}

【问题讨论】:

  • 由于您怀疑hashTable 可能不是随机分发的,因此如果您将代码发布到生成它的位置会有所帮助。如果是这种情况,this article 可能会有用。
  • 哈希表的长度实际上加倍到 512,以避免必须包装索引以适应 0-255 范围。创建它很简单,与 3D 相同。 for (int i = 0; i
  • 我解决了第二个问题,即噪音附着在左上角。如果您使用的区域从 (0, 0, 0) 开始,那么在 3D 中实际上会发生同样的事情我解决这个问题的方法是在您传递给噪声函数的坐标上添加一些,例如 - Noise2D((x + 1000) * 频率,(y + 1000) * 频率);基本上 (0, 0) 周围的噪声无法正确扩展,因此只会重复自身。
  • 答案已更新为 2D 版本。
  • “在 2D 中,我想我会使用 8 个渐变。上、下、左、右和四个对角线方向。”我很好奇你为什么选择这些方向。 Perlin 说他试图通过“改进的”噪音 (mrl.nyu.edu/~perlin/paper445.pdf) 避免的是“立方网格本身具有方向偏差,沿轴缩短并在对角线上拉长”。似乎通过避免轴和对角线可以得到更好的结果,例如将这些梯度向量旋转 22.5 度?

标签: c# noise


【解决方案1】:

我不得不改变这个:

            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。

【讨论】:

  • +1 我会把这个归档在“D'oh!”下在我这边。感谢您回来提供解决方案。
【解决方案2】:

如果您将z 的零值插入到您的 3D 方程中,然后简单地按照数学运算,删除项,您会发现最终得到了一个更简单的方程。

不过,您的实现看起来与我使用的有点不同。

这是我正在使用的 3D 和 2D 函数的比较(在 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)
                )
            )
    );
}

【讨论】:

    【解决方案3】:

    我有点着急,但这可能会有所帮助。我将 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 版本。这是课程:

    /// implements improved Perlin noise in 2D. 
    /// Transcribed from http://www.siafoo.net/snippet/144?nolinenos#perlin2003
    /// </summary>
    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;
            }
        }
    
        /// <summary>
        /// generate a new permutation.
        /// </summary>
        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 噪声,它在更高维度上要快得多。

    【讨论】:

    • 这很好用,我相信大多数人都是这样处理 2D 噪声的,但它与 3D 噪声需要相同的时间。我的目标是制作一个特别快的二维函数
    • @Frobot 我在某处放置了一个 2D 版本。我看看能不能挖出来。
    • 非常感谢您的帖子。我会尽快好好看看它,看看我是否能根据这个找到我的问题所在,如果没有,我可能会使用这种类型的实现编写一个新的。
    • @Frobot 很高兴为您提供帮助。你能接受我的回答(假设它有帮助),还是我错过了什么?
    • 我将继续努力,直到找出我的方法不起作用的原因。但是代码非常感谢,希望在里面我能找到我的答案。
    猜你喜欢
    • 1970-01-01
    • 2016-05-30
    • 2021-04-29
    • 2017-11-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-02-21
    • 2013-09-16
    相关资源
    最近更新 更多