Dibujando la alfombra de Sierpinski (Shaders y FBO)

Posted · Añadir comentario

El método que hemos escogido para dibujar la alfombra de Sierpinski en el artículo anterior es muy poco eficiente, la aplicación que lo utiliza, SierpinskiGreyScale, funciona a nueve fotogramas por segundo en mi ordenador. El programa necesita recorrer la matriz de 729 x 729, de la forma en que explicamos, dos veces; una primera vez para sumar los valores de todos los pixeles que están en los cuadrados centrales y una segunda vez para calcular la media de todos los valores sumados dividiendo el resultado por el número de pixeles de cada cuadrado, almacenar la media como escala de grises en las áreas sensibles y crear una textura que muestre el resultado.

sierpinski-shaders_00

¿Podemos utilizar shaders o aprovechar la forma de trabajar con Frame Buffer Objects que sugerimos en el segundo artículo, con el fin de resolver de forma mas eficiente el problema?

sierpinski-shaders_03

Reconsideremos la naturaleza de lo que la alfombra de Sierpinski propone en cada nivel. Comenzamos partiendo de una imagen de 729 x729 que descompusimos en un cuadrado central y ocho zonas de 243 x 243 pixeles. Sin embargo, desde el punto de vista de la alfombra, lo único que necesitábamos era transformar la imagen original en una matriz de 3 x 3 pixeles y seleccionar el pixel central. Podemos realizar este trabajo de forma muy eficiente y con mucha facilidad redimensionando la imagen mediante un Frame Buffer Object.

// declaración
OfFbo		fbo_1;
ofPixels    pixels_1;
ofTexture	texture_1;

...

// asignacion de memoria
fbo_1.allocate(5, 3, GL_RGB, 0);
texture_1.allocate(5, 3, GL_RGB);
texture_1.setTextureMinMagFilter(GL_NEAREST, GL_NEAREST);

...

fbo_1.begin();
ofClear(0, 0, 0, 255);
videoTexture.draw(0, 0, 3, 3);
fbo_1.end();
fbo_1.readToPixels(pixels_1);
texture_1.loadData(pixels_1.getPixels(), 5, 3, GL_RGB);

Al hacerlo nos hemos encontrado con un extraño problema, trabajando con texturas y Frame Buffer Objects cuyo ancho era un múltiplo exacto de 3, la tarjeta gráfica del ordenador generaba extraños errores y fallos. Sin haber ocupado mucho tiempo en averiguar porqué sucede esto, para resolver el problema, hemos decidido utilizar anchos 2 pixeles más grandes de lo que necesitamos, por esta razón en el fragmento de código anterior la textura y el Frame Buffer Object tienen un tamaño de 5 x 3 pixeles.

texture_1.setTextureMinMagFilter(GL_NEAREST, GL_NEAREST);

La clave de esta nueva estrategia que proponemos está en la sexta línea del código. La función setTextureMinMagFilter nos permite establecer la forma en que se renderizará la textura en la que almacenamos la matriz de 3 x 3 pixeles del primer nivel de la alfombra, de modo que mantenga su geometría aunque la mostremos en la pantalla a un tamaño de 729 x 729, debido al tipo de cálculo que se realizará para generar por interpolación los 531.441 pixeles que habrá en la pantalla para representar una textura que solamente tiene 9 pixeles, GL_NEAREST.

A continuación simplemente debemos realizar la misma operación para almacenar cada nivel de la alfombra en una textura del tamaño adecuado, 9 x 9, 27 x 27, 81 x 81 y 243 x 243.

// declaración
OfFbo		fbo_2;
ofPixels    pixels_2;
ofTexture	texture_2;

...

// asignacion de memoria
fbo_2.allocate(11, 9, GL_RGB, 0);
texture_2.allocate(11, 9, GL_RGB);
texture_2.setTextureMinMagFilter(GL_NEAREST, GL_NEAREST);

...

fbo_2.begin();
ofClear(0, 0, 0, 255);
videoTexture.draw(0, 0, 9, 9);
fbo_2.end();
fbo_2.readToPixels(pixels_2);
texture_2.loadData(pixels_2.getPixels(), 11, 9, GL_RGB);

...

De hecho podemos simplificar muchísimo el código almacenando Frame Buffer Objects, texturas, pixeles y tamaños en arrays, para hacer todas las operaciones en el contexto de un bucle for.

// declaración
OfFbo		fbo[6];
ofPixels    pix[6];
ofTexture	texture[6];
int         w[6];
int         h[6];

...

// asignacion de memoria
for (int i=0; i<6; i++) {
	fbo[i].allocate(w[i], h[i], GL_RGB, 0);
	texture[i].allocate(w[i], h[i], GL_RGB);
	texture[i].setTextureMinMagFilter(GL_NEAREST, GL_NEAREST);
}

...

for (int i=0; i<6; i++) {
  	fbo[i].begin();
	ofClear(0, 0, 0, 255);
	videoTexture.draw(0,0, w[i]-2, h[i]);
	fbo[i].end();
	fbo[i].readToPixels(pix[i]);
	texture[i].loadData(Pix[i].getPixels(), w[i], h[i], GL_RGB);
}

Una vez hayamos almacenado cada uno de los niveles de la alfombra en una textura diferente solamente nos quedará el delicado trabajo de mostrarlas unas encima de otras de modo que únicamente sean visibles los cuadrados centrales.

01-mago-composit

Recurramos a la misma lógica que seguimos en el artículo anterior. En el primer nivel el cuadrado central corresponde a un pixel cuyas coordenadas X e Y en la matriz de 3 x 3 son 1 y 1. En el segundo nivel tenemos una matriz de 9 x 9, con ocho zonas de 3 x 3 pixeles dentro de las cuales debemos seleccionar un cuadrado central que, a su vez, corresponde a un pixel cuyas coordenadas X e Y son, otra vez, 1 y 1. Al margen de la posición en la que esté cada pixel central, el resto de la división entera de sus coordenadas de entre 3 será siempre 1, ya que se tratará del segundo pixel de un grupo de tres pixeles, el número 1 contando a partir del 0.

sierpinski-shaders_04

En todos y cada uno de los niveles sucede exactamente lo mismo, el cuadrado central se corresponde con el segundo pixel de un grupo de tres, por lo tanto, podemos utilizar un shader para cambiar la transparencia de todos los pixeles de las texturas que hemos creado en función de sus coordenadas y hacer visibles solamente los cuadrados centrales. Bastará con asignar 0 al valor del canal alpha de cada pixel, cuando el resto de la división entera de sus coordenadas entre tres no sea 1 en ambos ejes.

uniform sampler2DRect tex0;
in vec2 texCoordVarying;

out vec4 outputColor;

void main() {
	 
	vec4 texel0 = texture(tex0, texCoordVarying);
	float alpha = 1.0;
	((floor(mod(texCoordVarying.x, 3)) == 1) && (floor(mod(texCoordVarying.y, 3)) == 1)) ? alpha = 1.0 : alpha = 0.0;
	if ((texel0.r == 0) && (texel0.g == 0) && (texel0.b == 0)) alpha = 0.0;
	outputColor = vec4(texel0.r, texel0.g, texel0.b, alpha);
	
}

Hemos utilizado esta estrategia para hacer una segunda aplicación y el resultado es asombroso, el nuevo programa permite dibujar un Sierpinski a partir del feedback de la webcam el doble de grande, 1458 x 729, y sin embargo funciona a 50 fotogramas por segundo.

https://github.com/alg-a/herm3TICa/tree/master/exploracion%20pruebas%20y%20juegos/SierpinskiShaders

 
 

Uso de cookies

Este sitio web utiliza cookies para que usted tenga la mejor experiencia de usuario. Si continúa navegando está dando su consentimiento para la aceptación de las mencionadas cookies y la aceptación de nuestra política de cookies, pinche el enlace para mayor información.plugin cookies

ACEPTAR
Aviso de cookies