Qual è l'origine di questa riga GLSL rand ()?


92

Ho visto questo generatore di numeri pseudo-casuali da utilizzare negli shader a cui si fa riferimento qua e là in giro per il web :

float rand(vec2 co){
  return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);
}

È variamente chiamato "canonico", o "una battuta che ho trovato da qualche parte sul web".

Qual è l'origine di questa funzione? I valori costanti sono così arbitrari come sembrano o c'è qualche arte nella loro selezione? C'è qualche discussione sui meriti di questa funzione?

EDIT: Il riferimento più antico a questa funzione che ho incontrato è questo archivio del febbraio '08 , la pagina originale ora scomparsa dal web. Ma non se ne discute più che altrove.


È una funzione di rumore, utilizzata per creare terreno generato proceduralmente. simile a qualcosa di simile en.wikipedia.org/wiki/Perlin_noise
foreyez

Risposte:


42

Domanda molto interessante!

Sto cercando di capirlo mentre digito la risposta :) Prima un modo semplice per giocarci: http://www.wolframalpha.com/input/?i=plot%28+mod%28+sin%28x*12.9898 +% 2B + y * 78,233% 29 + * + 43758,5453% 2C1% 29x% 3D0..2% 2C + y% 3D0..2% 29

Quindi pensiamo a quello che stiamo cercando di fare qui: per due coordinate di input x, y restituiamo un "numero casuale". Tuttavia, questo non è un numero casuale. È lo stesso ogni volta che inseriamo la stessa x, y. È una funzione hash!

La prima cosa che fa la funzione è passare da 2d a 1d. Questo non è interessante di per sé, ma i numeri sono scelti in modo che non si ripetano normalmente. Inoltre abbiamo un'aggiunta in virgola mobile lì. Ci saranno ancora alcuni bit da y o x, ma i numeri potrebbero essere scelti correttamente in modo da fare un mix.

Quindi campioniamo una funzione sin () della scatola nera. Questo dipenderà molto dall'implementazione!

Infine amplifica l'errore nell'implementazione di sin () moltiplicando e prendendo la frazione.

Non penso che questa sia una buona funzione hash nel caso generale. Il sin () è una scatola nera, sulla GPU, numericamente. Dovrebbe essere possibile costruirne uno molto migliore prendendo quasi tutte le funzioni hash e convertendole. La parte difficile è trasformare la tipica operazione intera usata nell'hashing della CPU in operazioni float (metà o 32 bit) o ​​in virgola fissa, ma dovrebbe essere possibile.

Ancora una volta, il vero problema con questa funzione hash è che sin () è una scatola nera.


1
Questo non risponde alla domanda sull'origine, ma non credo che sia davvero possibile rispondere. Accetterò questa risposta a causa del grafico illustrativo.
Grumdrig

19

L'origine è probabilmente l'articolo: "Sulla generazione di numeri casuali, con l'aiuto di y = [(a + x) sin (bx)] mod 1", WJJ Rey, 22nd European Meeting of Statisticians and the 7th Vilnius Conference on Probability Theory and Statistica matematica, agosto 1998

EDIT: Dal momento che non riesco a trovare una copia di questo documento e il riferimento "TestU01" potrebbe non essere chiaro, ecco lo schema descritto in TestU01 in pseudo-C:

#define A1 ???
#define A2 ???
#define B1 pi*(sqrt(5.0)-1)/2
#define B2 ???

uint32_t n;   // position in the stream

double next() {
  double t = fract(A1     * sin(B1*n));
  double u = fract((A2+t) * sin(B2*t));
  n++;
  return u;
} 

dove l'unico valore di costante consigliato è B1.

Notare che questo è per un flusso. La conversione in un hash 1D 'n' diventa la griglia intera. Quindi la mia ipotesi è che qualcuno abbia visto questo e abbia convertito 't' in una semplice funzione f (x, y). Utilizzando le costanti originali di cui sopra che produrrebbe:

float hash(vec2 co){
  float t = 12.9898*co.x + 78.233*co.y; 
  return fract((A2+t) * sin(t));  // any B2 is folded into 't' computation
}

3
Davvero molto interessante! Ho trovato un articolo che fa riferimento sia ad esso che al giornale stesso su Google Libri, ma sembra che il discorso o il documento stesso non fossero inclusi nel giornale.
Grumdrig

1
Inoltre, dal titolo sembrerebbe che la funzione di cui sto chiedendo debba tornare fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * (co.xy + vec2(43758.5453, SOMENUMBER))per conformarsi alla funzione di cui tratta l'articolo.
Grumdrig

E ancora una cosa, se questa è davvero l'origine dell'uso della funzione, la questione dell'origine dei numeri magici (scelta di ae b) utilizzati più e più volte rimane, ma potrebbe essere stata utilizzata nel documento che citi.
Grumdrig

Neanche io riesco più a trovare il giornale. (modifica: stesso documento del link sopra)
MB Reynolds

Aggiorna la risposta con maggiori informazioni.
MB Reynolds

8

i valori costanti sono arbitrari, soprattutto perché sono molto grandi e distano un paio di decimali dai numeri primi.

un modulo superiore a 1 di un seno di ampiezza elevata moltiplicato per 4000 è una funzione periodica. è come una tenda da finestra o un metallo ondulato reso molto piccolo perché moltiplicato per 4000 e ruotato di un angolo dal prodotto punto.

poiché la funzione è 2-D, il prodotto scalare ha l'effetto di ruotare la funzione periodica obliqua rispetto agli assi X e Y. Con rapporto 13/79 circa. È inefficiente, puoi effettivamente ottenere lo stesso facendo sinus di (13x + 79y), questo otterrà anche la stessa cosa penso con meno matematica ..

Se trovi il periodo della funzione sia in X che in Y, puoi campionarlo in modo che assomigli di nuovo a una semplice onda sinusoidale.

Ecco un'immagine di esso ingrandita nel grafico

Non conosco l'origine ma è simile a molti altri, se lo usassi nella grafica a intervalli regolari tenderebbe a produrre moiré e potresti vedere che alla fine va di nuovo in giro.


Ma sulle GPU X e Y vanno da 0 a 1 e sembra molto più casuale se cambi il grafico. So che suona come un'affermazione, ma in realtà è una domanda, perché la mia educazione in matematica è terminata a 18 anni.
Archi

lo so, ho solo ingrandito in modo che tu possa vedere che la funzione casuale è di quella forma eccetto che le creste cambiano molto velocemente, tranne che devi ingrandire piccolo per vedere i cambiamenti ... puoi immaginare che prendere punti sulle creste darà numeri abbastanza casuali da 0 a 1 altezza per valori da 1 a 1 xey.
aliential

Oh, capisco, e questo sembra molto logico per qualsiasi generazione di numeri casuali che al suo centro utilizzi una funzione peccato
Stringhe

2
è essenzialmente uno zigzag lineare, e il peccato dovrebbe aggiungere un po 'di variazione, è come se qualcuno stesse sfogliando un mazzo di carte da uno a 10 molto velocemente avanti e indietro davanti a te e dovresti provare Finire di prendere uno schema di numeri dalle carte, sarebbero numeri casuali perché sarebbe sfogliando molto velocemente che avrebbe potuto ottenere uno schema solo scegliendo le carte in una sincronizzazione esatta rispetto alla velocità con cui le carte giravano.
alienziale

Solo una nota, non sarebbe più veloce da fare (13x + 79y)poiché dot(XY, AB)farà esattamente ciò che descrivi, poiché è il prodotto x,y dot 13, 79 = (13x + 79y)
puntato

1

Forse è una mappatura caotica non ricorrente, quindi potrebbe spiegare molte cose, ma può anche essere solo una manipolazione arbitraria con grandi numeri.

EDIT: Fondamentalmente, la funzione fract (sin (x) * 43758.5453) è una semplice funzione simile a un hash, il sin (x) fornisce un'interpolazione sin uniforme tra -1 e 1, quindi sin (x) * 43758.5453 sarà interpolazione da - 43758.5453 a 43758.5453. Questo è un intervallo abbastanza ampio, quindi anche un piccolo passo in x fornirà un grande passo nel risultato e una variazione davvero grande nella parte frazionaria. Il "fract" è necessario per ottenere valori nell'intervallo da -0,99 ... a 0,999 .... Ora, quando abbiamo qualcosa come la funzione hash, dovremmo creare una funzione per l'hash di produzione dal vettore. Il modo più semplice è chiamare "hash" separatamente per x qualsiasi componente y del vettore di input. Ma poi, avremo alcuni valori simmetrici. Quindi, dovremmo ottenere un valore dal vettore, l'approccio è trovare un vettore casuale e trovare il prodotto "punto" per quel vettore, qui andiamo: fract (sin (punto (co.xy, vec2 (12.9898,78.233))) * 43758.5453); Inoltre, in base al vettore selezionato, la sua lunghezza dovrebbe essere abbastanza lunga per avere diversi peroidi della funzione "sin" dopo che sarà calcolato il prodotto "punto".


ma poi dovrebbe funzionare anche 4e5, non capisco perché il numero magico 43758.5453. (inoltre, compenserei x con un numero frazionario per evitare rand (0) = 0.
Fabrice NEYRET

1
Penso che con 4e5 non otterrai così tante variazioni dei bit frazionari, ti darà sempre lo stesso valore. Quindi, devono essere soddisfatte due condizioni, abbastanza grandi e avere una variazione abbastanza buona delle parti frazionarie.
Roman

cosa intendi con "ti darà sempre lo stesso valore"? (se intendi che prenderà sempre le stesse cifre, in primo luogo, sono ancora caotiche, in secondo luogo, float sono memorizzati come m * 2 ^ p, non 10 ^ p, quindi * 4e5 rimescola ancora i bit).
Fabrice NEYRET

Pensavo avessi scritto una rappresentazione esponenziale del numero, 4 * 10 ^ 5, quindi sin (x) * 4e5 ti darà un numero non così caotico. Sono d'accordo che anche i bit frazionari dell'onda del peccato ti daranno un buon chatoic.
Roman

Ma allora dipende dall'intervallo di x, voglio dire se la funzione dovrebbe essere robusta per valori piccoli (-0.001, 0.001) e grandi (-1, 1). Puoi provare a vedere la differenza con fract (sin (x /1000.0) * 43758.5453); e fract (sin (x /1000.0) * 4e5) ;, dove x è compreso nell'intervallo [-1., 1.]. Nella seconda variante l'immagine sarà più monotona (almeno vedo la differenza nello shader). Ma, in generale, sono d'accordo sul fatto che puoi ancora usare 4e5 e avere risultati abbastanza buoni.
Roman
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.