Crea mapas de ruido sin interrupciones

imagen

Crear una imagen perfecta en Photoshop es fácil: recorte la imagen, tome las partes derecha e inferior recortadas, y luego péguelas hacia la izquierda y la parte superior con la herramienta Desvanecimiento. Pero para la correcta implementación de mapas de ruido sin interrupciones, debe pensar cuidadosamente.

Si tiene una comprensión básica del ruido de Perlin , entonces sabe que consiste en números aleatorios interpolados. Se utiliza principalmente en dos dimensiones. Pero también es útil en una dimensión (por ejemplo, cuando se mueve), en tres dimensiones (transformación cilíndrica y esférica de objetos 3D) e incluso en cuatro o cinco dimensiones.

El ruido de cuatro dimensiones se puede utilizar para crear una imagen 2D perfecta. No es muy común para nosotros pensar en cuatro dimensiones, por lo que tomaremos una dimensión a la vez.

En mis ejemplos, usé ruido simplex de dos octavas. El ruido simplex es más rápido en grandes dimensiones y, debido a su naturaleza triangular, se ve mejor.

Escribí una pequeña función drawNoisepara crear un lienzo y procesar una matriz de píxeles en un bucle.

Ruido unidimensional sin interrupciones


En una dimensión, el ruido es una línea suave infinita (mi implementación del ruido comienza con dos, así que uso una constante como segundo parámetro). Aquí vemos que estos son solo números aleatorios interpolados.

// one dimensional line
fNoiseScale = .02;
drawNoise(function(i,x,y){
    var v = Simplex.noise(
         123+x*fNoiseScale
        ,137 // we just need one dimension so this parameter is a constant
    );
    return v*iSize>y?255:0;
}).img();


Ruido unidimensional

Puede usar esto en animación, contando el valor de ruido cada milisegundo, pero también puede crear un bucle y calcular todos los valores por adelantado. Los valores en la imagen de arriba no giran alrededor de los bordes. Pero implementar la repetibilidad es bastante simple, solo una dimensión más y un ciclo ... o círculo, para eso.

Bucle unidimensional


Para la mayoría de ustedes, el ruido de Perlin se parece a la imagen de abajo.


Si dibujamos un círculo aquí y contamos los valores de ruido en este círculo, obtendríamos un bucle unidimensional.


Ruido con un círculo para crear un bucle unidimensional.

En código, se ve así:

// one dimensional loop
drawNoise(function(i,x,y){
    var fNX = x/iSize // we let the x-offset define the circle
        ,fRdx = fNX*2*Math.PI // a full circle is two pi radians
        ,a = fRdsSin*Math.sin(fRdx)
        ,b = fRdsSin*Math.cos(fRdx)
        ,v = Simplex.noise(
             123+a*fNoiseScale
            ,132+b*fNoiseScale
        )
    ;
    return v*iSize>y?255:0;
}).img().div(2);


Probablemente ya entendiste lo que vamos a hacer. Para recorrer una imagen bidimensional, necesitamos un mapa de ruido tridimensional (al menos).

Tarjeta cilíndrica


Noise Perlin fue creado originalmente para texturas continuas en 3D (la película "Tron"). El mapa de imagen no es una hoja de papel envuelta alrededor de un objeto, sino que se calcula por su ubicación en un campo de ruido tridimensional. Por lo tanto, al cortar el objeto, aún podemos calcular el mapa para la superficie recién creada.

Antes de alcanzar nuestro objetivo final de una imagen perfecta, primero creamos una imagen que se una perfectamente a izquierda y derecha. Esto es similar a un círculo bidimensional para un bucle unidimensional, pero con una dimensión adicional: un cilindro.

// three dimensional cylindrical map
drawNoise(function(i,x,y){
    var fNX = x/iSize
        ,fRdx = fNX*2*Math.PI
        ,a = fRdsSin*Math.sin(fRdx)
        ,b = fRdsSin*Math.cos(fRdx)
        ,v = Simplex.noise(
             123+a*fNoiseScale
            ,132+b*fNoiseScale
            ,312+y*fNoiseScale // similar to the one dimensional loop but we add a third dimension defined by the image y-offset
        )
    ;
    return v*255<<0;
}).img().div(2);


Mapa de ruido cilíndrico

Imagen de mapa esférico


Puede pensar que sería conveniente usar una esfera para crear una imagen perfecta, pero está equivocado.

Haré una pequeña digresión y mostraré cómo se calcula el mapa de imagen esférica y cómo se ve.

// three dimensional spherical map
document.body.addChild('h2').innerText = 'three dimensional spherical map';
fNoiseScale = .1;
var oSpherical = drawNoise(function(i,x,y){
    var  fNX = (x+.5)/iSize // added half a pixel to get the center of the pixel instead of the top-left
        ,fNY = (y+.5)/iSize
        ,fRdx = fNX*2*Math.PI
        ,fRdy = fNY*Math.PI // the vertical offset of a 3D sphere spans only half a circle, so that is one Pi radians
        ,fYSin = Math.sin(fRdy+Math.PI) // a 3D sphere can be seen as a bunch of cicles stacked onto each other, the radius of each of these is defined by the vertical position (again one Pi radians)
        ,a = fRdsSin*Math.sin(fRdx)*fYSin
        ,b = fRdsSin*Math.cos(fRdx)*fYSin
        ,c = fRdsSin*Math.cos(fRdy)
        ,v = Simplex.noise(
             123+a*fNoiseScale
            ,132+b*fNoiseScale
            ,312+c*fNoiseScale
        )
    ;
    return v*255<<0;
}).img();


Mapa de ruido esférico


Esfera con ruido

Mapa panorámico cúbico


La esfera que creamos también se puede usar como panorama si coloca una cámara en el centro de la esfera. Pero la mejor manera sería usar un panorama cúbico, porque tiene muchas menos caras. La esfera se proyecta en los seis lados del cubo, como se muestra en este boceto.


Superposición de una esfera en un cubo

Para cada píxel en la superficie del cubo, necesitamos calcular la intersección entre el punto de vista C en el centro y la esfera. Puede parecer complicado, pero en realidad es bastante simple.

Podemos considerar la línea CA como un vector. Y los vectores pueden normalizarse para que su dirección no cambie, pero la longitud disminuye a 1. Debido a esto, todos los vectores juntos se verán como una esfera.

La normalización también es bastante simple, solo necesitamos dividir los valores del vector por xyz por la longitud total del vector. La longitud del vector se puede calcular utilizando el teorema de Pitágoras.

En el siguiente código, el cálculo de normalización se realiza primero en una cara. Luego, el ruido se calcula simultáneamente para los seis bordes, porque para obtener la posición de la siguiente cara, solo necesita voltear los valores a lo largo de xyz.

// 3D panoramical cube map
document.body.addChild('h2').innerText = '3D panoramical cube map';
// we're not using the drawNoise function because our canvas is rectangular
var mCubemap = document.createElement('canvas')
    ,iW = 6*iSize;
mCubemap.width = iW;
mCubemap.height = iSize;
var  iHSize = iSize/2 // half the size of the cube
    ,oCtx = mCubemap.getContext('2d')
    ,oImgData = oCtx.getImageData(0,0,iW,iSize)
    ,aPixels = oImgData.data
    ,aa = 123
    ,bb = 231
    ,cc = 321
;
for (var i=0,l=iSize*iSize;i<l;i++) {
    var  x = i%iSize        // x position in image
        ,y = (i/iSize)<<0    // y position in image
        ,a = -iHSize + x+.5    // x position on the cube plane, the added .5 is to get the center of the pixel
        ,b = -iHSize + y+.5 // y position on the cube plane
        ,c = -iHSize        // z position of the cube plane
        ,fDistanceAB = Math.sqrt(a*a+b*b) // to calculate the vectors length we use Pythagoras twice
        ,fDistanceABC = Math.sqrt(fDistanceAB*fDistanceAB+c*c)
        ,fDrds = .5*fDistanceABC // adjust the distance a bit to get a better radius in the noise field
        ,v = 1
    ;
    a /= fDrds; // normalize the vector
    b /= fDrds; // normalize the vector
    c /= fDrds; // normalize the vector
    //
    // since we now know the spherical position for one plane we can derive the positions for the other five planes simply by switching the x, y and z values (the a, b and c variables)
    var aNoisePositions = [
         [a,b,c]    // back
        ,[-c,b,a]    // right
        ,[-a,b,-c]    // front
        ,[c,b,-a]    // left
        ,[a,c,-b]    // top
        ,[a,-c,b]    // bottom
    ];
    for (var j=0;j<6;j++) {
        v = Simplex.noise(
             aa + aNoisePositions[j][0]
            ,bb + aNoisePositions[j][1]
            ,cc + aNoisePositions[j][2]
        );
        var pos = 4*(y*iW+j*iSize+x); // the final position of the rgba pixel
        aPixels[pos] = aPixels[pos+1] = aPixels[pos+2] = v*255<<0;
        aPixels[pos+3] = 255;
    }
}
oCtx.putImageData(oImgData,0,0);
document.body.addChild('img',{src:mCubemap.toDataURL("image/jpeg")});

Aquí hay seis lados en una imagen, más una captura de pantalla de cómo se ve cuando se ve desde un cubo. El código fuente tiene un ejemplo en 3D escrito en threejs .


Mapa panorámico cúbico


Imagen 2D perfecta


Puede parecer que una imagen 2D perfecta es fácil de implementar, pero me parece que esta es la más difícil de las descritas en el artículo, porque para comprenderla debes pensar en cuatro dimensiones. Lo más parecido a esto fue un mapa cilíndrico (con repetición horizontal), por lo que lo tomaremos como base. En el mapa cilíndrico, utilizamos la posición horizontal de la imagen para el círculo; es decir, la posición horizontal de la imagen nos da dos coordenadas x e y en el campo de ruido xyz. La posición vertical de la imagen corresponde a z en el campo de ruido.

Queremos que la imagen sea transparente y vertical, por lo que si agregamos otra dimensión, podemos usarla para crear un segundo círculo y reemplazar el valor lineal del campo z. Esto es similar a crear dos cilindros en un campo de cuatro dimensiones. Traté de visualizar esto en un boceto, es inexacto, pero traté de transmitir el principio general y no dibujar un cilindro de cuatro dimensiones.


Un bosquejo de dos cilindros en cuatro dimensiones

El código es bastante simple: estos son solo dos círculos en un espacio de ruido de cuatro dimensiones.

// four dimensional tile
fNoiseScale = .003;
drawNoise(function(i,x,y){
    var  fNX = x/iSize
        ,fNY = y/iSize
        ,fRdx = fNX*2*Math.PI
        ,fRdy = fNY*2*Math.PI
        ,a = fRds*Math.sin(fRdx)
        ,b = fRds*Math.cos(fRdx)
        ,c = fRds*Math.sin(fRdy)
        ,d = fRds*Math.cos(fRdy)
        ,v = Simplex.noise(
             123+a*fNoiseScale
            ,231+b*fNoiseScale
            ,312+c*fNoiseScale
            ,273+d*fNoiseScale
        )
    ;
    return (Math.min(Math.max(2*(v -.5)+.5,0),1)*255)<<0;
}).img().div(2,2);

Y aqui esta el resultado:

imagen

All Articles