El blog de Pablo Zurita.

Junio 12, 2006

Tone Mapping y Blooms

Archivado en: General — Pablo Zurita @ 6:26 pm

Últimamente dos de los agregados mas importantes que se le han dado a las escenas en tiempo real es el tonemapping y blooming. Pero por varias razones se ha estado realizando de manera demasiado incorrecta desde el punto de vista psicofísico. La idea de este artículo es analizar de manera superficial como ocurren estos efectos en la vida real y después analizar como simular en tiempo real.

El ojo humano tiene dos formas de células fotosensitivas en la retina, los conos y los bastones. Aunque los dos son similares en estructura, los dos son bastante diferentes e igualmente importantes para la visión. Los bastones son altamente sensibles a la luz lo que les permite responder en situaciones de iluminación baja. Los conos necesitan más luz para responder pero permiten ver un rango de luz más alto (y entre otras cosas permiten ver colores). Tampoco nos podemos olvidar de la pupila que ajusta su tamaño constantemente para mantener la entra de luz en un nivel mas o menos constante. Analizando un poco cada célula fotosensitivas podemos darnos cuenta que podemos ver en un gran rango de intensidades de luz. Estos rangos se los puede dividir en tres regiones. La región fotopica con rango de 10^1 cd/m^2 a 18^8 cd/m^2 y que estimula a los conos. La región escotópica con rango de 10^-1 cd/m^2 a 10^-6 cd/m^2 y que estimula a los bastones. La tercera región es la región mesópica, la cual estimula a los conos y bastones. Pero una parte muy importante es la pupila porque mediante su cambio constante de tamaño permite que nuestro sistema responda a diferentes intensidades de luz. Por ejemplo a bajas intensidades de luz la pupila se abre lo mas posible para dejar entrar luz, en niveles altos de intensidad la pupila se cierra para que menos luz entre y así mantener la visión en una región en la que los conos y bastones puedan generar una respuesta. Pero hay veces que no importa que haga la pupila, no va a haber forma de generar la respuesta esperada, lo que quiere decir que o la intensidad es tan baja que no genera ninguna respuesta o la intensidad es tan alta que la sensibilidad de la visión baja mas y mas. Esto es conocido como compresión de respuesta y es la parte vital para determinar el sistema de tone mapping correcto. Pero hay otro factor, aunque la sensibilidad del ojo cambia de acuerdo a la iluminación predominante, este proceso no es instantáneo. A este mecanismo se le llama adaptación. El periodo de adaptación pueden llegar a ser de hasta 15 minutos en los conos y 40 minutos en los bastones para la adaptación total en un cambio severo de iluminación. Teniendo todo esto claro podemos empezar a hablar del tone mapping en si.

La idea de los operadores de tone mapping es pasar una escena de High Dynamic Range (HDR) a un medio Low Dynamic Range (LDR). Estos operadores se pueden dividir en diferentes categorías según como intentan llevar la información HDR a un medio LDR. Los operadores que aplican la misma función sobre toda una imagen son conocidos como operadores globales. Estos algoritmos tienden a ser muy buenos en performance por su simplicidad, pero tienden a perder mucha información sobretodo si hay un buen espectro de luminancia. Ejemplos de operadores globales son el operador de Ward, Schlick, Tumblin y Drago. Los operadores que dependen de la información de píxeles contiguos son llamadores operadores locales. Los operadores locales son los mejores ya que pueden proveer una gran compresión de la información HDR sin perder detalles. Ejemplos de operadores globales son el operador de Pattanaik, Jonson, Chiu, etc. Como en nuestro caso queremos agregar mas realidad a nuestras escenas simulando el comportamiento del ojo humano, esto quiere decir que vamos a necesitar un operador que tenga en cuenta los conos y bastones, y los tiempos de adaptación.

Independientemente del operador que decidas usar hay cierta información que hay que obtener siempre como por ejemplo la intensidad global. El problema es que la mayoría de los operadores están pensados para que corran en CPU, pero cuando tenemos que hacer eso en GPU el tema es un poco diferente porque las restricciones son diferentes. Por ejemplo obtener la intensidad global en CPU es tan fácil como hacer un loop, vas sumando la intensidad en cada píxel y después dividís por una constante para obtener la intensidad global. Pero en GPU es un poco mas complicado sobretodo por dos problemas, primero es demasiado intensivo calcular la intensidad global en cada fragment (porque se ejecuta en un fragment shader), y segundo no podemos hacer un fetch del fragment anterior o posterior. Veamos por ejemplo como obtener la luminancia global y máxima de un frame. Teniendo en cuenta las restricciones anteriores, la forma de obtener la luminancia máxima y la global consiste en usar varios FBOs de diferentes resoluciones y ejecutar un shaders para obtener el valor. Podemos empezar con nuestra escena renderizada en un FBO de 512×512, de ahí lo que hacemos es usar el siguiente shader y poner el contenido en un FBO de 256×256:

varying vec2 vTexCoord;//Screen
uniform sampler2D alb_screenfbo; //FBO texture.
uniform float alb_screenfbo_width; //FBO Width.

void main (void)
{
vec4 finalCalc;
vec2 uvCoords;
float maxLum;
float avLum;
float currentLum;

uvCoords.x = vTexCoord.x - 0.5 / alb_screenfbo_width;
uvCoords.y = vTexCoord.y - 0.5 / alb_screenfbo_width;
currentLum = (0.30 * texture2D(alb_screenfbo, uvCoords).x) + (0.59 * texture2D(alb_screenfbo, uvCoords).y) + (0.11 * texture2D(alb_screenfbo, uvCoords).z);
avLum = log(currentLum + 1e-4);
maxLum = currentLum;

uvCoords.x = vTexCoord.x - 0.5 / alb_screenfbo_width;
uvCoords.y = vTexCoord.y + 0.5 / alb_screenfbo_width;
currentLum = (0.30 * texture2D(alb_screenfbo, uvCoords).x) + (0.59 * texture2D(alb_screenfbo, uvCoords).y) + (0.11 * texture2D(alb_screenfbo, uvCoords).z);
avLum = log(currentLum + 1e-4);
if(maxLum < currentLum)
maxLum = currentLum;

uvCoords.x = vTexCoord.x + 0.5 / alb_screenfbo_width;
uvCoords.y = vTexCoord.y - 0.5 / alb_screenfbo_width;
currentLum = (0.30 * texture2D(alb_screenfbo, uvCoords).x) + (0.59 * texture2D(alb_screenfbo, uvCoords).y) + (0.11 * texture2D(alb_screenfbo, uvCoords).z);
avLum = log(currentLum + 1e-4);
if(maxLum < currentLum)
maxLum = currentLum;

uvCoords.x = vTexCoord.x + 0.5 / alb_screenfbo_width;
uvCoords.y = vTexCoord.y + 0.5 / alb_screenfbo_width;
currentLum = (0.30 * texture2D(alb_screenfbo, uvCoords).x) + (0.59 * texture2D(alb_screenfbo, uvCoords).y) + (0.11 * texture2D(alb_screenfbo, uvCoords).z);
avLum = log(currentLum + 1e-4);
if(maxLum < currentLum)
maxLum = currentLum;

avLum = exp(avLum / (alb_screenfbo_width * alb_screenfbo_width));
finalCalc.x = avLum;
finalCalc.y = maxLum;
gl_FragColor = finalCalc;
}

Como se ve en el shader lo que hacemos es obtener el obtener la luminancia de los 4 fragments alrededor del sample. Esto lo hacemos varias veces hasta llegar a un FBO de 1×1, al final de todo el proceso vamos a tener la luminancia global en el canal red y la luminancia máxima en el canal green del FBO 1×1. Parte del proceso se puede ver en el siguiente diagrama.

HDR Chart

Ahora que tenemos nuestra escena lista podemos encargarnos del bloom. El bloom se ha hecho constantemente mal no tanto por el bloom en si sino por la información que usaban al momento de hacer el bloom. Lo correcto es que una vez que tenemos nuestra escena ajustada al nivel de luminancia, si hay algo que quedo fuera del rango LDR entonces desde esos fragments generamos el bloom (independientemente en cada canal). De esta manera nos diferenciamos con el modelo incorrecto, porque en general se tiende a hacer un bright-pass que no tiene nada que ver con la reacción del ojo y crean un blooming por todos lados cuando eso no pasa en la vida real. En vez de usar un bright-pass con una ecuación complicada y que no refleja lo que realmente pasa en la vida real, nuestro bright-pass simplemente consiste en los valores que están por encima de 1.0 después del tone mapping porque en ese momento los conos y bastones se vieron sobrepasados.

Bueno, espero que este artículo les ayude a elegir el operador de tone mapping apropiado, y también a hacer el blooming de manera correcta. Cualquier comentario sobre el tema lo pueden dejar aquí mismo y yo lo responderé a la brevedad.

6 comentarios »

  1. Bien por tí!. A mi por lo menos me sirvió para saber que es el tone mapping. Un abrazo

    Comentario por Pablo — Junio 12, 2006 @ 9:22 pm

  2. Muy interesante el artículo, aunque requiere una lectura en profundidad. Lo único que le falta es explicar un poco más la parte de los operadores (quizás con alguna referencia).

    Ya espero el siguiente artículo.

    Comentario por javi — Junio 13, 2006 @ 6:24 am

  3. Interesante :)

    Saludos y éxitos!

    Comentario por w!z@rd — Junio 13, 2006 @ 9:56 am

  4. Muy interesante el articulo.

    Sin embargo el Bloom muchas veces se hace sin HDR. Entonces para poder aplicar bloom a algo y no a todo es necesario algun tipo de Bright Pass (por mas que no sea el correcto) ademas de que no todos los juegos intentan lograr graficos realistas. Ej: NFS:Most Wanted o Regnum que tienen Bloom pero no HDR…

    Comentario por Sergio — Junio 13, 2006 @ 1:25 pm

  5. Gracias a todos por los comentarios.

    Javi: Ya voy a editar el post y agregar links a papers de diferentes operadores, tanto globales como locales.

    Sergio: Claro, por eso en el inicio hablo de hacerlo de manera psicofisicamente correcta. Esto deja de lado los casos donde por ejemplo el bloom esta para darle un estilo artístico a la escena, donde no es la intención hacer algo realista. El tema es que en general se tiende a buscar más realismo para atrapar más al jugador en el juego. Mucha gente busca mas realismo y lo hacen de la manera incorrecta, por eso escribí el articulo, después cada uno decide como implementar determinado efecto.

    Comentario por Pablo Zurita — Junio 13, 2006 @ 3:25 pm

  6. Muy bueno el artículo. Yo no sabía este tipo de cosas. Buen trabajo.

    Comentario por JtR666 — Junio 13, 2006 @ 3:51 pm

Suscripción RSS a los comentarios de la entrada. URI para TrackBack.

Deje un comentario

Gestionado con WordPress