¿Cuál es el origen de este GLSL rand() one-liner?
shader prng (4)
He visto este generador de números pseudoaleatorios para utilizar en sombreadores a los que se hace referencia aquí y allá en la web :
float rand(vec2 co){
return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);
}
Se le llama "canónico", o "un trazador de líneas que encontré en la web en algún lugar".
¿Cuál es el origen de esta función? ¿Son los valores constantes tan arbitrarios como parecen o hay algo de arte en su selección? ¿Hay alguna discusión sobre los méritos de esta función?
EDITAR: La referencia más antigua a esta función con la que me he encontrado es este archivo desde febrero de 2008 , la página original ahora se ha ido de la web. Pero no hay más discusión allí que en cualquier otro lado.
El origen es probablemente el artículo: "Al generar números aleatorios, con ayuda de y = [(a + x) sin (bx)] mod 1", WJJ Rey, 22 ° Encuentro Europeo de Estadísticos y la 7ma Conferencia de Vilnius sobre Teoría de Probabilidad y Estadística matemática, agosto de 1998
EDIT: dado que no puedo encontrar una copia de este documento y la referencia "TestU01" puede no ser clara, este es el esquema descrito en TestU01 en 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;
}
donde el único valor constante recomendado es el B1.
Tenga en cuenta que esto es para una transmisión. La conversión a un 1D hash ''n'' se convierte en la grilla entera. Así que supongo que alguien vio esto y convirtió ''t'' en una función simple f (x, y). Usando las constantes originales anteriores que producirían:
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
}
Pregunta muy interesante!
Estoy tratando de resolver esto mientras escribo la respuesta :) Primero una manera fácil de jugar con él: 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
Entonces, pensemos en lo que intentamos hacer aquí: para dos coordenadas de entrada x, y devolvemos un "número aleatorio". Ahora bien, este no es un número aleatorio. Es lo mismo cada vez que ingresamos el mismo x, y. ¡Es una función hash!
Lo primero que hace la función es pasar de 2d a 1d. Eso no es interesante en sí mismo, pero los números se eligen para que no se repitan normalmente. También tenemos una adición de coma flotante allí. Habrá algunos bits más de y o x, pero los números podrían elegirse justo para hacer una mezcla.
Luego probamos una función de caja negra sin (). ¡Esto dependerá mucho de la implementación!
Por último, amplifica el error en la implementación de sin () multiplicando y tomando la fracción.
No creo que esta sea una buena función hash en el caso general. The sin () es una caja negra, en la GPU, numéricamente. Debería ser posible construir uno mucho mejor tomando casi cualquier función hash y convirtiéndola. La parte difícil es convertir la operación entera típica utilizada en el hash de la CPU en flotación (media o 32 bits) u operaciones de punto fijo, pero debería ser posible.
Una vez más, el problema real con esto como función hash es que sin () es una caja negra.
Tal vez sea un mapeo caótico no recurrente, entonces podría explicar muchas cosas, pero también puede ser solo una manipulación arbitraria con grandes números.
EDITAR: Básicamente, la función fract (sin (x) * 43758.5453) es una función similar a un hash, el sin (x) proporciona una interpolación suave de sin entre -1 a 1, así que sin (x) * 43758.5453 será una interpolación de - 43758.5453 a 43758.5453. Este es un rango bastante grande, por lo que incluso un pequeño paso en x proporcionará un gran paso en el resultado y una gran variación en la parte fraccionaria. El "fract" es necesario para obtener valores en el rango -0.99 ... a 0.999 .... Ahora, cuando tenemos algo así como la función hash, debemos crear una función para hash de producción a partir del vector. La forma más simple es llamar "hash" por separado para x cualquier componente y del vector de entrada. Pero luego, tendremos algunos valores simétricos. Entonces, debemos obtener algún valor del vector, el enfoque es encontrar un vector aleatorio y encontrar un producto "punto" para ese vector, aquí vamos: fract (sin (punto (co.xy, vec2 (12.9898, 78.233))) * 43758.5453); Además, de acuerdo con el vector seleccionado, su longitud debe ser larga para tener varios peroides de la función "sin" después de que se compute el producto "punto".
los valores constantes son arbitrarios, especialmente que son muy grandes, y un par de decimales de los números primos.
un módulo de más de 1 de un seno de amplitud alta multiplicado por 4000 es una función periódica. es como una ventana ciega o un metal corrugado hecho muy pequeño porque se multiplica por 4000, y se convierte en un ángulo por el producto de puntos.
como la función es 2-D, el producto escalar tiene el efecto de convertir la función periódica en oblicua en relación con los ejes X e Y. Por 13/79 relación aproximadamente. Es ineficiente, puedes lograr lo mismo al hacer sinus de (13x + 79y) esto también logrará lo mismo, creo que con menos matemáticas.
Si encuentra el período de la función tanto en X como en Y, puede muestrearlo para que parezca una onda sinusoidal simple nuevamente.
Aquí hay una imagen de ella ampliada en gráfico
No conozco el origen, pero es similar a muchos otros, si lo usaras en gráficos a intervalos regulares, tendería a producir patrones de muaré y podrías ver que eventualmente vuelve a aparecer.