2D Perlin Noise - PullRequest
       5

2D Perlin Noise

14 голосов
/ 28 декабря 2011

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

Вот что я получаю:

enter image description here

Общий вид шума всегда правильный, но края квадратов не совсем совпадают. Я также пытался использовать другие градиенты или меньше градиентов, но получаю похожие результаты. Здесь, в другом примере, вы можете видеть, что края иногда совпадают, и результаты в этой области хорошие -

enter image description here

Когда я не использую градиенты и вместо этого просто интерполирую между значением, выбранным случайным образом в каждом из 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);
}

Ответы [ 3 ]

11 голосов
/ 28 декабря 2011

Я немного тороплюсь, но это может быть полезно. Я адаптировал эталонную реализацию Perlin для C #. Для 2D, просто используйте функцию 3D Noise () с фиксированным параметром z. (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), но должно быть достаточно ясно, что они делают.

Если вы хотите более высокочастотный (более «шумный») контент с меньшим количеством октав, увеличьте начальное значение частоты, которое используется в цикле октав.

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

8 голосов
/ 01 января 2012

Мне пришлось изменить это:

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

В основном просто вычитая 1 из x и y, где это необходимо.

2 голосов
/ 19 октября 2012

Если вы добавите нулевое значение для z в свое трехмерное уравнение и просто выполните математическую процедуру, удалив термины, вы увидите, что в итоге вы получите более простое уравнение.

Ваша реализация выглядит несколько иначе, чем та, которую я использую.

Вот сравнение используемой 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)
                )
            )
    );
}
...