2011-12-28 17 views
13

He dominado por completo el arte de Perlin Noise en 3D, y ahora estoy tratando de usar mi misma implementación para un algoritmo 2D. El problema parece estar en escoger mis direcciones de degradado. En 3D uso 16 degradados en direcciones distribuidas uniformemente y esto funciona muy bien. En 2D, pensé que usaría 8 degradados. arriba, abajo, izquierda, derecha y las cuatro direcciones diagonales.2D Perlin Noise

Esto es lo que me sale:

enter image description here

El aspecto general del ruido es siempre correcta, pero los bordes de los cuadrados no se ajusta exactamente con arriba. También he intentado usar otros degradados o menos degradados, pero obtengo resultados similares. Aquí, en otro ejemplo se puede ver que los bordes no coinciden veces y los resultados están bien en esa área -

enter image description here

Cuando no uso de gradientes y en su lugar sólo interpolar entre un valor elegido al azar en cada una de las 4 esquinas obtengo los resultados correctos, que es lo que me hace pensar que es la parte del degradado lo que está estropeando.

Aquí está mi código:

//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); 
} 
+0

Dado que sospecha que 'hashTable' podría no distribuirse aleatoriamente, sería útil que publicara el código donde lo genera. Si ese es el caso, [este artículo] (http://www.codinghorror.com/blog/2007/12/the-danger-of-naivete.html) podría ser útil. – Groo

+0

la tabla hash en realidad se ha duplicado en longitud a 512 para evitar tener que ajustar el índice para que quepa en el rango 0-255. Crearlo es simple y lo mismo que con 3D. para (int i = 0; i <512; i ++) hashTable [i] = run.Next (256); El problema podría ser que dos búsquedas en esta tabla no son suficientes para crear la aleatoriedad completa. En 3D hay 3 búsquedas en la tabla, pero parece que 2D se haría de la misma manera. Indicas con el valor x y el valor y de tu punto. – Frobot

+0

Resolví el segundo problema donde el ruido se pega a la esquina superior izquierda. Lo mismo sucede realmente en 3D si el área que está utilizando comienza en (0, 0, 0) Lo que hice para solucionar esto es agregar algo a las coordenadas que pasa a la función de ruido, por ejemplo - Noise2D ((x + 1000) * frecuencia, (y + 1000) * frecuencia); Básicamente, el ruido alrededor (0, 0) no se puede expandir correctamente, por lo que simplemente se repite. – Frobot

Respuesta

8

que tenía que cambiar esto:

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

a esto:

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

Básicamente, sólo restando 1 al X e Y donde sea necesario.

+2

+1 Voy a presentar este en "D'oh!" en mi extremo. Gracias por regresar con la solución. –

8

estoy en un poco de prisa, pero esto podría ser útil. Adapte la implementación de referencia de Perlin a C#. Para 2D, simplemente use la función 3D Noise() con un parámetro z fijo. (public static float Noise(float x, float y, float z) hacia el final de la clase.)

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

    } 
} 

actualización

Está bien, he conseguido crear una versión 2D de trabajo. Aquí está la clase:

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

} 

llamada así:

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

Tenga en cuenta que he usado un par de estructuras de XNA (vector2 y Texture2D), pero debe ser bastante claro lo que hacen.

Si desea un contenido de mayor frecuencia (más "ruidoso") con menos octavas, aumente el valor de frecuencia inicial que se utilizó en el bucle de octava.

Esta implementación usa ruido Perlin "mejorado", que debería ser un poco más rápido que la versión estándar. También podría echarle un vistazo al ruido Simplex, que es bastante más rápido en dimensiones más altas.

+0

Esto funciona muy bien y creo que así es como la mayoría de la gente hace ruido 2D, pero lleva el mismo tiempo que el ruido 3D. Mi objetivo aquí es hacer una función específicamente 2D que sea considerablemente más rápida – Frobot

+0

@Frobot Tengo una versión 2D en algún lado. Veré si puedo desenterrarlo. –

+0

Eso sería apreciado – Frobot

2

Si inserta un valor cero para z en su ecuación 3D y simplemente sigue los cálculos, eliminando los términos, verá que termina con una ecuación más simple al final.

Sin embargo, su implementación se ve un poco diferente a la que estoy usando.

He aquí una comparación de una función 3D y 2D que estoy usando (en 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) 
       ) 
      ) 
     ) 
    ); 
} 

La versión 2D implica un menor número de cálculos.

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