Hay quien dice que el tratamiento de la luz es lo más importante en las artes visuales. En videojuegos cobra aún más importancia si cabe, ya que constituye la forma en la que se decide el color de cada píxel en la pantalla y, en definitiva, todo lo que verá el jugador. Continuando con el último artículo, toca intentar explicar las bases e introducirnos en una de las técnicas más importantes en el videojuego: la iluminación.
Ya hemos visto a grandes rasgos cómo se transforma la geometría de un modelo para pintarlo en la pantalla del ordenador, pero todavía queda lo más importante. Sólo con lo visto el otro día no podríamos hacer mucho más que el clásico Battlezone (Atari, 1980), pintando líneas de un vértice a otro. Hay que rellenar lo que se encuentra entre las aristas del modelo y obtener un valor RGB para cada píxel que abarquen, y para ello se suele usar un modelo de iluminación, que calculará el color en función de fuentes de luz y la propia geometría.
RGB
Para representar los colores en una pantalla, se usan sus componentes RGB. El color que vemos en cada píxel del monitor es la resultante de mezclar sus componentes Roja (R), Verde (G), y Azul (B). Esto se debe, entre otras cosas, a la forma de funcionar de las pantallas desde casi el cine en Technicolor. En ausencia de luz, la mezcla de luz roja, verde y azul crea luz blanca, por lo que variando la intensidad de cada color podemos crear (casi) toda la gama de colores visible por un ser humano. Con todo ello, para pintar una pantalla de ordenador hay que formar una imagen similar a una foto digital, que lleva la información de color de cada píxel codificada en esos tres valores RGB.
En juegos más antiguos se utilizaba una tabla con todos los colores que se podían usar (la llamada Paleta de colores). Así, en lugar de tener que usar un valor para cada componente de color de cada píxel, bastaba con un solo número por píxel, correspondiente con un color de esta tabla: el índice (de ahí el nombre de color indexado), lo que reducía considerablemente el tamaño de la imagen. El tamaño en bits de ese índice nos indicaba el número de colores que podía haber en la tabla. De aquí venían los típicos formatos de pantalla CGA de 4 colores, EGA de 16, VGA de 256, etc. Es conveniente tener en cuenta que esto de la indexación de colores aún se usa en algunos casos, sobretodo en juegos en 2D, donde muchas veces es necesario reducir el tamaño en memoria de los gráficos.
Si no se utiliza una paleta de colores hay que almacenar directamente los valores de cada componente de color por cada píxel. Por ejemplo, los colores de 24 bits permiten 8 bits por cada componente (RGB), que en la mayoría de casos es más que suficiente. Independientemente del tamaño en bits, es común a nivel de programación tratar cada componente como un valor real entre 0 y 1. Esto permite operar con colores como si de un vector se tratara, lo que es más cómodo a la hora de calcular el color final de cada píxel de pantalla. Además, a menudo el RGB se suele acompañar de un valor de transparencia o Alpha, pasando a llamarse RGBA. Esta nueva componente nos indica cuánto de ese color se va a aplicar, lo que es útil cuando estamos empleando varias capas de color unas encima de otras, mezclando colores y permitiendo transiciones suaves, huecos, etc. Añadiendo el alpha los colores de 24 bits RGB, pasan a ser colores de 32 bits RGBA.
La fórmula de la iluminación
Volviendo al tema principal del artículo, para obtener ese valor RGB que necesitamos, lo más directo es asociar al modelo un color, lo que se conoce como color ambiente, o luz ambiente. Recorriendo todos los píxeles que abarca el modelo y utilizando solamente este color, los objetos aparecerían como siluetas de un solo tono uniforme. Sin embargo, si queremos que una fuente de luz ilumine y oscurezca zonas del modelo hay que hacer más cálculos.
Con un poco de geometría espacial se puede obtener el vector normal a cada cara, que es el vector que apunta en dirección perpendicular a una superficie. En este caso, apunta en la dirección en la que mira una cara (normalmente un triángulo) del modelo. Y con él, junto con la dirección de los rayos de luz, podemos calcular el ángulo de incidencia de la luz con cada cara, y asignarle mayor o menor intensidad de color en función de ello, en lo que se conoce como luz difusa, o color difuso.
Por último, utilizando la dirección en la que mira la cámara, el vector normal, y el ángulo de incidencia de la luz, se puede obtener la intensidad de luz reflejada en el modelo. Con ello obtendremos un tercer color llamado color especular, o luz especular.
Lo más habitual es combinar estos tres colores. Ésta es la razón de que a los entornos de modelado podamos aplicarle los tres colores al material del modelo: ambiente, difuso y especular. Aquí entran en juego los valores Alpha del color, si queremos que se apliquen en mayor o menor medida. También se suelen aplicar otras variables a la fórmula, como la distancia al foco de luz, para que no se iluminen de igual forma todas las caras de la imagen que estén orientadas en la misma dirección.
Suavizado
Con todo lo anterior podemos obtener un color uniforme para cada cara del modelo. Esto puede bastarnos para muchos casos, pero si buscamos un color suavizado a lo largo de toda la superficie del modelo, por ejemplo para iluminar una superficie curva, no es suficiente. Necesitamos distintos valores de color para cada píxel que ocupe cada polígono en la pantalla, en lugar de valores para cada polígono.
Una forma de suavizarlo es el Sombreado Gouraud (en honor a su inventor, Henri Gouraud), que consiste en obtener un vector normal para cada vértice en lugar de cada cara (simplemente haciendo la media de los vectores normales de las caras en las que participa ese vértice). Así podemos calcular la intensidad de luz, y por tanto el color, para cada vértice. Y una vez obtenido, podemos interpolar ese color a lo largo de la superficie entre un vértice y otro, obteniendo un degradado de color. Para el que no conozca el término, podría decirse que una interpolación es una especie de regla de tres para calcular cualquier valor intermedio a otros dos.
Otra forma más precisa de hacerlo es interpolar el vector normal, en lugar del color, de forma que el suavizado queda más natural. Esto se conoce como Sombreado Phong (que toma el nombre del pionero Bui Tuong Phong), que quizá sea el modelo de suavizado más usado hoy día. Sin embargo en este caso la pega es que, al variar la normal, hay que repetir la fórmula y calcular la intensidad de luz para cada píxel, en lugar de para cada vértice, lo que es bastante más costoso en tiempo de cálculo. Afortunadamente, el hardware de las tarjetas gráficas está especializado en realizar este tipo de cálculos.
Esta interpolación de suavizado se suele poder controlar en el software de modelado para indicar qué caras de un modelo se suavizan, y cuales no. En 3D Studio Max con los llamados grupos de suavizado, y en Maya, si no me equivoco, es posible hacerlo indicando soft edges para suavizar, y hard edges para lo contrario. La información de las normales por vértice las calculará el software de modelado que estemos usando, y se adjuntan a la información de la propia geometría. Esto permite al modelador modificarlas si quiere, y ahorrar tiempo de cálculo de iluminación, pero será más memoria la que ocupe el modelo.
En resumen
Hay que tener en cuenta que todo lo que he explicado hay que repetirlo para cada componente de color. Por eso es interesante tratar los colores con números reales y vectores, ya que hay que operar bastante con ellos.
El proceso de renderizado se recorre cada vértice y cada píxel encerrado entre varios vértices y obtiene el color que se aplicará en la pantalla, usando una fórmula de iluminación como la que os he comentado aquí. Además, todo este procedimiento se realiza en tiempo real, una o varias veces por cada fotograma, por lo que interesa que sea lo más rápido posible. En general, la iluminación es un proceso costoso que aumenta exponencialmente de complejidad con cada fuente de luz. Por ello, las tarjetas gráficas están diseñadas específicamente para realizar estas tareas a toda velocidad. Recordemos que, al contrario del cine, que va a veinticuatro fotogramas por segundo, idealmente los juegos deben correr a unos sesenta fotogramas por segundo —y si es más mejor— aunque pocos juegos consiguen esa frecuencia. De hecho lo normal gira alrededor de los treinta fotogramas por segundo, de los que se intenta no bajar para evitar que se vean saltos.
Esto no es todo, por supuesto. Con estas fórmulas sólo conseguimos pintar los modelos de un solo color, y no se consiguen sombras proyectadas de unos modelos sobre otros, cosa que implica cálculos bastante más complicados. Con lo que debemos quedarnos de este escueto artículo, es con el concepto de vector normal y cómo afecta a la iluminación (ya que se usa para uno de los efectos gráficos más comunes de la actual generación de videojuegos: el normal mapping), y empezar a darnos cuenta de que hay ciertas operaciones que se realizan a nivel de vértices, como transformar la geometría en función de las matrices, y otras operaciones a nivel de píxel como la iluminación. Esto es la razón de que existan los vertex shaders y los pixel shaders, pero no adelantemos más acontecimientos.