2011-12-28 36 views
13

Tôi đã hoàn toàn nắm vững nghệ thuật của Perlin Noise trong 3D, và bây giờ tôi đang cố gắng sử dụng cùng một triển khai cho một thuật toán 2D. Vấn đề có vẻ là chọn các hướng dốc của tôi. Trong 3D, tôi sử dụng 16 gradient trong các hướng phân bố đồng đều và điều này hoạt động rất tốt. Trong 2D tôi figured tôi muốn sử dụng 8 gradient. lên, xuống, trái, phải và bốn hướng chéo.2D Perlin Noise

Dưới đây là những gì tôi nhận được:

enter image description here

Cái nhìn chung về tiếng ồn phải lúc nào cũng đúng, nhưng các cạnh của hình vuông không khá phù hợp. Tôi cũng đã thử sử dụng các gradient khác hoặc ít gradient hơn nhưng có kết quả tương tự. đây trong ví dụ khác, bạn có thể thấy rằng các cạnh trùng khớp nhau đôi và kết quả cũng tốt ở khu vực đó -

enter image description here

Khi tôi không sử dụng gradient và thay vào đó chỉ là suy giữa một giá trị chọn ngẫu nhiên tại mỗi trong số 4 góc tôi nhận được kết quả đúng, đó là những gì làm cho tôi nghĩ rằng đó là phần gradient đó là rối tung nó lên.

Đây là mã của tôi:

//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

Vì bạn nghi ngờ rằng 'hashTable' có thể không được phân phối ngẫu nhiên, nó sẽ giúp ích nếu bạn đăng mã nơi bạn tạo mã. Nếu đúng như vậy, [bài viết này] (http://www.codinghorror.com/blog/2007/12/the-danger-of-naivete.html) có thể hữu ích. – Groo

+0

bảng băm thực sự được tăng gấp đôi chiều dài lên 512 để tránh phải quấn chỉ mục để vừa với khoảng 0-255. Tạo ra nó rất đơn giản và giống với 3D. cho (int i = 0; i <512; i ++) hashTable [i] = ran.Next (256); Vấn đề có thể là hai tra cứu vào bảng này là không đủ để tạo ra sự ngẫu nhiên đầy đủ. Trong 3D có 3 tra cứu vào bảng, nhưng có vẻ như 2D sẽ được thực hiện theo cùng một cách chính xác. Bạn chỉ mục vào nó với giá trị x và giá trị y của điểm của bạn. – Frobot

+0

Tôi giải quyết được vấn đề thứ hai, nơi tiếng ồn bám vào góc trên cùng bên trái. Điều tương tự thực sự xảy ra trong 3D nếu khu vực bạn đang sử dụng bắt đầu tại (0, 0, 0) Những gì tôi đã làm để khắc phục điều này là thêm một số vào tọa độ bạn chuyển vào chức năng tiếng ồn, ví dụ - Noise2D ((x + 1000) * tần số, (y + 1000) * tần số); Về cơ bản, nhiễu xung quanh (0, 0) không thể mở rộng một cách chính xác để nó chỉ lặp lại chính nó. – Frobot

Trả lời

8

tôi đã phải thay đổi này:

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

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

Về cơ bản chỉ trừ 1 từ x và y khi cần thiết.

+2

+1 Tôi sẽ gửi tệp này dưới "D'oh!" về phía tôi. Cảm ơn bạn đã quay lại với giải pháp. –

8

tôi đang ở trong một chút của một cuộc chạy đua, nhưng điều này có thể hữu ích. Tôi đã điều chỉnh việc thực hiện tham chiếu của Perlin thành C#. Đối với 2D, chỉ cần sử dụng chức năng 3D Noise() với tham số z cố định. (public static float Noise(float x, float y, float z) về phía cuối lớp.)

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

    } 
} 

Cập nhật

Được rồi, tôi cố gắng tạo ra một phiên bản 2D làm việc. Dưới đây là các lớp:

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

} 

Gọi nó như thế này:

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

Lưu ý rằng tôi đã sử dụng một vài cấu trúc XNA (Vector2 và Texture2D), nhưng nó nên được khá rõ ràng những gì họ làm.

Nếu bạn muốn nội dung tần số cao hơn (có nhiều tiếng ồn hơn) có ít quãng tám hơn, hãy tăng giá trị tần số ban đầu được sử dụng trong vòng octave.

Triển khai này sử dụng tiếng ồn Perlin được cải tiến, nhanh hơn một chút so với phiên bản chuẩn. Bạn cũng có thể xem xét nhiễu Simplex, nhanh hơn một chút ở các kích thước cao hơn.

+0

Điều này làm việc tuyệt vời và tôi tin rằng đó là cách hầu hết mọi người làm tiếng ồn 2D, nhưng phải mất cùng một thời gian như tiếng ồn 3D. Mục tiêu của tôi ở đây là tạo ra một chức năng 2D cụ thể nhanh hơn đáng kể – Frobot

+0

@Frobot Tôi đã có một phiên bản 2D nằm xung quanh một nơi nào đó. Tôi sẽ xem tôi có thể đào nó không. –

+0

Điều đó sẽ được đánh giá cao – Frobot

2

Nếu bạn cắm giá trị bằng không cho z vào phương trình 3D của mình và chỉ cần thực hiện theo toán, loại bỏ các cụm từ, bạn sẽ thấy rằng bạn kết thúc bằng một phương trình đơn giản cuối cùng.

Triển khai của bạn có vẻ khác với cách tôi đang sử dụng.

Dưới đây là một sự so sánh của một 3D và 2D chức năng Tôi đang sử dụng (trong 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) 
       ) 
      ) 
     ) 
    ); 
} 

Phiên bản 2D liên quan đến việc tính toán ít hơn.

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) 
       ) 
      ) 
    ); 
} 
Các vấn đề liên quan