2013-08-15 37 views
5

Tôi đang nghiên cứu một vài cách triển khai khác nhau cho tiếng ồn mạch lạc (tôi biết có thư viện, nhưng điều này chủ yếu là do sự chỉnh sửa và tò mò của riêng tôi) và cách bạn có thể sử dụng nó. Tôi có điều tiếng ồn Perlin gốc.Phạm vi đầu ra của nhiễu Perlin

Theo this frequently linked Math FAQ, phạm vi đầu ra sẽ nằm trong khoảng từ -11, nhưng tôi không hiểu cách giá trị nằm trong phạm vi đó.

Như tôi đã hiểu, thuật toán về cơ bản là: mỗi điểm lưới có một vector gradient ngẫu nhiên liên quan có chiều dài 1. Sau đó, đối với mỗi điểm, đối với tất cả bốn điểm lưới xung quanh, bạn tính toán sản phẩm chấm của gradient ngẫu nhiên và vectơ đi từ điểm lưới đó. Sau đó, bạn sử dụng một đường cong dễ dàng ưa thích và nội suy tuyến tính để giảm giá trị đó xuống một giá trị. Tuy nhiên, đây là vấn đề của tôi: đôi khi, các sản phẩm này sẽ nằm ngoài phạm vi [-1, 1] và do bạn thực hiện nội suy tuyến tính cuối cùng giữa các sản phẩm chấm, điều đó có nghĩa là giá trị cuối cùng sẽ, đôi khi, nằm ngoài phạm vi [-1, 1]? Ví dụ:

Ví dụ: một trong các vectơ ngẫu nhiên là (sqrt(2)/2, sqrt(2)/2) (có chiều dài 1) và (0.8, 0.8) (nằm trong ô vuông đơn vị), bạn sẽ nhận được kết quả là khoảng 1.131. Nếu giá trị đó được sử dụng trong nội suy tuyến tính, hoàn toàn có thể là giá trị được tạo sẽ lớn hơn 1. Và, quả thực, với việc triển khai thẳng tiến của tôi, điều đó xảy ra khá thường xuyên.

Tôi có thiếu gì đó ở đây không?

Để tham khảo, đây là mã của tôi bằng Java. Vec là một lớp đơn giản để làm số học vectơ 2d đơn giản, fade() là đường cong dễ dàng, lerp() là nội suy tuyến tính và gradient(x, y) cung cấp cho bạn độ dốc cho điểm lưới đó là Vec. Biến gridSize mang đến cho bạn kích thước của lưới bằng pixel (nó có kiểu double):

public double getPoint(int x, int y) { 
    Vec p = new Vec(x/gridSize, y/gridSize); 
    Vec d = new Vec(Math.floor(p.x), Math.floor(p.y)); 


    int x0 = (int)d.x, 
     y0 = (int)d.x; 


    double d00 = gradient(x0 , y0 ).dot(p.sub(x0 , y0 )), 
      d01 = gradient(x0 , y0 + 1).dot(p.sub(x0 , y0 + 1)), 
      d10 = gradient(x0 + 1, y0 ).dot(p.sub(x0 + 1, y0 )), 
      d11 = gradient(x0 + 1, y0 + 1).dot(p.sub(x0 + 1, y0 + 1)); 

    double fadeX = fade(p.x - d.x), 
      fadeY = fade(p.y - d.y); 

    double i1 = lerp(fadeX, d00, d10), 
      i2 = lerp(fadeX, d01, d11); 

    return lerp(fadeY, i1, i2); 
} 

Edit: đây là đoạn code để tạo ra các gradient ngẫu nhiên:

double theta = gen.nextDouble() * 2 * Math.PI; 
gradients[i] = new Vec(Math.cos(theta), Math.sin(theta)); 

đâu gen là một java.util.Random.

Trả lời

6

Bạn có y0 = (int)d.x;, nhưng bạn có nghĩa là d.y. Điều này chắc chắn sẽ ảnh hưởng đến phạm vi đầu ra của bạn và là lý do bạn thấy các giá trị ngoài phạm vi rộng lớn như vậy.


Điều đó nói rằng, phạm vi sản lượng Perlin tiếng ồn là không thực [-1, 1]. Trong khi tôi không hoàn toàn chắc chắn về bản thân toán học (tôi phải già đi), this rather lengthy discussion phát hiện ra phạm vi thực tế là [-sqrt (n)/2, sqrt (n)/2], trong đó n là kích thước (2 trong trường hợp của bạn). Vì vậy, phạm vi đầu ra của chức năng tiếng ồn 2D Perlin của bạn phải là [-0.707, 0.707]. Điều này là bằng cách nào đó liên quan đến thực tế là cả hai d và các thông số nội suy là một chức năng của p.Nếu bạn đọc qua cuộc thảo luận đó, bạn có thể tìm thấy lời giải thích chính xác mà bạn đang tìm kiếm (đặc biệt, post #7).

Tôi đang thử nghiệm thực hiện của bạn bằng cách sử dụng chương trình sau đây (mà tôi bị hack với nhau từ ví dụ của bạn, vì vậy tha thứ việc sử dụng kỳ lạ của gridCellsgridSize):

import java.util.Random; 


public class Perlin { 

    static final int gridSize = 200; 
    static final int gridCells = 20; 
    static final Vec[][] gradients = new Vec[gridCells + 1][gridCells + 1]; 

    static void initializeGradient() { 
     Random rand = new Random(); 
     for (int r = 0; r < gridCells + 1; ++ r) { 
      for (int c = 0; c < gridCells + 1; ++ c) { 
       double theta = rand.nextFloat() * Math.PI; 
       gradients[c][r] = new Vec(Math.cos(theta), Math.sin(theta));     
      } 
     } 
    } 

    static class Vec { 
     double x; 
     double y; 
     Vec (double x, double y) { this.x = x; this.y = y; } 
     double dot (Vec v) { return x * v.x + y * v.y; } 
     Vec sub (double x, double y) { return new Vec(this.x - x, this.y - y); } 
    } 

    static double fade (double v) { 
     // easing doesn't matter for range sample test. 
     // v = 3 * v * v - 2 * v * v * v; 
     return v; 
    } 

    static double lerp (double p, double a, double b) { 
     return (b - a) * p + a; 
    } 

    static Vec gradient (int c, int r) { 
     return gradients[c][r]; 
    } 

    // your function, with y0 fixed. note my gridSize is not a double like yours.  
    public static double getPoint(int x, int y) { 

     Vec p = new Vec(x/(double)gridSize, y/(double)gridSize); 
     Vec d = new Vec(Math.floor(p.x), Math.floor(p.y)); 

     int x0 = (int)d.x, 
      y0 = (int)d.y; 

     double d00 = gradient(x0 , y0 ).dot(p.sub(x0 , y0 )), 
       d01 = gradient(x0 , y0 + 1).dot(p.sub(x0 , y0 + 1)), 
       d10 = gradient(x0 + 1, y0 ).dot(p.sub(x0 + 1, y0 )), 
       d11 = gradient(x0 + 1, y0 + 1).dot(p.sub(x0 + 1, y0 + 1)); 

     double fadeX = fade(p.x - d.x), 
       fadeY = fade(p.y - d.y); 

     double i1 = lerp(fadeX, d00, d10), 
       i2 = lerp(fadeX, d01, d11); 

     return lerp(fadeY, i1, i2); 

    } 

    public static void main (String[] args) { 

     // loop forever, regenerating gradients and resampling for range. 
     while (true) { 

      initializeGradient(); 

      double minz = 0, maxz = 0; 

      for (int x = 0; x < gridSize * gridCells; ++ x) { 
       for (int y = 0; y < gridSize * gridCells; ++ y) { 
        double z = getPoint(x, y); 
        if (z < minz) 
         minz = z; 
        else if (z > maxz) 
         maxz = z; 
       } 
      } 

      System.out.println(minz + " " + maxz); 

     } 

    } 

} 

tôi nhìn thấy giá trị trong phạm vi lý thuyết của [-0.707, 0.707], mặc dù tôi thường thấy giá trị từ -0,6 đến 0,6; đó có thể chỉ là hậu quả của việc phân phối giá trị và tỷ lệ lấy mẫu thấp.

+0

Cảm ơn sự giúp đỡ của bạn! Điều đó đã sửa nó. – Oskar

0

Khi bạn tính toán sản phẩm chấm, bạn có thể nhận giá trị ngoài phạm vi -1 +1, tuy nhiên trong bước nội suy, giá trị cuối cùng nằm trong khoảng -1 +1. Điều này là do các vectơ khoảng cách của các sản phẩm chấm được nội suy điểm vào các hướng đối diện của trục nội suy. Trong đầu ra nội suy cuối cùng sẽ không vượt quá -1 +1 phạm vi.

Phạm vi đầu ra cuối cùng cho nhiễu Perlin được xác định theo độ dài của vectơ gradient. Nếu chúng ta nói về nhiễu 2D và mục tiêu của chúng ta có phạm vi đầu ra -1 +1, độ dài của các vectơ gradient phải là sqrt (2) (~ 1,4142). Lỗi phổ biến là trộn các vectơ này (1, 1) (-1, 1) (1, -1) (-1, -1) và (1, 0) (0, 1) (-1, 0) (0, -1). Trong trường hợp này, dải đầu ra cuối cùng sẽ là -1 +1, tuy nhiên các giá trị trong dải -0.707 +0.707 sẽ thường xuyên hơn. Để tránh vấn đề này (1, 0) (0, 1) (-1, 0) (0, -1) vectơ nên được thay thế bằng (sqrt (2), 0) (0, sqrt (2)) (-sqrt) (2), 0) (0, -sqrt (2)).

Các vấn đề liên quan