viernes, 25 de julio de 2014

Embellece tu juego 2D - Self-Shadowing

Existen varias técnicas para que una textura de 2 dimensiones proyecte sombras en tiempo real sobre sí misma. Las técnicas más rápidas hacen uso de mapas de horizonte pero es menos precisa y genera más artefactos que la implementación que voy a presentar aquí. Esta implementación es computacionalmente muy pesada pero, asumiendo que en un juego 2D tenemos una carga de GPU relativamente baja y que somos un poco putilla gráfica y tenemos hardware puntero, puede llegar a ser viable en una aplicación interactiva; sobretodo si nuestro motor gráfico trabaja con deferred rendering.


Para que una textura en 2 dimensiones proyecte sombras se necesita información sobre la profundidad de esta; por ello, necesitamos un mapa que defina la altura de cada pixel. En este caso utilizamos una textura en escala de grises donde negro es 0 altura y blanco es la altura máxima, cualquier valor gris representa una altura intermedia. Para evitar artefactos en los cambios de altura mas pronunciada  sin coste computacional los bordes de el mapa de altura debe estar muy ligeramente desenfocada. Esto permite evitar artefactos mientras se mantiene una proyección de sombra muy cercana a la forma original del objeto que ocluye la luz



La idea principal de este método es dividir la profundidad de la superficie en un número de capas de misma altura e ir recorriendo las capas desde el pixel donde incide a luz hacia el origen de la luz. Si en alguna capa la altura del mapa de altura el mayor que la posición donde nos encontramos, deducimos que a ese pixel no le llega la luz por lo que descartamos la luz del punto de luz y sólo aplicamos la luz ambiental.


Para optimizar el rendimiento calculamos en tiempo de ejecución el número de capas en las que dividimos el mapa de altura, utilizando más capas cuanto más inclinada esté la luz con respecto al pixel, puesto que se presenta mayor posibilidad de saltarnos un pixel que se encuentre entre las 2 muestras que tomamos del mapa de altura (diferencias de altura muy pronunciada).

Los cambios en el shader de iluminación serían los siguientes:

//altura maxima para escalar los valores del mapa de alturas
float scale = 0.05;
//mapa de alturas
sampler tex_height_map : register(s3);
//maximo y minimo numero de capas en las que dividimos el mapa de altura
const float steep_shadow_steps_min = 15.0;
const float steep_shadow_steps_max = 30.0;

//interpolamos entre el minimo y maximo numero de capas segun la inclinacion de la luz con respecto al pixel  
float num_steps = lerp(steep_shadow_steps_max, steep_shadow_steps_min, LightDirection.z);
//calculamos la altura de cada capa
float step = 1.0 / num_steps;
//calculamos cuanto avanzamos hacia la luz en cada paso
float2 delta = LightDirection.xy * scale / (LightDirection.z * num_steps);
//obtenemos la primera muestra del mapa de altura
float map_height = tex2D(tex_height_map, texCoord).r;
//calculamos la altura del rayo de luz en el siguiente paso
float height = map_height + step;
//calculamos las coordenadas del pixel que vamos a muestrear
float2 shadow_texture_coord = texCoord + delta;
//obtenemos el valor del mapa de altura
float shadow_map_height = tex2D(tex_height_map, shadow_texture_coord).r;

//mientras no encontremos oclusion y no lleguemos al final vamos avanzando
while (shadow_map_height < height && height < 1.0)
{
  //avanzamos en altura
  height += step;
  //avanzamos las coordenadas del pixel a muestrear
  shadow_texture_coord += delta;
  //obtenemos el pixel del mapa de altura
  shadow_map_height = tex2Dlod(tex_height_map, float4(shadow_texture_coord,0,0)).r;
}
//si no hemos encontrado oclusion aplicamos luz del punto de luz
if (shadow_map_height < height)
{
  tex.rgb *= (AmbientLight +(A*(S + (D*LightColor))));
}
else{ //si hemos encontrado oclusion aplicamos solo luz ambiental
  tex.rgb *= AmbientLight;
}

Y el resultado final se merece un vídeo para que se vea en todo su esplendor:


2 comentarios:

  1. Teniendo en cuenta que quiero hacer un juego de naves este verano, me van a venir fenomenal todos éstos artículos, la verdad es que los efectos quedan muy vistosos. A ver si venzo la pereza y paso de idear a implementar.

    ResponderEliminar
  2. ¡Pues date prisa que se te va el Agosto! ;)

    ResponderEliminar