Por Paul Rademacher
No obstante las gráficas por computadora tridimensionales han existido por varias décadas, ha habido una oleada de interés general hacia el campo en los últimos dos años. Justamente una rápida mirada a las películas más recientes en vídeo es suficiente para ver la fascinación del público con la nueva generación de gráficas. Tan excitantes como las gráficas lo son, sin embargo, hay una barrera definitiva la cual previene a la mayoría de la gente aprender acerca de ellas. Por una lado, hay mucho de matemáticas y teoría involucrado. Mas allá de eso, el obtener una ventana para desplegar, no obstante gráficas simples 2D, puede ser a menudo una tarea que desanima. En este artículo, hablaremos acerca de un poderoso, no obstante simple, método de graficación 3D conocido como seguimiento de rayos, el cual puede ser entendido e implementado sin tratar con muchas matemáticas o lo intrincado de los sistemas de ventana. La única matemática supuesta es un conocimiento básico de vectores, productos punto y productos cruzados. No cubriremos la mayoría de la teoría de seguimiento de rayos, enfocándonos en lugar sobre una vista general de la técnica desde una perspectiva orientada a la implementación. El código fuente completo en C++, para un simple seguidor de rayos independiente del hardware está disponible en el web, para mostrar cómo los principios descritos en este artículo son aplicados.
El seguimiento de rayos es una técnica para representar las gráficas tridimensionales con interacciones de luz muy complejas. Esto significa que se pueden crear gráficas llenas de espejos, superficies transparentes y sombras, con asombrosos resultados. Discutimos el seguimiento de rayos en este artículo introductorio de gráficas ya que es un método muy simple para ambos, entender e implementar. Es basado en la idea que se puede modelar la reflexión y refracción mediante el seguimiento recursivo de la trayectoria que la luz sigue como esta rebota a través de un entorno.
Figura 1. Imagen Seguida de Rayos
Antes de presentar la descripción de seguimiento de rayos, se debe poner de acuerdo sobre alguna terminología básica. Cuando creamos cualquier clase de gráficas por computadora, se debe tener una lista de objetos que se desea que el software represente. Estos objetos son parte de la escena o mundo (Figura 2), así que cuando hablamos acerca de "mirar en el mundo" no se está tomando un reto filosófico, sino solo refiriendo al seguidor de rayos trazando los objetos desde un punto de vista. En las gráficas, este punto de vista se denomina el ojo o la cámara. Siguiendo esta analogía de la cámara igual que una cámara necesita un rollo sobre el cual la escena es proyectada y grabada, en las gráficas se tiene una vista en una ventana sobre la cual se traza la escena. La diferencia es que mientras en las cámaras la película es colocada detrás de la apertura o punto focal, en las gráficas la vista-ventana está en frente del punto focal. Así que el color de cada punto en la película real es causado por un rayo de luz (realmente, un grupo de rayos) que pasan a través de la apertura y golpean la película, mientras que en las gráficas por computadora cada pixel de la imagen final es causado por un rayo de luz simulado que golpea la vista-ventana en su trayectoria hacia el ojo. Los resultados, sin embargo, son los mismos.
Como se puede haber imaginado, nuestra meta es encontrar el color de cada punto sobre la vista de la ventana. Dividimos la vista de la ventana dentro de pequeños cuadrados, donde cada cuadrado corresponde a un pixel en la imagen final. Si se desea crear una imagen en la resolución de 600 por 400, se debe dividir la vista de la ventana dentro de una rejilla de 600 por 400 cuadrados. El problema real, entonces, es asignar un color a cada cuadrado. Esto es lo que el seguimiento de rayos hace.

El seguimiento de rayos es denominado así ya que este trata de simular la trayectoria que los rayos de luz toman cuando rebotan dentro del mundo - ellos son seguidos a través de la escena. El objetivo es determinar el color de cada rayo de luz que golpea la vista de la ventana antes de que alcance el ojo. Un rayo de luz puede ser imaginado como si fuera un único fotón (no obstante esto no es estrictamente exacto ya que la luz tiene propiedades de onda - pero como se prometió no insistiremos en la teoría). El nombre "seguimiento de rayos " es un poco malinterpretado ya que la suposición natural sería que los rayos son seguidos con un inicio en su punto de origen, la fuente de luz, y hacia su destino, el ojo (ver la Figura 3). Esto podría ser una manera exacta de hacer esto, pero desafortunadamente tiende a ser muy difícil debido al número de detalles involucrados. Considere el seguimiento de rayos de esta manera a través de una escena con una luz y un objeto, tal como una mesa. Iniciamos en el foco, pero primero se necesita decidir cuantos rayos salen del foco entonces para cada rayo se tiene que decidir en que dirección está yendo. Hay realmente una infinidad de direcciones en la cual este puede viajar - ¿cómo sabemos cual escoger? Asumiendo que estas preguntas han sido contestadas ahora son seguidos un número de fotones. Algunos alcanzarán el ojo directamente, otros rebotarán y entonces alcanzarán el ojo y muchos más probablemente nunca alcanzarán el ojo en lo absoluto. Para todos los rayos que nunca alcanzan el ojo, el esfuerzo de seguimiento fue en vano.

Para ahorrar este esfuerzo en vano, trazamos solamente aquellos rayos que son garantizados a golpear la ventana de la vista y alcanzar el ojo. Parece a primera vista imposible conocer de antemano cuales rayos alcanzan el ojo. Después de todo, cualquier rayo dado puede rebotar alrededor del cuarto muchas veces antes de alcanzar el ojo. Sin embargo, si se mira el problema de regreso, se ve que tiene una simple solución. En vez de seguir los rayos iniciando en la fuente de la luz, se siguen ellos de regreso, iniciando en el ojo. Considere cualquier punto sobre la ventana de la vista cuyo color se está tratando de determinar. Su color es dado por el color del rayo de luz que pasa a través de ese punto en la ventana de la vista y alcanza el ojo. Se puede seguir el rayo de regreso iniciando en el ojo y pasando a través del punto en su camino hacia la escena. Los dos rayos serán idénticos. Excepto por su dirección: si el rayo original viniera directamente desde la fuente de luz, entonces el rayo de regreso irá directamente hacia la fuente de luz; si el rayo original rebotó en una mesa primero, el rayo de regreso también rebotará (bounce off) de la mesa. Se puede ver esto observando en la Figura 3 otra vez e invirtiendo las direcciones de las flechas. Así el método de regreso hace la misma cosa como el método original, excepto que no desperdicia cualquier esfuerzo sobre los rayos que nunca alcanzaron el ojo.
Esto, entonces, es la manera como el seguimiento de rayos trabaja en gráficas por computadora. Para cada pixel sobre la ventana de la vista, se define un rayo que se extiende del ojo al punto. Se sigue este rayo dentro de la escena y como rebota este de diferentes objetos. El color final del rayo ( y por lo tanto del pixel correspondiente) está dado por los colores de los objetos golpeados por el rayo como este viaja a través de la escena.
Igual como en el método luz-fuente-al-ojo el cual toma una gran cantidad de rebotes antes de que el rayo golpee el ojo, en el método de regreso pudiera haber muchos rebotes antes que el rayo siquiera alcance la luz. Ya que se necesita establecer algún limite sobre el número de rebotes para seguir el rayo, se hace la siguiente aproximación: cada vez que un rayo alcanza un objeto, se sigue un nuevo rayo desde el punto de intersección directamente hacia la fuente de luz (Figura 4).
En la figura se ven dos rayos, a y b, los cuales interceptan la esfera púrpura. Para determinar el color de a, se sigue el nuevo rayo a' directamente hacia la fuente de luz. El color de a entonces dependerá de varios factores, discutidos en Color y Sombreado abajo. Como se puede ver, b estará sombreado ya que

el rayo b' hacia la fuente de luz está bloqueado por la esfera misma. El rayo a podría haber estado sombreado si otro objeto bloqueó el rayo a'.
De la misma manera como las sombras son manejadas con facilidad, así lo son la reflexión y la refracción. En el ejemplo de arriba, se consideró un único rebote desde el ojo a la fuente de luz. Para manejar la reflexión consideramos múltiples rebotes desde objetos y para manejar la refracción, se considera lo que sucede cuando un rayo pasa a través de un objeto parcial - o completamente - transparente.
Si un objeto es reflectivo simplemente se sigue un nuevo rayo reflejado desde el punto de intersección hacia la dirección de reflexión. El rayo reflejado es la imagen espejo del rayo original, apuntando lejos de la superficie. Si el objeto es en alguna extensión transparente, entonces también seguimos un rayo refractado dentro de la superficie. Si los materiales sobre cualquier lado de la superficie tienen diferentes índices de refracción, tales como aire en un lado y agua en el otro, entonces el rayo refractado se doblará, igual que la imagen de un popote en un vaso con agua. Si el mismo medio existe sobre ambos lados de la superficie entonces el rayo refractado viaja en la misma dirección como el rayo original y no es doblado.

Las direcciones de los rayos reflejados y refractados están dados por las siguientes fórmulas. Para un rayo con dirección V, y una superficie con normal N ( la normal es la dirección perpendicular a la superficie - apuntando directamente lejos de esta), la dirección del rayo reflejado R1 está dada por
Note que ya que V, N y R1 son vectores con componentes x, y y z la aritmética sobre ellos es realizada por componente. El rayo refractado Rr está dado por
REFLEXION Y REFRACCION RECURSIVAS
La figura 6 muestra un ejemplo de objetos que son ambos reflectivos y refractivos (no se muestran los rayos de cada intersección a la fuente de la luz, para mayor claridad). Note cuanto mas complicado los rayos se convierten una vez que la reflexión y la refracción son añadidas. Ahora, cada rayo reflejado y refractado puede golpear un objeto el cual engendra otros dos rayos, cada uno de estos puede engendrar otros dos, y así sucesivamente. Este arreglo de rayos engendrando subrayos y estos a su vez engendrando otros conduce a la idea de un árbol de rayos. El nodo raíz de este árbol es el rayo original (a en la figura) del ojo y cada nodo en el árbol es cualquier rayo reflejado o refractado del rayo sobre este. Cada hoja del árbol es cualquiera donde el rayo alcanzó un objeto no reflexivo y no transparente o donde la profundidad del árbol alcanzó un máximo. Tan complicado como aparece, existe realmente una manera simple de implementar este árbol de rayos: con llamadas a una función recursiva. Para cada punto de la ventana de la vista llamamos una función trace_ray(), pasando esta el rayo del ojo a través del punto. Si el primer objeto interceptado por el rayo es no reflectivo y no transparente, la función simplemente determina el color en el punto de intersección y retorna este color. Si este rayo golpea un objeto reflexivo, sin embargo, la función invoca trace_ray() recursivamente, pero con el rayo reflejado en su lugar. Si el objeto interceptado es transparente, la función también llama a trace_ray() con el rayo refractado como un parámetro. Los colores retornados por estas dos llamadas a la función son combinadas con el objeto color y un color combinado es regresado. Esto puede ser fácilmente expresado en pseudocódigo como:
Color trace_ray( Ray original_ray )
{
Color point_color, reflect_color, refract_color
Object obj
obj = get_first_intersection( original_ray)
point_color = get_point_color( obj )
if ( object is reflective ) reflect_color =
trace_ray( get_reflected_ray
(original_ray, obj ) )
if ( object is refractive )refract_color =
trace_ray( get_refracted_ray
(original_ray, obj ) )
return ( combine_colors( point_color,
reflect_color, refract_color ))
}
donde un Color es la tripleta (R, G, B) para los componentes rojo, verde y azul del color, el cual puede variar entre cero y uno. Esta función, además de unas subrutinas de intersección, es todo lo necesario para escribir un seguidor de rayos.
Uno de los cálculos básicos necesarios para el seguidor de rayos es una rutina de intersección para cada tipo de objeto de la escena: uno para esferas, uno para cubos, uno para conos y así para los demás. Si un rayo intersecta un objeto, la rutina de intersección de objetos regresa la distancia del punto de intersección desde el origen del rayo, el vector normal en el punto de intersección, y si el mapeo de textura esta siendo usado un mapeo de coordenadas entre el punto de intersección y la imagen de textura (discutido más tarden el Mapeo de Textura). La distancia al punto de intersección es necesaria ya que si un rayo intersecta mas de un objeto, escogemos el que tenga el punto de intersección más cercano. La normal es usada para determinar la sombra en el punto ya que esta describe la dirección en la cual la superficie esta enfrentando, y por lo tanto afecta cuanta luz recibe el punto (ver Color y Sombreado).
Para cada tipo de objeto que el seguidor de rayos soporta, se necesita una rutina por separado. Se limitara el seguidor de rayos para manejar esferas únicamente, ya que la rutina de intersección para una esfera está entre las más simples. La siguiente derivación de la intersección de una esfera está basada en [4], página 388.

La figura 7 muestra la geometría de un rayo R (el cual se origina en E y con dirección V) intersectando una esfera con centro en O y radio r. De acuerdo al diagrama,
Hay muchas maneras diferentes para determinar el color en un punto de intersección. Algunos métodos son muy simples - como en el sombreado plano, donde cada punto en el objeto tiene el mismo color. Algunas técnicas - tales como el método Cook-Torrance [1] - son bastante complejos, y toman en cuenta muchos atributos físicos del material en cuestión. Se describirá aquí un modelo simple conocido como sombreado Lambertiniano o sombreado por coseno. Este determina la brillantez de un punto basado en el vector normal en el punto y el vector del punto a la fuente de luz. Si los dos coinciden (la superficie está directamente en frente de la luz) entonces el punto está en la intensidad completa. Cuando el ángulo entre los dos vectores aumenta, como cuando la superficie está inclinada lejos de la luz, entonces la intensidad disminuye. Este modelo es conocido como el "sombreado por coseno" ya que la función matemática fácilmente implementa el efecto anterior: retorna el valor de uno cuando el ángulo es cero y retorna cero cuando el ángulo es noventa grados (cuando la superficie y la luz son perpendiculares). Entonces, para encontrar la brillantez de un punto, simplemente se toma el coseno del ángulo entre los dos vectores. Este valor puede ser rápidamente calculado ya que es igual al producto punto de los vectores de longitud unitaria. Entonces, tomando el producto punto del vector normal a la superficie y el vector unitario hacia la luz, se obtiene un valor entre -1 y 1. Los valores de -1 a 0 indican que la superficie se está retirando de la fuente de la luz, de tal manera que deja de recibir luz. El valor de cero significa que la superficie está directamente perpendicular a la fuente de luz (esto está en una incidencia de nivelación), y así otra vez no recibe luz en lo absoluto. Los valores arriba de 0 indican que la super ficie recibe alguna luz, con una recepción máxima cuando el producto punto es 1.
En el caso que el resultado del producto punto sea cero o abajo de cero aún se puede no querer que el objeto esté en total obscuridad. Después de todo, aun cuando un objeto es bloqueado completamente de la fuente de luz, hay aun luz rebotando alrededor que ilumina al objeto en cierta medida. Por ello agregamos un poco de luz ambiente a cada objeto lo cual significa que la mínima cantidad de luz que un objeto puede recibir es mayor que 0. Si usamos un coeficiente del ambiente como 20%, por decir un valor, entonces no obstante en la obscuridad total un objeto recibirá 20% de iluminación y entonces será algo visible. Si el 20 % de iluminación es siempre 20% visible, entonces el 80% restante es determinado por el sombreado por coseno. El valor de 80% en este caso es conocido como el coeficiente difuso, el cual es justamente (1- el coeficiente del ambiente). El cálculo final del color es entonces:
Hasta aquí hemos asumido que cada punto sobre el objeto dado tiene el mismo color básico, variado solamente a través del sombreado por coseno para hacer este más claro o más obscuro. Sin embargo, hay otro camino para colorear objetos, el cual asigna imágenes enteras a ello en vez de sólo colores. Conocido como mapeo de textura, en efecto se "pegan" imágenes a las superficies de los objetos. Un ejemplo de esto lo cual ciertamente es reconocible está en los juegos al estilo "Doom" – donde las paredes están dibujadas utilizando modelos de piedra o metal en vez de colores únicos. Estos programas inician con imágenes pequeñas de aquellos modelos (llamados texturas), y entonces cubre las paredes con estas imágenes. Esta cobertura es llamada mapeo ya que mapea pixeles de la imagen a puntos de las paredes. El mapeo de textura es mucho más lento que asignar un único color a cada objeto, ya que este requiere determinar y entonces buscar el pixel de textura apropiada correspondiente a cada punto que se dibuja. Se ha convertido muy común en los últimos pocos años, sin embargo, gracias a los avances en las velocidades de los procesadores y los algoritmos de graficación más rápidos.
Mientras el mapeo de textura produce efectos muy agradables, no es difícil para implementar. Todo lo que es necesario es una rutina que regrese las coordenadas de un pixel de textura dadas las coordenadas de un punto sobre un objeto. Las coordenadas de textura son referidas como (u, v), donde u es la posición horizontal del punto en la imagen, y v es la posición vertical del punto. El seguidor de rayo entonces busca el pixel en (u, v) de la imagen de textura y ajusta esta como sea necesario usando el sombreado por coseno para hacer esta más claro o más obscuro.
.
La manera más fácil para mapear textura sobre una esfera es definiendo a v como la latitud del punto y u a ser la longitud del punto en la esfera. De esta manera, la imagen de textura será "envuelta" alrededor de la esfera tanto como el mapa del mundo rectangular es envuelto alrededor de una esfera.
Figura 8. Mapeo de un mundo rectangular sobre una esfera.
Figura 9. Mapeo de una imagen de textura sobre una esfera
Primero se encuentran los dos vectores de longitud unitaria Vn y Ve , el cual apunta desde el centro de la esfera hacia " el polo norte" y un punto sobre el ecuador respectivamente. Se encuentra el vector de longitud unitaria Vp , desde el centro de la esfera al punto que se esta coloreando. La latitud es simplemente el ángulo entre Vp y Vn . Ya que notamos arriba que el producto punto de dos vectores de longitud unitaria es igual al coseno del ángulo entre ellos, se puede encontrar el ángulo mismo por
Entonces encontramos la longitud como
La última comparación simplemente checa sobre que lado del vector ecuador Ve está el punto (a favor o en contra de las manecillas del reloj respecto a este) y fija u de acuerdo a esto. Ahora el color del punto es el pixel en (u* textura_amplitud, v* textura_altura). dentro de la imagen de la textura. Para mayor información sobre esto leer [3], página 48.
Primero, se necesita definir la escena que deseamos representar. Se crea un archivo de entrada de texto que describe los objetos en la escena al especificar la posición, atributos (tales como el radio para las esferas), color y mapas de textura opcionales para cada objeto. Entonces se especifica donde la cámara y la ventana de la vista deberían de estar posicionados. Por simplicidad, se puede asumir que la cámara está siempre sobre el eje Z hacia el origen y que la ventana de la vista esta localizada en el origen. También se necesita determinar el tamaño de la ventana de la vista el cual es especificado por el parámetro de un campo de vista (field of view FOV). El campo de la vista determina el tamaño de la ventana de la vista y por lo tanto cuanto de la escena es visible. Basado en estos parámetros, se puede calcular las esquinas superior izquierda e inferior derecha de la ventana (ver el código en línea para un ejemplo)
Ahora que se tiene los puntos de la esquina superior izquierda e inferior derechas, se divide la ventana dentro de la resolución requerida para la fotografía , es decir, 640 por 400. Se ve a través de cada uno de los puntos 640 por 400 en la ventana de la vista definiendo un rayo desde el ojo hasta ese punto. Para cada rayo, se calcula su intersección con todos los objetos en la escena. Si este no intercepta a cualquier objeto, ponemos el rayo - y por lo tanto el pixel correspondiente - a el fondo del color. Por otro lado, se selecciona el objeto con la intersección más cercana. Si el objeto es reflexivo, se llama recursivamente la función del seguimiento de rayo otra vez, pasando este la dirección del rayo reflejado. Si el objeto es transparente, se hace la misma cosa con la dirección refractada. La función que llama entonces calcula el color para el objeto que está tratando y combina este color con los colores relajados y refractados.
Cuando la llamada original del seguidor de rayos del ojo a la ventana de la vista regresa, el color será el color final del pixel - tomando en cuenta reflexión, refracción y sombras. Este color de pixel es entonces escrito al archivo de salida. Cuando todos los puntos de la ventana de la vista han sido procesados de esta manera, se tendrá una fotografía terminada seguida por rayos.
Se puede encontrar un código completo en C++ para un seguidor de rayos simple en http://www.cs.unc.edu/~rademach/xroads-RT. El archivo README describe como compilar y correr el programa y muestra donde encontrar las partes importantes del código así que se puede ver como las ideas descritas arriba son implementadas. Se encontrará que el código es muy básico y tiene mucho espacio para enriquecerse.
Aquí están algunas imágenes creadas con el simple código disponible en línea. Ellas demuestran las técnicas descritas en este artículo. Para ver unas asombrosas imágenes creadas con características completas, seguidores de rayos libres visita los enlaces en la sección de Recursos de Internet aquí abajo.
Hay muchos recursos sobre seguidores de rayos en el Internet. A continuación se enlistan algunos de los mejores:
REFERENCIAS
1 Cook, R.L., and Torrance, K.E. A Reflectance Model for Computer Graphics. ACM Trans. On Graphics 1, 1 (Jan 1982)
2 Foley, J.D., and Van Dam, A. Computer Graphics: Principles and Practice, Second Edition. Addison-Wesley New York, N.Y. 1990.
3 Glassner, A. (ed) An Introduction to Ray Tracing. Academic Press New York, N.Y. 1989.
4 Glassner, A (ed) Graphics Gems. Academic Press New York, N.Y. 1990.
5 Watt, A., and Watt, M. Advanced Animation and Rendering Techniques. Addison-Wesley New York, N.Y. 1990.