Tiếng ồn ngẫu nhiên dựa trên hạt giống


16

Tôi hiện đang làm việc trên một chương trình sẽ tạo ra nhiễu ngẫu nhiên trên màn hình dựa trên 'tọa độ' của pixel. Các tọa độ phải có cùng màu mỗi khi bạn khởi động lại chương trình. Tuy nhiên, bằng cách sử dụng Java.Random, các kết quả tôi nhận được không phải là ngẫu nhiên như tôi muốn:

in màn hình

Tôi nghĩ rằng nếu tôi sử dụng tọa độ kết hợp (như trong một số nguyên được hình thành từ cả hai tọa độ bên cạnh nhau) thì mỗi tọa độ sẽ có một số khác nhau. Bằng cách sử dụng số đó làm hạt giống, tôi dự kiến ​​sẽ có được một số ngẫu nhiên khác nhau cho mỗi tọa độ để sử dụng cho giá trị rgb của tọa độ đó.

Đây là mã tôi đã sử dụng:

public class Generate {

static Random Random;

    public static int TileColor(int x, int y){          
        Random = new Random(Integer.valueOf(Integer.toString(x)+Integer.toString(y)));
        int b = 1 + Random.nextInt(50);
        int g = 1 + Random.nextInt(50);
        int r = 1 + Random.nextInt(50);
        int color = -Color.rgb888(r, g, b);
        return color;
    }
}

Là mô hình mà chương trình tạo ra do cách chức năng Random của java hoạt động hay tôi đang làm gì đó sai và tôi có nên thử một cách tiếp cận khác không?

Cập nhật: Bây giờ tôi đã cố gắng loại bỏ các vấn đề xung quanh việc ghép nối bằng cách sử dụng mã sau đây:

public static int TileColor(int x, int y){  
            Randomy = new Random(y);
            Randomx = new Random(x);
            Random = new Random(Integer.valueOf(Integer.toString(Randomx.nextInt(1234))+Integer.toString(Randomy.nextInt(1234))));
            int b = 1 + Random.nextInt(100);
            int g = 1 + Random.nextInt(100);
            int r = 1 + Random.nextInt(100);
            int color = -Color.rgb888(r, g, b);
            return color;
}

Bằng cách nào đó, điều này cũng cung cấp một hình ảnh (theo ý kiến ​​của tôi) đủ ngẫu nhiên:

hình ảnh mew

Tuy nhiên, mã này đã quét lại ba lần cho mỗi pixel. Mặc dù điều này không phải là vấn đề đối với tôi ngay bây giờ, tôi vẫn xem xét thay đổi mã này trong trường hợp tôi cần sự phù hợp tốt hơn sau này.


3
Không chắc chắn về Ngẫu nhiên của Java nhưng tôi khá chắc chắn rằng nó không thực sự ngẫu nhiên ... Đọc en.wikipedia.org/wiki/Pseudorandom_number_generator Bạn sẽ hiểu tại sao bạn thấy các mẫu đó.
Salketer

23
Một cái gì đó rất quan trọng bị thiếu trong các câu trả lời khác: không lấy lại RNG cho mỗi pixel. Tạo hạt giống một lần và tạo các giá trị liên tiếp cho tất cả các pixel trong hình ảnh của bạn dựa trên đó.
Konrad Rudolph

4
Lưu ý: một số lượng giả ngẫu nhiên có thể được phân phối đồng đều theo một chiều , nhưng không thành công khi sử dụng nhiều hơn một chiều ... bạn đang tạo các điểm trong 3D (r, g và b và 3 tọa độ khác nhau một cách hiệu quả) nên bạn cần một trình tạo ngẫu nhiên đảm bảo không chỉ các giá trị mà nó tạo ra được phân phối đồng đều, mà cả các bộ ba mà nó tạo ra được phân phối đồng đều trong không gian 3D.
Bakuriu

6
@Bakuriu Nếu X, Y và Z là các biến ngẫu nhiên thống nhất độc lập, thì tôi khá chắc chắn (X, Y, Z) là đồng nhất trong không gian 3d.
Jack M

2
Bạn có thể thử nghiệm sử dụng các RNG khác nhau, như Mersenne Twister .
Kevin

Câu trả lời:


21

java.util.RandomLớp của Java thường cung cấp cho bạn các chuỗi số giả ngẫu nhiên đủ tốt để sử dụng trong các trò chơi 1 . Tuy nhiên, đặc điểm đó chỉ áp dụng cho một chuỗi nhiều mẫu dựa trên một hạt giống. Khi bạn xác định lại RNG với các giá trị hạt tăng dần và chỉ nhìn vào giá trị đầu tiên của mỗi chuỗi, các đặc tính ngẫu nhiên sẽ không tốt bằng.

Thay vào đó, bạn có thể làm gì:

  • Sử dụng cùng một hạt giống để tạo ra toàn bộ khối pixel tại một thời điểm. Ví dụ: khi bạn cần giá trị màu của pixel 425: 487, hãy đưa tọa độ 400: 400 vào RNG, tạo 10000 màu ngẫu nhiên và sử dụng màu ở chỉ số 2587 (25 * 100 + 87). Chun được tạo theo cách đó nên được lưu vào bộ nhớ cache để tránh tạo lại 10000 màu ngẫu nhiên đó cho mỗi pixel của đoạn đó.
  • Thay vì sử dụng trình tạo số ngẫu nhiên, hãy sử dụng chức năng phân loại thông báo để biến cặp tọa độ thành giá trị màu. Sản lượng của hầu hết các loại gỗ MDF không đủ dự đoán để hoàn thành hầu hết các thử nghiệm về tính ngẫu nhiên. Đầu ra thường nhiều hơn 24 bit bạn cần cho một giá trị RGB, nhưng việc cắt bớt chúng thường không có vấn đề gì.

    Để cải thiện hiệu suất, bạn có thể kết hợp tạo thông báo tiêu hóa với khối. Tạo các khối pixel nhỏ đủ lớn để sử dụng toàn bộ chiều dài của một đầu ra của chức năng phân loại.

1 khi thực sự cần thiết là không ai có thể dự đoán số tiếp theo, hãy sử dụng số chậm hơn nhưng ít dự đoán hơnjava.security.SecureRandom


13

Các tọa độ phải có cùng màu với mọi người bạn khởi động lại chương trình

Trong trường hợp đó, bạn sẽ muốn sử dụng chức năng nhiễu xác định như tiếng ồn Perlin hoặc tiếng ồn đơn giản .

( Xem câu hỏi này để biết thêm thông tin về nhiễu Perlin với một số hình ảnh đẹp. )

Đối với hầu hết các phần, sử dụng hàm tích hợp random()hoặc tương tự sẽ cung cấp cho bạn các giá trị khác nhau mỗi khi bạn chạy chương trình, vì chúng có thể sử dụng đồng hồ làm đầu vào hoặc một số giá trị giả ngẫu nhiên khác.

Một tùy chọn khác là tạo "bản đồ nhiễu" một lần, ngoại tuyến và sau đó sử dụng đó làm nguồn số ngẫu nhiên của bạn sau này.

Trong triển khai của bạn, bạn đang nối các biểu diễn chuỗi của x và y. Điều đó thật tệ vì nó không phải là duy nhất trên toàn miền. Ví dụ,

x    y   concatenated
40   20  4020
402   0  4020
10   10  1010
101   0  1010
12   34  1234
123   4  1234
1   234  1234

Chúc may mắn!


1
Điểm tốt về các con số nối. Tuy nhiên, chương trình luôn cho kết quả chính xác nếu tôi chạy chương trình nhiều lần. Tôi cũng đã xem xét tiếng ồn perlin / simplex, có thể xem xét điều đó và xem liệu nó có hoạt động tốt hơn không. Tuy nhiên tôi vẫn chưa chắc tại sao Java lại tạo ra các mẫu, vì vấn đề ghép nối dường như không giải quyết được hoàn toàn
chuồn chuồn

1
Là không đủ để chỉ đơn giản là gieo ngẫu nhiên với giá trị hạt không đổi trước khi tạo các pixel?
Jack M

1
@JackM Điều đó phụ thuộc hoàn toàn vào thuật toán PRNG đang chơi.
3Dave

4
"Tôi đã từng thấy một triển khai rand () sử dụng mỗi giá trị làm hạt giống cho giá trị tiếp theo." Không phải đó là cách mà hầu hết các trình tạo số giả không mã hóa hoạt động sao? Họ sử dụng số ngẫu nhiên (hoặc trạng thái) trước đó làm đầu vào để tạo số / trạng thái ngẫu nhiên tiếp theo.
JAB

2
@DavidLively Hầu như tất cả các PRNG đều làm điều này hoặc một cái gì đó tương đương, trừ khi trạng thái bên trong của chúng lớn hơn phạm vi số mà chúng tạo ra (ví dụ: cặp song sinh Mersenne), và thậm chí sau đó chuỗi số ngẫu nhiên hoàn toàn được xác định bởi hạt giống.
Konrad Rudolph

9

Hãy xem chính xác những gì bạn đang làm:

  • Bạn lặp qua từng pixel một
  • Đối với mỗi pixel, bạn sử dụng phép nối tọa độ của nó làm hạt giống
  • Sau đó, bạn bắt đầu một ngẫu nhiên mới từ hạt giống đã cho và lấy ra 3 số

Tất cả điều này nghe có vẻ ổn, nhưng bạn đang nhận được một mẫu vì:

Pixel ở mức 1,11 và pixel ở 11,1 đều được chọn là số 111, vì vậy chúng chắc chắn có cùng màu.

Ngoài ra, miễn là bạn luôn quay vòng theo cùng một cách, bạn chỉ có thể sử dụng một trình tạo, không cần sử dụng một trình tạo cho mỗi pixel. Một cho toàn bộ hình ảnh sẽ làm! Vẫn sẽ có một số kiểu mẫu vì giả ngẫu nhiên. @David_Lively nói đúng về việc sử dụng một số thuật toán Nhiễu, nó sẽ làm cho nó trông ngẫu nhiên hơn.


Điều này là chế độ xem của hình ảnh sẽ có thể thay đổi (hơn nữa để tọa độ tích cực). Vì vậy, phương pháp này sẽ không hoạt động hoàn toàn
chuồn chuồn

4
Trên thực tế, tất cả các điều này không có vẻ ổn - việc tạo ra RNG xác định cho mỗi pixel là một chiến lược khủng khiếp trừ khi chính hạt giống đó là từ RNG mật mã (và thậm chí sau đó nó là một chiến lược có lợi cho các lý do không liên quan đến phân phối).
Konrad Rudolph

bạn có thể bao gồm cách nối chính xác các số trong ngữ cảnh này. I E. (x + y * chiều rộng)
Taemyr

1

Tạo một trình tạo màu, sau đó tạo màu cho gạch của bạn. Hạt giống chỉ một lần! Bạn không cần phải gieo hạt nhiều hơn thế, ít nhất là trên mỗi ô.

public class RandomColorGenerator {
  private final int minValue;
  private final int range;
  private final Random random;
  public RandomColorGenerator(int minValue, int maxValue, Random random) {
    if (minValue > maxValue || (long)maxValue - (long)minValue > (long)Integer.MAX_VALUE) {
      throw new IllegalArgumentException();
    }
    this.minValue = minValue;
    this.range = maxValue - minValue + 1;
    this.random = Objects.requireNonNull(random);
  }

  public int nextColor() {
    int r = minValue + random.nextInt(range);
    int g = minValue + random.nextInt(range);
    int b = minValue + random.nextInt(range);
    return -Color.rgb888(r, g, b);
  }
}

public class Tile {
  private final int[][] colors;
  public Tile(int width, int height, RandomColorGenerator colorGenerator) {
    this.colors = new int[width][height];
    for (int x = 0; x < width; x++) {
      for (int y = 0; y < height; y++) {
        this.colors[x][y] = colorGenerator.nextColor();
      }
    }
  }

  public int getColor(int x, int y) {
    return colors[x][y];
  }
}

Và cách sử dụng sẽ như sau:

RandomColorGenerator generator = new RandomColorGenerator(1, 100, new Random(0xcafebabe));
Tile tile = new Tile(300, 200, generator);
...
// getting the color for x, y:
tile.getColor(x, y);

Với điều này nếu bạn không hài lòng với kết quả, chỉ cần thay đổi Randomhạt giống. Ngoài ra, bạn chỉ phải lưu trữ / truyền đạt hạt giống và kích thước để tất cả các máy khách có cùng một hình ảnh.


1

Thay vì sử dụng Ngẫu nhiên, hãy xem xét sử dụng thông báo băm như MD5. Nó cung cấp một giá trị 'ngẫu nhiên' khó dự đoán dựa trên một đầu vào nhất định, nhưng luôn luôn có cùng giá trị cho cùng một đầu vào.

Thí dụ:

public static int TileColor(int x, int y){
        final MessageDigest md = MessageDigest.getInstance("MD5");
        final ByteBuffer b = ByteBuffer.allocate(8);
        b.putInt(x).putInt(y);
        final byte[] digest = md.digest(b.array());
        return -Color.rgb888(digest[0], digest[1], digest[2]);
}

LƯU Ý: Tôi không biết Color.rgb888 (..) đến từ đâu, vì vậy tôi không biết phạm vi được phép là gì. 0-255 là bình thường mặc dù.

Những cải tiến cần xem xét:

  • Tạo các biến MessageDigest và ByteBuffer bên ngoài lớp, để cải thiện hiệu suất. Bạn sẽ cần phải thiết lập lại ByteBuffer để làm điều đó và phương thức sẽ không còn là Thread an toàn.
  • Mảng digest sẽ chứa các giá trị byte 0-255, nếu bạn muốn các phạm vi khác, bạn sẽ phải thực hiện một số phép toán trên chúng.
  • Nếu bạn muốn kết quả 'ngẫu nhiên' khác nhau, bạn có thể thêm một số loại 'hạt giống'. Ví dụ: thay đổi thành ByteBuffer.allocate (12) và thêm .putInt (seed).

1

Những người khác đã chỉ ra rằng một cách để có được hành vi bạn muốn là sử dụng hàm băm, hay còn gọi là "chức năng tiêu hóa thông điệp". Vấn đề là những thứ này thường dựa trên các thuật toán như MD5, được bảo mật bằng mật mã (tức là thực sự, thực sự, thực sự ngẫu nhiên) nhưng rất chậm. Nếu bạn sử dụng hàm băm mật mã mỗi khi bạn cần một pixel ngẫu nhiên, bạn sẽ gặp phải các vấn đề hiệu suất khá nghiêm trọng.

Tuy nhiên, có các hàm băm không mã hóa có thể tạo ra các giá trị đủ ngẫu nhiên cho mục đích của bạn trong khi cũng nhanh. Người mà tôi thường với tới là murmurhash . Tôi không phải là người dùng Java nhưng có vẻ như có ít nhất một triển khai Java . Nếu bạn thấy rằng bạn thực sự cần phải tạo từng pixel từ tọa độ của nó, thay vì tạo tất cả chúng cùng một lúc và lưu trữ chúng trong một kết cấu, thì đây sẽ là một cách tốt để làm điều đó.


1

Tôi sẽ sử dụng một số nguyên tố trên 2000 (độ phân giải điển hình tối đa)
Điều này sẽ giảm thiểu (hoặc loại bỏ các hạt giống trùng lặp)

public class Generate {

    static Random Random;

    public static int TileColor(int x, int y){          
        Random = new Random(x + 2213 * y);
        int b = 1 + Random.nextInt(50);
        int g = 1 + Random.nextInt(50);
        int r = 1 + Random.nextInt(50);
        int color = -Color.rgb888(r, g, b);
        return color;
    }
}

0

Randomlà đủ ngẫu nhiên. Bạn đang sử dụng sai vì hai lý do chính.

  • Nó không được thiết kế để được lặp lại nhiều lần. Các thuộc tính ngẫu nhiên chỉ giữ cho một chuỗi các số ngẫu nhiên.
  • Có mối tương quan rất lớn Integer.valueOf(Integer.toString(x)+Integer.toString(y))giữa các pixel mà bạn đang gieo hạt.

Tôi chỉ sử dụng một số biến thể của mã sau đây, nơi bạn có thể chọn hàm băm (không sử dụng Integer.getHashCode) từ các câu trả lời tại /programming/9624963/java-simplest-integer- băm

public static int TileColor(int x, int y) {
    return hash(x ^ hash(y));
}

hàm băm có thể ở đâu


0

Bạn có thể thử sử dụng thời gian hiện tại của hệ thống như một hạt giống như thế này:

Random random = new Random(System.currentTimeMillis())

Hy vọng nó tạo ra một giá trị ngẫu nhiên hơn.


Tuy nhiên, điều đó không tạo ra giá trị dame cho tọa độ dame.
chuồn chuồn

0

Đây là một hàm shader tĩnh một dòng mà tôi đã nghĩ ra - poltergeist (Noisy Ghost).

Nó có tọa độ 2D và hạt giống, và hiển thị đơn điệu theo yêu cầu. Nó chạy ở khung hình / giây thời gian thực, bất kể độ phân giải màn hình. Đây là những gì GPU dành cho.

// poltergeist (noisy ghost) pseudo-random noise generator function
// dominic.cerisano@standard3d.com 03/24/2015

precision highp float;

float poltergeist(in vec2 coordinate, in float seed) 
{
    return fract(sin(dot(coordinate*seed, vec2(12.9898, 78.233)))*43758.5453); 
}

void mainImage(out vec4 fragColor, in vec2 fragCoord) 
{   
    fragColor = vec4(poltergeist(fragCoord, iGlobalTime)); 
}

Bất kỳ độ phân giải, bất kỳ kết cấu nào, trên bất kỳ thiết bị nào (cả thiết bị di động) đều hỗ trợ GL (khá giống với màn hình).

Xem nó chạy ở đây, ngay bây giờ!

https://www.shadertoy.com/view/ltB3zD

Bạn có thể dễ dàng đưa trình đổ bóng này vào chương trình java của mình bằng opengl tiêu chuẩn hoặc trong bất kỳ trình duyệt nào sử dụng webgl tiêu chuẩn.

Chỉ để cho vui, tôi ném xuống găng tay cho bất cứ ai đánh bại Poltergeist về chất lượng và hiệu suất trên tất cả các thiết bị. Quy tắc ma ồn ào! Bất khả chiến bại!

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.