Funciones aleatorias / de ruido para GLSL

179

Como los proveedores de controladores de GPU generalmente no se molestan en implementar noiseXen GLSL, estoy buscando un conjunto de funciones de utilidad de "aleatorización de gráficos de navaja suiza" , preferiblemente optimizado para usar dentro de sombreadores de GPU. Prefiero GLSL, pero codifique cualquier lenguaje que me sirva, estoy de acuerdo en traducirlo por mi cuenta a GLSL.

Específicamente, esperaría:

a) Funciones pseudoaleatorias - N-dimensional, distribución uniforme sobre [-1,1] o sobre [0,1], calculada a partir de semilla M-dimensional (idealmente es cualquier valor, pero estoy de acuerdo con restringir la semilla a, digamos, 0..1 para una distribución uniforme de resultados). Algo como:

float random  (T seed);
vec2  random2 (T seed);
vec3  random3 (T seed);
vec4  random4 (T seed);
// T being either float, vec2, vec3, vec4 - ideally.

b) Ruido continuo como el ruido de Perlin - nuevamente, distribución N-dimensional, + - uniforme, con un conjunto restringido de valores y, bueno, luciendo bien (algunas opciones para configurar la apariencia como los niveles de Perlin también podrían ser útiles). Esperaría firmas como:

float noise  (T coord, TT seed);
vec2  noise2 (T coord, TT seed);
// ...

No estoy muy interesado en la teoría de la generación de números aleatorios, por lo que preferiría una solución prefabricada , pero también agradecería respuestas como "aquí hay un rand 1D muy bueno y eficiente (), y déjame explicarte cómo hacer un buen rand N-dimensional () encima ... " .

Kos
fuente

Respuestas:

263

Para cosas muy simples de aspecto pseudoaleatorio, utilizo esta línea que encontré en Internet en alguna parte:

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

También puede generar una textura de ruido utilizando el PRNG que desee, luego subirlo de la manera normal y muestrear los valores en su sombreador; Puedo desenterrar una muestra de código más tarde si lo desea.

Además, consulte este archivo para ver implementaciones GLSL de ruido Perlin y Simplex, por Stefan Gustavson.

appas
fuente
14
¿Cómo lo usas vec2 co? es el rango? ¿semilla?
Ross
12
Tenga cuidado con los sombreadores de fragmentos de punto flotante de baja precisión con este algoritmo (por ejemplo, ARM Mali de S3): stackoverflow.com/questions/11293628/… . El proyecto github.com/ashima/webgl-noise no parece tener problemas de lowp.
PT
44
FWIW, la función descrita aquí se discute con más detalle aquí .
Loomchild
3
FYI: La distribución de esa función es horrible.
Tara
3
Soy nuevo en GLSL, ¿alguien puede explicar por qué co.xyse usa, en lugar de co?
Kelin
83

Se me ocurre que podrías usar una simple función hash de enteros e insertar el resultado en la mantisa de un flotador. IIRC, la especificación GLSL garantiza enteros sin signo de 32 bits y representación flotante binaria IEEE32, por lo que debe ser perfectamente portátil.

Lo intenté hace un momento. Los resultados son muy buenos: se ve exactamente como estático con cada entrada que probé, sin patrones visibles en absoluto. Por el contrario, el popular fragmento sin / fract tiene líneas diagonales bastante pronunciadas en mi GPU con las mismas entradas.

Una desventaja es que requiere GLSL v3.30. Y aunque parece lo suficientemente rápido, no he cuantificado empíricamente su rendimiento. Shader Analyzer de AMD reclama 13.33 píxeles por reloj para la versión vec2 en un HD5870. Contraste con 16 píxeles por reloj para el fragmento sin / fract. Entonces, ciertamente es un poco más lento.

Aquí está mi implementación. Lo dejé en varias permutaciones de la idea para que sea más fácil derivar sus propias funciones.

/*
    static.frag
    by Spatial
    05 July 2013
*/

#version 330 core

uniform float time;
out vec4 fragment;



// A single iteration of Bob Jenkins' One-At-A-Time hashing algorithm.
uint hash( uint x ) {
    x += ( x << 10u );
    x ^= ( x >>  6u );
    x += ( x <<  3u );
    x ^= ( x >> 11u );
    x += ( x << 15u );
    return x;
}



// Compound versions of the hashing algorithm I whipped together.
uint hash( uvec2 v ) { return hash( v.x ^ hash(v.y)                         ); }
uint hash( uvec3 v ) { return hash( v.x ^ hash(v.y) ^ hash(v.z)             ); }
uint hash( uvec4 v ) { return hash( v.x ^ hash(v.y) ^ hash(v.z) ^ hash(v.w) ); }



// Construct a float with half-open range [0:1] using low 23 bits.
// All zeroes yields 0.0, all ones yields the next smallest representable value below 1.0.
float floatConstruct( uint m ) {
    const uint ieeeMantissa = 0x007FFFFFu; // binary32 mantissa bitmask
    const uint ieeeOne      = 0x3F800000u; // 1.0 in IEEE binary32

    m &= ieeeMantissa;                     // Keep only mantissa bits (fractional part)
    m |= ieeeOne;                          // Add fractional part to 1.0

    float  f = uintBitsToFloat( m );       // Range [1:2]
    return f - 1.0;                        // Range [0:1]
}



// Pseudo-random value in half-open range [0:1].
float random( float x ) { return floatConstruct(hash(floatBitsToUint(x))); }
float random( vec2  v ) { return floatConstruct(hash(floatBitsToUint(v))); }
float random( vec3  v ) { return floatConstruct(hash(floatBitsToUint(v))); }
float random( vec4  v ) { return floatConstruct(hash(floatBitsToUint(v))); }





void main()
{
    vec3  inputs = vec3( gl_FragCoord.xy, time ); // Spatial and temporal inputs
    float rand   = random( inputs );              // Random per-pixel value
    vec3  luma   = vec3( rand );                  // Expand to RGB

    fragment = vec4( luma, 1.0 );
}

Captura de pantalla:

Salida de random (vec3) en static.frag

Inspeccioné la captura de pantalla en un programa de edición de imágenes. Hay 256 colores y el valor promedio es 127, lo que significa que la distribución es uniforme y cubre el rango esperado.

Espacial
fuente
17
+1 para una buena idea e implementación. Yo cuestionaría la afirmación de que debido a que hay 256 colores y el valor promedio es 127, la distribución debe ser uniforme (en sentido estricto). Puede ser uniforme, pero no creo que lo sepamos todavía. Por ejemplo, una distribución de curva de campana podría tener el mismo promedio y número de colores, pero no sería uniforme.
LarsH
Voté esto por la razón dada por @LarsH.
Autumnsault
Bueno, es lo suficientemente bueno para la mayoría de las aplicaciones que no necesitan uniformidad. :-)
itmuckel
55
Parece ser muy uniforme, según mi percepción del histograma ... Diría que es lo suficientemente bueno para la mayoría de las aplicaciones que también necesitan uniformidad. (Los únicos valores que parecen generarse menos que los demás son 0 y 255)
leviathanbadger
Gracias. Mi probabilidad es oxidada. Habiendo examinado el conjunto de instrucciones GCN, esto debería ser muy rápido en el hardware más nuevo porque admiten directamente operaciones de campo de bits en sus conjuntos de instrucciones. Las pruebas que realicé en hardware antiguo.
Espacial
73

La implementación de Gustavson usa una textura 1D

No, no lo hace, no desde 2005. Es solo que la gente insiste en descargar la versión anterior. La versión que se encuentra en el enlace que proporcionó utiliza solo texturas 2D de 8 bits.

La nueva versión de Ian McEwan de Ashima y yo no utiliza una textura, pero funciona a la mitad de la velocidad en plataformas de escritorio típicas con mucho ancho de banda de textura. En plataformas móviles, la versión sin textura puede ser más rápida porque la textura es a menudo un cuello de botella significativo.

Nuestro repositorio fuente activamente mantenido es:

https://github.com/ashima/webgl-noise

Aquí se encuentra una colección de las versiones sin ruido y de textura que usan ruido (usando solo texturas 2D):

http://www.itn.liu.se/~stegu/simplexnoise/GLSL-noise-vs-noise.zip

Si tiene alguna pregunta específica, no dude en enviarme un correo electrónico directamente (puede encontrar mi dirección de correo electrónico en las classicnoise*.glslfuentes).

Stefan Gustavson
fuente
44
Sí, la implementación a la que me refiero, su código en davidcornette.com al que @dep se vinculó, usa una textura 1D: glBindTexture(GL_TEXTURE_1D, *texID);etc. No está claro qué quiere decir con "el enlace que proporcionó", ya que cita mi respuesta pero esa respuesta no se vinculó a su implementación. Actualizaré mi respuesta para aclarar a qué me refiero y reflejar la nueva información que ha proporcionado. Caracterizar a las personas como "insistentes" en descargar la versión anterior es una distorsión que no le da crédito.
LarsH
1
PD: Puede escribir a David Cornette (tiene información de contacto en davidcornette.com ) y pedirle que cambie su enlace en davidcornette.com/glsl/links.html para vincular a su repositorio fuente. También le enviaré un correo electrónico.
LarsH
1
PPS ¿Puede aclarar qué versión usa solo texturas 2D de 8 bits? Parece que podría ser una buena opción para ciertas plataformas ...
LarsH
31

Ruido de oro

// Gold Noise ©2015 [email protected]
// - based on the Golden Ratio
// - uniform normalized distribution
// - fastest static noise generator function (also runs at low precision)

float PHI = 1.61803398874989484820459;  // Φ = Golden Ratio   

float gold_noise(in vec2 xy, in float seed){
       return fract(tan(distance(xy*PHI, xy)*seed)*xy.x);
}

¡Vea Gold Noise en su navegador ahora mismo!

ingrese la descripción de la imagen aquí

Esta función ha mejorado la distribución aleatoria sobre la función actual en la respuesta de @appas a partir del 9 de septiembre de 2017:

ingrese la descripción de la imagen aquí

La función @appas también está incompleta, dado que no se suministra semilla (uv no es una semilla, lo mismo para cada cuadro), y no funciona con conjuntos de chips de baja precisión. Gold Noise funciona con baja precisión por defecto (mucho más rápido).

Dominic Cerisano
fuente
Gracias por publicar esto. ¿Consideraría publicar una versión ejecutable, por ejemplo, en shadertoy.com, para que la gente pueda probarla en el navegador?
LarsH
@snb Shadertoy.com está en mantenimiento este mes, tenga paciencia. También documenté claramente el requisito de valores de semilla irracionales en el código allí. Dado que el ruido de oro devuelve un escalar, construir vectores con él es trivial y también está documentado en el código.
Dominic Cerisano
77
No creo que esto sea diferente a otras funciones de ruido. ¿Cuál es su prueba de que tiene propiedades especiales? el hecho de que uses un montón de números irracionales no lo hace especial.
M.kazem Akhgary
2
@Dominic: "Tiene una distribución superior a funciones similares": esto tiene que ser probado. tan () está realmente mal acondicionado. Es muy probable que tanto tan () cerca de pi / 2 como sqrt () cerca de cero produzcan resultados diferentes en diferentes hardwares ya que todas las fracturas (no lineales * grandes) se basan en bits menos significativos. Los valores de entrada pequeños o altos también lo afectarán. Además, la dinámica de bits probablemente varía mucho dependiendo de las ubicaciones.
Fabrice NEYRET
2
NB: Hoy en día, GLSL tiene números enteros, por lo que ya no hay ninguna razón para no usar generadores hash "serios" basados ​​en int cuando se requiere una distribución de calidad (y dinámica), con rendimientos similares. (excepto para dispositivos de muy baja gama).
Fabrice NEYRET
12

También hay una buena implementación descrita aquí por McEwan y @StefanGustavson que se parece al ruido de Perlin, pero "no requiere ninguna configuración, es decir, no texturas ni matrices uniformes. Simplemente agréguelo al código fuente de su sombreador y llámelo donde quiera".

Eso es muy útil, especialmente dado que la implementación anterior de Gustavson, a la que @dep se vinculó, usa una textura 1D, que no es compatible con GLSL ES (el lenguaje de sombreado de WebGL).

LarsH
fuente
1
¡Esta es la mejor respuesta a la solicitud de tipo de ruido b) de OP! Aquí hay un enlace directo github.com/ashima/webgl-noise . Hay versiones 2d, 3d y 4d listas como código GLSL 120.
user362515
3

Usa esto:

highp float rand(vec2 co)
{
    highp float a = 12.9898;
    highp float b = 78.233;
    highp float c = 43758.5453;
    highp float dt= dot(co.xy ,vec2(a,b));
    highp float sn= mod(dt,3.14);
    return fract(sin(sn) * c);
}

No uses esto:

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

Puede encontrar la explicación en Mejoras al canon de una línea GLSL rand () para OpenGL ES 2.0

hoangdado
fuente
Leí el artículo pero todavía no estoy seguro, ¿es 3.14 en moduna aproximación de pi?
Kaan E.
2

Acabo de encontrar esta versión de ruido 3D para GPU, es una de las más rápidas disponibles:

#ifndef __noise_hlsl_
#define __noise_hlsl_

// hash based 3d value noise
// function taken from https://www.shadertoy.com/view/XslGRr
// Created by inigo quilez - iq/2013
// License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.

// ported from GLSL to HLSL

float hash( float n )
{
    return frac(sin(n)*43758.5453);
}

float noise( float3 x )
{
    // The noise function returns a value in the range -1.0f -> 1.0f

    float3 p = floor(x);
    float3 f = frac(x);

    f       = f*f*(3.0-2.0*f);
    float n = p.x + p.y*57.0 + 113.0*p.z;

    return lerp(lerp(lerp( hash(n+0.0), hash(n+1.0),f.x),
                   lerp( hash(n+57.0), hash(n+58.0),f.x),f.y),
               lerp(lerp( hash(n+113.0), hash(n+114.0),f.x),
                   lerp( hash(n+170.0), hash(n+171.0),f.x),f.y),f.z);
}

#endif
comprensible
fuente
1
Gold Noise (arriba) es obviamente más rápido, ya que tiene muchas menos operaciones y solo realiza un hash: este llama a su función hash 8 veces, mientras realiza interpolaciones lineales anidadas (lerps). También éste tiene una distribución inferior, especialmente a baja precisión.
Dominic Cerisano
1
Oh, buen punto, es un gráfico de tipo ruido perlin de shadowrtoh por Inigo Quilez. Nice code Dominic ill check it l8r
com.prehensible
@Fabrice Parece que no comprende la pregunta del OP, mi respuesta, mi código o mi comentario. Gold Noise es continuo según la definición del OP: acepta uv y una semilla y lo prueba al proporcionar un sombreador. Todo sobre tu comentario está mal. Sigue confundiendo funciones hash con funciones de ruido pseudoaleatorio. Ellos no son los mismos. Las funciones de ruido no tienen el requisito de generar identificadores únicos como las funciones hash (el punto completo real del hash).
Dominic Cerisano
Por favor, por favor, Dominic, lee más y aprende más antes de reclamar cosas sobre términos que crees que entiendes mientras no sea el caso. No solo estos términos son totalmente precisos y bien definidos en la literatura, además trabajo en el campo, sino que también el OP demuestra que entendió los términos por los ejemplos que dio después. Sugerencia: "continuo" + "ruido" + "como Perlin". en.wikipedia.org/wiki/Perlin_noise
Fabrice NEYRET
Continuo es solo un caso de agregar una cláusula de bucle, muchas funciones de ruido se repiten y se degradan después de cierta manera debido al redondeo de bits, especialmente para gráficos. Chicos, es solo una comunicación de ustedes, usen su tiempo para investigaciones importantes.
com.prehensible
1

Una versión recta y dentada de 1d Perlin, esencialmente un zigzag lfo aleatorio.

half  rn(float xx){         
    half x0=floor(xx);
    half x1=x0+1;
    half v0 = frac(sin (x0*.014686)*31718.927+x0);
    half v1 = frac(sin (x1*.014686)*31718.927+x1);          

    return (v0*(1-frac(xx))+v1*(frac(xx)))*2-1*sin(xx);
}

También he encontrado el ruido 1-2-3-4d de Perlin en el sitio web tutorial del propietario del sombreador de sombras inigo quilez perlin, y voronoi y demás, tiene implementaciones y códigos rápidos para ellos.

comprensible
fuente
1

hash: Hoy en día, webGL2.0 está allí, por lo que los enteros están disponibles en (w) GLSL. -> para un hash portátil de calidad (a un costo similar al de los hashes de flotación feos) ahora podemos usar técnicas de hash "serias". IQ implementó algunos en https://www.shadertoy.com/view/XlXcW4 (y más)

P.ej:

  const uint k = 1103515245U;  // GLIB C
//const uint k = 134775813U;   // Delphi and Turbo Pascal
//const uint k = 20170906U;    // Today's date (use three days ago's dateif you want a prime)
//const uint k = 1664525U;     // Numerical Recipes

vec3 hash( uvec3 x )
{
    x = ((x>>8U)^x.yzx)*k;
    x = ((x>>8U)^x.yzx)*k;
    x = ((x>>8U)^x.yzx)*k;

    return vec3(x)*(1.0/float(0xffffffffU));
}
Fabrice NEYRET
fuente
0

Vea a continuación un ejemplo de cómo agregar ruido blanco a la textura renderizada. La solución es usar dos texturas: ruido blanco original y puro, como este: ruido blanco wiki

private static final String VERTEX_SHADER =
    "uniform mat4 uMVPMatrix;\n" +
    "uniform mat4 uMVMatrix;\n" +
    "uniform mat4 uSTMatrix;\n" +
    "attribute vec4 aPosition;\n" +
    "attribute vec4 aTextureCoord;\n" +
    "varying vec2 vTextureCoord;\n" +
    "varying vec4 vInCamPosition;\n" +
    "void main() {\n" +
    "    vTextureCoord = (uSTMatrix * aTextureCoord).xy;\n" +
    "    gl_Position = uMVPMatrix * aPosition;\n" +
    "}\n";

private static final String FRAGMENT_SHADER =
        "precision mediump float;\n" +
        "uniform sampler2D sTextureUnit;\n" +
        "uniform sampler2D sNoiseTextureUnit;\n" +
        "uniform float uNoseFactor;\n" +
        "varying vec2 vTextureCoord;\n" +
        "varying vec4 vInCamPosition;\n" +
        "void main() {\n" +
                "    gl_FragColor = texture2D(sTextureUnit, vTextureCoord);\n" +
                "    vec4 vRandChosenColor = texture2D(sNoiseTextureUnit, fract(vTextureCoord + uNoseFactor));\n" +
                "    gl_FragColor.r += (0.05 * vRandChosenColor.r);\n" +
                "    gl_FragColor.g += (0.05 * vRandChosenColor.g);\n" +
                "    gl_FragColor.b += (0.05 * vRandChosenColor.b);\n" +
        "}\n";

El fragmento compartido contiene el parámetro uNoiseFactor que se actualiza en cada renderizado por la aplicación principal:

float noiseValue = (float)(mRand.nextInt() % 1000)/1000;
int noiseFactorUniformHandle = GLES20.glGetUniformLocation( mProgram, "sNoiseTextureUnit");
GLES20.glUniform1f(noiseFactorUniformHandle, noiseFactor);
klimletov
fuente
0

Traduje una de las implementaciones Java de Ken Perlin a GLSL y la utilicé en un par de proyectos en ShaderToy.

A continuación se muestra la interpretación GLSL que hice:

int b(int N, int B) { return N>>B & 1; }
int T[] = int[](0x15,0x38,0x32,0x2c,0x0d,0x13,0x07,0x2a);
int A[] = int[](0,0,0);

int b(int i, int j, int k, int B) { return T[b(i,B)<<2 | b(j,B)<<1 | b(k,B)]; }

int shuffle(int i, int j, int k) {
    return b(i,j,k,0) + b(j,k,i,1) + b(k,i,j,2) + b(i,j,k,3) +
        b(j,k,i,4) + b(k,i,j,5) + b(i,j,k,6) + b(j,k,i,7) ;
}

float K(int a, vec3 uvw, vec3 ijk)
{
    float s = float(A[0]+A[1]+A[2])/6.0;
    float x = uvw.x - float(A[0]) + s,
        y = uvw.y - float(A[1]) + s,
        z = uvw.z - float(A[2]) + s,
        t = 0.6 - x * x - y * y - z * z;
    int h = shuffle(int(ijk.x) + A[0], int(ijk.y) + A[1], int(ijk.z) + A[2]);
    A[a]++;
    if (t < 0.0)
        return 0.0;
    int b5 = h>>5 & 1, b4 = h>>4 & 1, b3 = h>>3 & 1, b2= h>>2 & 1, b = h & 3;
    float p = b==1?x:b==2?y:z, q = b==1?y:b==2?z:x, r = b==1?z:b==2?x:y;
    p = (b5==b3 ? -p : p); q = (b5==b4 ? -q : q); r = (b5!=(b4^b3) ? -r : r);
    t *= t;
    return 8.0 * t * t * (p + (b==0 ? q+r : b2==0 ? q : r));
}

float noise(float x, float y, float z)
{
    float s = (x + y + z) / 3.0;  
    vec3 ijk = vec3(int(floor(x+s)), int(floor(y+s)), int(floor(z+s)));
    s = float(ijk.x + ijk.y + ijk.z) / 6.0;
    vec3 uvw = vec3(x - float(ijk.x) + s, y - float(ijk.y) + s, z - float(ijk.z) + s);
    A[0] = A[1] = A[2] = 0;
    int hi = uvw.x >= uvw.z ? uvw.x >= uvw.y ? 0 : 1 : uvw.y >= uvw.z ? 1 : 2;
    int lo = uvw.x <  uvw.z ? uvw.x <  uvw.y ? 0 : 1 : uvw.y <  uvw.z ? 1 : 2;
    return K(hi, uvw, ijk) + K(3 - hi - lo, uvw, ijk) + K(lo, uvw, ijk) + K(0, uvw, ijk);
}

Lo traduje del Apéndice B del Capítulo 2 de Noise Hardware de Ken Perlin en esta fuente:

https://www.csee.umbc.edu/~olano/s2002c36/ch02.pdf

Aquí hay una sombra pública que hice en Shader Toy que usa la función de ruido publicado:

https://www.shadertoy.com/view/3slXzM

Algunas otras buenas fuentes que encontré sobre el tema del ruido durante mi investigación incluyen:

https://thebookofshaders.com/11/

https://mzucker.github.io/html/perlin-noise-math-faq.html

https://rmarcus.info/blog/2018/03/04/perlin-noise.html

http://flafla2.github.io/2014/08/09/perlinnoise.html

https://mrl.nyu.edu/~perlin/noise/

https://rmarcus.info/blog/assets/perlin/perlin_paper.pdf

https://developer.nvidia.com/gpugems/GPUGems/gpugems_ch05.html

Recomiendo encarecidamente el libro de sombreadores, ya que no solo proporciona una excelente explicación interactiva del ruido, sino también otros conceptos de sombreadores.

EDITAR:

Es posible que pueda optimizar el código traducido utilizando algunas de las funciones aceleradas por hardware disponibles en GLSL. Actualizaré esta publicación si termino haciendo esto.

Andrew Meservy
fuente
Además, estoy bastante seguro de que el ruido Perlin / Simplex sigue siendo pseudoaleatorio. Por lo que recuerdo, lo interesante es que puedes aplicar capas y "acercar" el ruido a diferentes niveles para que parezca muy fluido. No me cites sobre eso, sino algo en lo que pensar.
Andrew Meservy
@Zibri Desafortunadamente, no estoy muy familiarizado con los comandos directos C o .sh. Pero parece que la función es simplemente un generador de números pseudoaleatorio y no una función de ruido. También tenga en cuenta que los sombreadores de píxeles glsl se ejecutan directamente en la gpu. No tendrá acceso a ninguna de esas bibliotecas adicionales o capacidades de CPU que puedan estar disponibles en C.
Andrew Meservy
El Libro de Shaders tiene una gran explicación sobre cómo Simplex Noise es una versión más eficiente de Perlin Noise debido a la inclinación de la cuadrícula y los cálculos menos necesarios por punto. Definitivamente vale la pena leerlo.
Andrew Meservy
también vea los capítulos sobre el movimiento browniano fractal y voronoise
Andrew Meservy
Andrew Meservy: no se necesitan bibliotecas ... mi función de ruido es muy simple: 2 entradas de 64 bits son el estado x (n) yx (n-1) .la fórmula simple y rápida es x (n + 1) = ROTR ( x (n) + x (n-1), 8). Si clonas mi git y lo ejecutas, lo verás en acción.
Zibri