Ray Tracing: Barrido del Plano de Proyección

Juan Mellado, 14 Octubre, 2005 - 18:46

Este artículo detalla el proceso de construcción del algoritmo encargado de realizar el barrido con rayos del plano de proyección en un sistema básico de Ray Tracing. Indicando, uno a uno, qué parámetros de entrada son necesarios, y qué valores se calculan a partir de ellos. Siendo el objetivo final generar un rayo por cada pixel. Rayos que partan de la posición del observador, y pasen por el centro de cada uno de los pixeles del plano de proyección.

El plano de proyección es la superficie sobre la que representa la imagen bidimensional generada a partir de la escena definida tridimensionalmente. En la práctica el plano tiene una extensión continua ilimitada, pero de él sólo se toma una pequeña región, normalmente rectángular, a modo de ventana. Esta ventana se divide a su vez en una cuadrícula rectángular de filas y columnas en la que cada celda representa un pixel al que puede asignársele un color individualmente.

Este artículo trata de cómo se define dicho plano de proyección, de cómo se calcula la posición de cada pixel dentro del mismo, y de cómo se pueden construir rayos que los recorran ("barran").

Sistema de Referencia

En lo sucesivo consideraremos un sistema de referencia en el que el eje horizontal X crece de izquierda a derecha, el vertical Y de abajo hacia arriba, y los valores sobre el eje de profundidad Z aumentan a medida que se penetra en el plano XY (adentrándose en la pantalla del monitor, si se toma este como referencia).

Dirección en la que mira el Observador

Los primeros parámetros de entrada al sistema a considerar son la posición que ocupa el observador y el punto concreto al que mira. Normalmente el observador de la escena recibe el nombre de "cámara", pero por el momento seguiré llamándolo observador. Creo que el término cámara tiene unas connotaciones técnicas implícitas, como puede ser el uso de lentes, que no resultan adecuadas introducir aún. Me parece más natural considerar un observador humano.

Denotaremos con EYE a la posición del observador, y LOOKAT al punto concreto que mira.

Sistema de Referencia

El primer valor calculado será la dirección VIEW en la que mira el observador, que es la dirección del vector que une EYE con LOOKAT:

VIEW = (LOOKAT - EYE) / |LOOKAT - EYE|

La mayoría de los vectores que se calculen se normalizarán para simplificar cálculos posteriores. Es decir, se dividirán por su módulo (longitud) para convertirlos en vectores unitarios. Los vectores unitarios tienen módulo unidad, lo que interesa de ellos es su dirección y sentido.

Los parámetros EYE y LOOKAT se pueden inicializar con valores por defecto evitando tener que pedir al usuario que los introduzca. Por ejemplo, EYE = (0, 0, 0) y LOOKAT = (1, 1, 1) situan al observador en el origen de coordenadas mirando a un punto situado arriba a su derecha. Aunque lo normal es que se obligue a introducir al menos uno de estos dos valores.

En ningún caso EYE y LOOKAT deben ser iguales, ya que ello equivaldría a decir que el observador observa la misma posición que ocupa, lo que es físicamente imposible. Y matemáticamente equivaldría a que la longitud de VIEW fuese cero, o sea, un punto en vez de un vector, no se podría realizar la división para normalizarlo.

Arriba y Abajo

El siguiente parámetro de entrada requiere una explicación un poco más detallada. Se trata de indicar cual es el plano vertical que considera el observador.

Intentaré explicarlo con un ejemplo. Supongamos que quiere hacer una fotografía. Se ubica, apunta al objetivo, y retrocede o avanza para captar mejor el motivo a fotografiar. Sin embargo, a veces no es posible incluir todo lo observable en la fotografía, no cabe. Como cuando se retrata a una persona de pie. Si se apunta con la cámara en su postura natural las piernas salen "cortadas". Por lo que se utiliza el truco de girar la cámara y ponerla en posición vertical para que la persona salga completa, a lo ancho de la fotografía en vez de a lo alto. Lo que estaba "arriba" ahora está en un lateral.

El observador de una escena, al igual que un fotógrafo, debe especificar que parte de la escena debe quedar "arriba", para que no exista ningún tipo de ambigüedad con respecto al resultado buscado. Para ello se define otro parámetro de entrada al sistema, el vector unitario UP que apunta hacia "arriba".

El vector UP suele tomar el valor (0, 1, 0) por defecto, es decir, apuntando hacia el "cielo". Y como se verá en el siguiente apartado, su dirección no tiene porque ser perpendicular (formar un ángulo recto) a la dirección en la que mira el observador.

Ejes sobre el Plano de Proyección

El plano de proyección contiene la ventana sobre la que se representa la escena. Y para definir un plano matemáticamente se necesita al menos un punto sobre el mismo y un vector normal a la superficie. En la práctica existen más formas de definir un plano, pero esta forma es la más conveniente para este caso, ya que se dispone del vector VIEW, normal (perpendicular) al mismo, y el punto sobre el plano se calculará muy fácilmente introduciendo la distancia dis que separa al observador del plano.

La distancia dis es un parámetro de entrada al sistema. Suele asignársele el valor 1 por defecto. Y en todo caso debe ser un valor positivo mayor que cero, ya que un valor de cero querría decir que el observador se encuentra contenido en el propio plano de proyección, y un valor negativo que el plano de proyección se encuentra por detrás de la dirección en la que se mira.

Plano de Proyección

El punto sobre el plano, CENTER, se obtendrá partiendo del observador y siguiendo la dirección de visión una distancia dis:

CENTER = EYE + (dis * VIEW)

Este punto CENTER se utilizará como origen de un eje de coordenadas locales sobre la ventana en la que se proyecta la escena, es decir, un eje centrado en ella que será, a la postre, el que permita moverse por ella recorriendo todos los pixels que la forman.

Los ejes de coordenadas se definen matemáticamente con un punto origen, que ya se tiene (CENTER), y tres vectores, del que también se tiene uno (VIEW). El vector VIEW permite moverse en profundidad con respecto al plano de proyección, y el eje de coordenadas se completará añadiendo dos nuevos vectores, HORZ y VERT, que permitan moverse horizontal y verticalmente.

El vector HORZ es normal al plano que contiene a los vectores unitarios UP y VIEW, por lo que su cálculo es sencillo. Se obtiene del producto cruzado (vectorial) de UP por VIEW:

HORZ = (UP x VIEW) / |UP x VIEW|

Se tiene que tener en cuenta que UP y VIEW no pueden tener la misma dirección, ya que HORZ no podría calcularse. Este caso ocurre cuando se indica que la dirección en la que mira el observador es la misma que la dirección indicada para "arriba". Un sistema así definido no es coherente.

Por su parte, el vector VERT es normal al plano que contiene a los vectores unitarios VIEW y HORZ. Por lo que puede obtenerse del producto cruzado de ambos vectores:

VERT = (VIEW x HORZ) / |VIEW x HORZ|

La explicación de qué consiste el producto vectorial queda fuera del alcance de este artículo, puede encontrarse en cualquier tutorial básico de cálculo de vectores. Baste decir que dados dos vectores A = (ax, ay, az) y B = (bx, by, bz), el producto vectorial es el vector resultante de evaluar la siguiente expresión:

A x B = (ay * bz - az * by, az * bx - ax * bz, ax * by - ay * bx)

Tamaño de la Ventana de Proyección

El tamaño de la ventana de proyección es el tamaño del rectángulo en el que se impresiona la escena observada. Para su cálculo necesitamos introducir dos nuevos parámetros de entrada, dos ángulos. El ángulo de visión del observador en el plano horizontal hfov, y el ángulo de visión del observador en el plano vertical vfov. La nomenclatura de los nombres procede del inglés "Field Of View" (Campo de Visión).

Estos ángulos determinan el ancho y alto de la ventana de proyección en la medida que las partes visibles de la escena serán sólo aquellas que se encuentren dentro de estos ángulos de visión.

Para calcular el ancho de la ventana de proyección se debe notar que el observador y el centro de la ventana se encuentran ambos contenidos dentro del plano que definen VIEW y HORZ, por lo que el cálculo se puede reducir a un problema de trigonometría 2D sobre este plano:

Tamaño de la Ventana de Proyección

Resolviendo el triángulo superior de la imagen se obtiene el ancho del campo de visión:

TAN(hfov / 2) = (hsize / 2) / dis
hsize = 2 * dis * TAN(hfov / 2)

Y siguiendo el mismo razonamiento se obtiene la altura del campo de visión:

vsize = 2 * dis * TAN(vfov / 2)

Los dos ángulos pueden tomar un mismo valor de entrada por defecto, unos 45 grados, lo que haría que se genere una ventana de proyección cuadrada. Algunos sistemas prefieren que se indique el aspect ratio (cociente entre el ancho y alto) de la imagen deseada, y calcular los ángulos a partir de esta cantidad.