viernes, marzo 10, 2006

Sistema de Partículas III : Rendering

Recién ahora se podría pensar en qué motor usar, en caso de elegir no reinventar la rueda, claro. SDL es muy lento, y requiere de optimizaciones a mano hasta para poner un pixel en pantalla. Hay muchas funciones rondando por la web para dibujar un pixel en pantalla con SDL, ésta es la típica (la que se ve en todas partes) y es deprimente (el autor avisa que no está optimizada, pero no creo que el comentario ayude de mucho despues de ver cómo está implementada) . La que voy a usar está hecha por mí, y tampoco es una de las mejores...pero es bastante rápida y consume 6 veces menos memoria que la anterior. Es importante optimizar esta parte, ya que es una etapa crucial dentro del sistema: interpretar en pantalla la física actual de cada partícula. También está sujeta a la cantidad total de partículas. En los screenshots he usado 10.000 partículas y el consumo total del sistema fue de 3.480 KB y aún se podría seguir optimizando, pero no mucho. Una optimización clave es que he usado un sistema estático, y las partículas aún muertas siguen chupando memoria. Si fuese dinámico el consumo tiende a 0 en menos de 5 segundos, almenos para esta explosión en particular.

  • Sin considerar ninguna paleta


No voy a considerar ninguna paleta, por ahora almenos...En el sistema inicial las explosiones siempre las hice rojas con chispitas negras al final...Se ajusta a mi manera de dibujar XD. Es muy fácil usar una paleta, pero no tan fácil es modelarla, hay que probar y probar viendola en pantalla hasta que quede bien. Hasta ahora he probado estas implementaciones con la paleta anterior (rojo y negro) y con una que saqué de "All about color palettes, Chapter 8" ; no sé el nombre del libro, pero los fascículos están muy buenos y completos, tiene todo sobre programación gráfica en 2D.
  • Explosiones: ejemplo

Para modelar explosiones usamos los algoritmos de setup y movimiento de partículas vistos en el cap. 2, modificando los siguientes parámetros:

- gravedad: +3.5
- considerar: muertes y sistema muerto

Considerar muertes implica asegurar que luego de que una partícula queda pegada a una superficie (al piso por ejemplo) ya no se la esté intentando mover. El sistema está muerto cuando todas las partículas están muertas. Si esto no se controla, al finalizar la explosión el sistema seguirá chupando recursos innecesariamente.

Para los ejemplos usé 10.000 partículas, la resolución que usé es 320x200. Este sistema en su pico máximo (todas las partículas vivas) consume 3480 KB (sí, un disparate). Este mismo sistema a una resolución de 800x600 consume 4716 KB. El motor influye mucho en el rendimiento, y también la forma de usarlo. Notar que el sistema es estático, o sea que con 'pico máximo' me refiero a poca diferencia con pico mínimo. Una vez mas...es recomendable implementarlo de forma dinámica.

Esta es la función que usé para ver las partículas:

void putpixel(SDL_Surface *screen, int x, int y, Uint32 color)
{
// Determinamos posición de inicio
char *buffer=(char*) screen->pixels;
// Calculamos offset para y
buffer+=screen->pitch*y;
// Calculamos offset para x
buffer+=screen->format->BytesPerPixel*x;
// Copiamos el pixel
memcpy(buffer, &color, screen->format->BytesPerPixel);
}


Explosión con pixeles: (10.000 partículas)





Explosión usando 1.000 partículas:




































  • Que venga la sangre!!!

Con este algoritmo generar cascadas, salpicones, etc. de sangre es muy fácil, y va muy bien :D. En cascada:





No se nota mediante shots, pero hay un leve rebote antes de terminar de caer.
Lo mejor de todo, es la vagancia de implementar la sangre, simplmente hice las siguientes modificaciones:

-> gravedad = +3.5
-> las partículas mueren cuando alcanzan la coordenada y=180

Como vimos en el capítulo 2, considerabamos choques con las superficies. Por lo tanto al existir gravedad, se genera una serie de rebotes descendentes, acabando las partículas muertas en el piso. Si no se considerara las colisiones con el piso, las partículas mueren apenas alcancen la coordenada y=180 , esto sería ideal para cuando la sangre cae de una ventana o algo así...sin tocar piso o nada en su camino.

  • Obtener otras orientaciones
Solo basta con jugar con los parámetros de la función setup, dado que hay que modificar la inicialización de cada partícula para obtener otras orientaciones hacia donde sea.

Ejemplo:
// olvidemos la clase vector por un instante
// sinó no me da el espacio ;)

// coordenadas de inicio
// si hubiese que modelar lluvia
// sería y=0, x= rand()%320
particles[i].x = float(rand()%2+ 160);

particles[i].y = float(rand()%2+ 100);

// para obtener direcciones a gusto
// hay que modificar los ángulos de
// salida

// generamos ángulo pseudoaleatorio
// en grados y lo pasamos a radianes

int grade = rand()% 360;
float angle = (float) grade/180 * 3.1416;

// incializamos velocidad
float vel = 4.0f;
// generamos el vector dirección
particles[i].dirx = sin(angle) * vel;
particles[i].diry = cos(angle) * vel;

Notar que hemos cambiado la forma de disparar las partículas respecto de lo visto en el capítulo 2. Ambas formas funcionan, es decir, mediante ángulos y mediante valores pseudoaleatorios, sin embargo con el segundo método aparecen 2 lineas perpendiculares en el centro de explosión, y se debe a que en esas rectas no hay partículas viajando porque sus valores iniciales no pertenecen a la recta. Las rectas no son de partículas en sí, son de vacío, es decir, el conjunto de partículas explota y deja espacios vacíos alrededor de esas rectas. En fin, es solo un detalle, pero para más calidad usar ángulos para incializar las direcciones.

  • Dibujando las partículas
La función Draw_Particle dibuja una partícula, la función Draw_Particles llama PARTICLES veces a Draw_Particles.

void Draw_Particle(particle *aparticle)
{
long x, y;

// pasando las coordenadas a enteros...

x = (long)(aparticle->x);
y = (long)(aparticle->y);

// color rojo, no hay paleta

Uint32 c=SDL_MapRGB(pantalla->format,255,0,0);

// dibujamos la partícula en pantalla

putpixel(paux,x,y,c);

}

  • Dinámica en pantalla
Este es el orden de invocación que usé en el bucle principal:

// limpiar la pantalla
SDL_FillRect(pantalla, 0, SDL_MapRGB(pantalla->format, 0, 0, 0));
// dibujar partículas
Draw_Particles(particles);
// doble buffer
SDL_BlitSurface(paux, 0, pantalla, &dstrect);
// mover partículas
Move_Particles(particles);
// actualizar
SDL_Flip(pantalla);

Esto es todo por el capítulo 3...Quedan muchos efectos, aunque después de hacer 2 o 3 y entender la dinámica es fácil imaginarse otros efectos con tan solo modificar parámetros y colores. Una mejora que vale la pena es considerar una paleta de colores.