Revistas en papel
 Commodore World Nº50
Anterior
Menú
Logotipo

AMIGA WORLD

Los maravillosos mundos
del Ray-tracing

Por Eric Graham

Observa al
robot
haciendo
malabarismos
con las
esferas
plateadas.
Permanece
de pie en el
paisaje
mientras

Un ejemplo - 52 Kb

brilla bajo la
luz de los
focos. Sin
embargo, es
sólo un
fantasma de
los

Este autómata coloreado no es el típico dibujo de un robot hecho por ordenador. No ha sido dibujado al detalle con un programa de dibujo, ni es una imagen digitalizada de una foto o un modelo tridimensional. El y su mundo surrealista han sido creados "automáticamente" por un programa en C. En este artículo describiré cómo se crean estas simulaciones gráficas mediante una técnica conocida como ray-tracing.

Este artículo va acompañado de un programa de ray-tracing escrito en C llamado "Simulador de Escenas Gráficas". Estas imágenes requieren la generación de gráficos en modo HAM (Hold-And-Modify), para obtener 4.096 colores a la vez. Debido a la limitación de espacio, el listado no incluye las rutinas para generar las imágenes en HAM. El procedimiento para crear estas rutinas puede encontrarse en el "Amiga ROM Kernel Reference Manual". Para aquellos que os iniciéis en el C (o para los que no os gusta programar), al final del artículo se indica cómo conseguir el programa completo en disco.


Perspectiva

Las imágenes de ordenador están hechas generalmente de figuras bi-dimensionales. En el caso del Amiga, estas figuras pueden colorearse. Como en cualquier otro tipo de arte bi-dimensional, combinando los gráficos de la forma adecuada el resultado puede dar la apariencia de una tercera dimensión con objetos sólidos. O por decirlo de otra manera: las formas y objetos tridimensionales de un escenario pueden descomponerse en formas bidimensionales en la pantalla de un ordenador. El producto final, una imagen en perspectiva, consigue dar la apariencia de realidad, volumen y profundidad. El conjunto, en este caso, es algo más que la suma de las partes.

Las simulaciones gráficas por ordenador muy sofisticadas van más allá todavía e intentan sustituir las imágenes de ordenador de "dibujos animados" (colores sólidos) por imágenes que contienen todas las variaciones de sombra y color que pueden encontrarse en el mundo real. Estas simulaciones gráficos son muy comunes y pueden verse hoy en día en los programas de televisión, en los espacios deportivos y en la animación de logotipos. Este tipo de simulaciones gráficas se han utilizado también en muchas películas, como Tron y The Last Starfighter.

microchips,
una sombra
imaginaria.
Puedes verlo
reflejado en
las esferas
que tan
hábilmente
lanza por el
aire, pero
habita en un
espacio que
no existe, en
un primitivo
paisaje
dentro del
sueño de
una computadora.
Aunque
parece
extrañamente
real, sólo
existe en la
memoria del
Amiga.

Aunque la mayoría de estas simulaciones emplean superordenadores como el Cray XMP o super-potentes (y caras) pantallas gráficas, nosotros podemos utilizar el Amiga para experimentar con gráficos de este tipo. El Amiga es el único ordenador entre los "baratos" que permite utilizar una gran cantidad de colores y brillos distintos en alta resolución. Utilizando el modo Hold-And-Modify (HAM), pueden usarse hasta 4.096 colores simultáneamente en la pantalla. El Amiga, como puede verse en las imágenes que he creado con este sistema, es capaz de producir gráficos de este tipo.


Una realidad alternativa

Cuando creas una simulación gráfica tienes la rara oportunidad de, en cierta manera, crear tu propio universo. Obviamente, existen algunas opciones mundanas como son cambiar los colores que se utilizan para el fondo del cielo y el suelo (si quieres que existan suelo y cielo), pero también puedes elegir tus propias leyes de la física.

Otro ejemplo - 133 Kb

Como estás intentando crear un efecto visual, tienes que decidir cómo será la luz de tu mundo. ¿Viajará en línea resta o a través de curvas? ¿Cómo se refleja en los diferentes tipos de objetos? También tienes que colocar los objetos que existen en ese universo. Finalmente, has de situar un "observador" en algún lugar, y será lo que él esté viendo lo que aparecerá en la pantalla de tu ordenador.


El universo del robot

El universo de nuestro robot es particularmente sencillo. Tiene un suelo plano, como un tablero de ajedrez gigante, que se aleja hasta el infinito. También tiene un cielo que cambia de color a medida que se aleja del horizonte (siendo un tradicionalista, elegí sombras de azul, aunque en tus programas puedes seleccionar las que tú quieras).

El universo necesita un punto desde el que tomar todas las medidas, lo que los matemáticos llaman un punto de origen. Todos los objetos de este universo tienen la misma forma: son esferas. Cada esfera tiene una posición, un radio, un color y un tipo de superficie. Las esferas tienen una profundidad muy agradable: no tienes que especificar sus orientación. Puedes rotar una esfera plana y no notarás la diferencia. La posición de cada esfera viene especificada por sus coordenadas, que son tres números. Si quieres, puedes considerar que el primero de esos números indica lo lejos que el centro de la esfera está al norte del origen. El segundo punto indicaría lo alejado que está al oeste del origen y el tercero lo lejos que está por encima del origen.

El color de la esfera se representa con tres números también, que indican la cantidad de rojo, verde y azul que refleja su superficie. La forma en que se refleja la luz en la esfera depende del tipo de superficie ésta. En este universo sólo existen tres posibilidades a considerar: primero, la esfera es opaca y por lo tanto dispersa la luz en todas direcciones; segundo, la esfera es brillante y dispersa la mayor parte de la luz que recibe, aunque una pequeña parte se refleja en cierta dirección, y, tercero, la superficie de la esfera es como la de un espejo, donde no se dispersa la luz sino que se releja en una dirección.

Otro ejemplo - 95 KbTodo lo que posiblemente interesa saber sobre las esferas de este universo se puede resumir en ocho números; tres para la posición, uno para el radio, tres para el color y otro para el tipo de superficie. La memoria del Amiga es suficientemente grande como para almacenar la información de escenas de miles de esferas, con lo que podemos, en principio, construir un universo bastante complejo.

La iluminación se obtiene posicionando varias lámparas. Las lámparas también son esferas, pero emiten luz en vez de reflejarla. Para suavizar un poco las sombras finales, hay una luz ambiente; luz difusa que viene de todas las direcciones.

El último elemento es el observador. Tal vez es mejor imaginárselo como una cámara. Vamos a intentar calcular cómo entran los rayos de luz en la cámara, para después mostrarlos en la pantalla del ordenador tal y como lo haría una cámara convencional. Hay que especificar la posición de la cámara (tres números), en qué dirección está apuntadon (dos números: uno para el ángulo circular a partir del norte y otro para el ángulo hacia arriba o abajo del horizonte) y finalmente, el ángulo de visión de la cámara, lo que los fotógrafos llaman distancia focal de las lentes de la cámara.


Cómo generar un dibujo

Hemos condensado una escena completa en unos cuantos números, de forma que puedan ser manejados por el ordenador. Ahora hay que hablar de los cálculos que deben realizarse para obtener la representación en pantalla. Hay dos formas de resolver el problema. La primera se conoce como "representación de limites" (en inglés, "boundary representation") o B-Rep, como lo conocen los expertos. Consiste básicamente en considerar dónde están los límites de cada objetos que va a aparecer en la pantalla y rellenar esa zona del color apropiado. Esto es particularmente sencillo en el Amiga porque el software del sistema permite rellenar áreas fácilmente. Por desgracia, la representación de límites tiene unas cuantas desventajas. El color de las superficies varía según las reflexiones de la luz y las sombras, por lo que rellenar un área de un color no es suficiente para conseguir un efecto realista. El segundo problema a considerar es que algunas partes del objeto pueden quedar ocultas por otras, por lo que calcular cuáles son las zonas visibles puede ser bastante complicado.

La otra forma de generar las escenas se llama Ray-Tracing, cuya traducción sería algo así como "seguimiento de rayos". La idea es muy simple: Imagina que estás en la posición del observador. Coges una lámina de cristal, rayada con líneas y formando una rejilla que coincida con la posición de los pixels de la pantalla, y la colocas delante de tí. El color que ves a través de cada cuadradito de la rejilla es justamente el que tienes que mostrar en la pantalla del ordenador, en la posición correspondiente. Sigue el camino del rayo de luz que te llega al ojo, desde la rejilla hacia el mundo exterior. Una vez que encuentras el primero objeto con el que choca el rayo de luz, puedes calcular el brillo y el color de ese punto multiplicando el color del objeto por la cantidad de luz que recibe.

La cantidad de luz que recibe ese punto se calcula considerando los rayos que van desde el punto hacia las lámparas que iluminan la escena. Cuando nada interfiere con un rayo, la iluminación depende del color, el brillo y la distancia entre la lámpara y ese punto. Según la luz incida en el objeto de forma oblicua, la iluminación por undiad de área disminuye.

El método de ray-tracing tiene un gran número de ventajas, la primera de las cuales es que los cálculos son mucho más simples que en la representación de límites. Maneja automáticamente cosas tan complicadas como la perspectiva, las reflexiones y las sombras. También permite añadir fácilmente otras características al universo, como nuevos tipos de superficie para los objetos, y diferentes leyes de reflexión y refracción. El principal defecto del ray-tracing es que, aun para las escenas más simples, es más lento que la representación de límites.


HAM: Hold-And-Modify

Para poder mostrar escenas gráficas con graduaciones de color y sombras hace falta disponer de más de dos, cuatro o incluso de 16 colores. El modo Hold-And-Modify (HAM) del Amiga permite visualizar simultáneamente hasta 4.096 colores, lo que proporciona una gran flexibilidad. Los colores que aparecen en la pantalla son combinación de los tres colores primarios: rojo, verde y azul (en el Amiga hay 16 niveles para cada color, lo que da un total de 4.096 combinaciones posibles).

Las imágenes HAM tienen una pequeña desventaja debido a la forma en que trabajan. Cuando generas una imagen en HAM, no puedes especificar directamente cuál es el color que quieres que aparezca en la pantalla. En vez de eso, debes indicar la diferencia que hay entre el color de ese pixel y el anterior. Si has puesto un pixel negro (rojo=0, verde=0 y azul=0) y quieres poner el siguiente pixel en blanco, debes, por ejemplo, modificar primero el valor del nivel de rojo, después el de verde y finalmente el de azul. La secuencia de pixels será negro-rojo-amarillo-blanco, en vez de una transición directa. Por esto, los objetos brillantes que estén sobre un fondo oscuro tendrán un pequeño "halo" a su alrededor, como sucede cuando observas objetos a través de unos


Trucos con el HAM

El modo HAM permite especificar algunos colores para los pixels de forma directa, siempre que sea alguno de los 16 básicos que puedes seleccionar y almacenar en los registros de vídeo del Amiga. Si eliges esos 16 colores cuidadosamente, podrás hacer transiciones rápidas (como blanco-negro) entre colores diferentes, lo que reduce considerablemente los cambios de color en los bordes de los objetos.

En principio, después de saber todas las transiciones de color que necesita el dibujo (unas 64.000) puedes hacer una selección de esos 16 colores para minimizar las transiciones bruscas. Esto llevaría un montón de trabajo. También puedes seleccionar esos 16 colores a tu gusto sin preocuparte del contenido del dibujo.

La definición de los
objetos, formados por
esferas de colores, es
sumamente sencilla y se
puede hacer desde un
editor o mediante algún
programa de Basic o C.

Lo que yo he hecho con el programa es emplear un método conocido como heurístico, que consiste en aproximarse a la resolución de un problema actualizando las soluciones que se van encontrando a medida que se avanza, y utilizando los nuevos resultados otra vez en los cálculos. Al principio, el programa no utiliza ninguno de los 16 colores auxiliares. Cuando aparece la primera transición, se asigna el color paropiado al primero de los registros libres. Según progresa el cálculo de la imagen, cuando se encuentran nuevas transiciones, se busca la mejor aproximación con los ya existentes. Si es mejor que la que había anteriormente, se sustituye por ella. Todo lo que hay que hacer pues, es calcular con precisión las "aproximaciones más cercanas". SI no las eliges bien, habrás ocupado todos los registros libres al principio y no podrás cmabiarlos cuando encuentres nuevos cambios de colores al final de la pantalla.

Otro de los problemas más comunes en la representación de gráficos en pantalla es conocido como "aliasing" o popularmente "gaggies" (más o menos "dientes de sierra"). Es causado por el tamaño finito de los pixels. Cuando más se aproxima una línea oblicua a la horizontal, más se acentúa la apariencia de "escalera" formada por pequeñas líneas rectas horizontales. El ojo humano es experto en distinguir estas aberraciones. Las técnicas más sofisticadas de anti-aliasing están diseñadas para reconocer estos efectos y modificar la imagen para que aparezca menos visible. Un simple truco consiste en difuminar ligeramente la imagen mezclando el color de cada pixel con el de los pixels vecinos.

Otro sutil problema aparece cuando se trata de crear sombreados graduales de color. COmo los colores en el Amiga cambian sólo en pequeños incrementos, el efecto final puede ser como el del contorno de un mapa, con anillos concéntricos de color evidente a la vista. La mejor solución es hacerse con un ordenador mejor, con más resolución y más de 16 niveles para cada color. Para nosotros, una solución más sencilla consiste en modificar aleatoriamente el valor de esos colores, para romper con la regularidad de los contornos fijos, y hacerlos menos evidentes. El resultado final es un gráfico en el que la apariencia de esa graduación es mucho mejor.


Acelerando la presentación en pantalla

En el Amiga, con una resolución de 320x200, hay que calcular el color y el brillo de 64.000 pixels. Con el método de ray-tracing, hay un solo rayo por pixel. Si en la escena hay 50 objetos, hay que comprobar si el rayo interseca con alguno de ellos. Si lo hace con varios objetos, hay que calcular cuál de ellos es el que más cerca está del observador. Para ello hay que comprobar si hay algún otro objeto entre el primero y cada una de las lámparas. La comprobación de cada intersección requiere unas doce operaciones aritméticas en coma flotante. Como el Amiga no puede realizar directamente este tipo de operaciones, cada una de ellas debe dividirse en otras más simples. El tiempo necesario para generar un solo gráfico puede llegar a ser... ¡un mes!

Cuando te enfrentas con el problema de optimizar el programa para acelerar la velocidad, hay que retroceder sobre los detalles y observar qué es lo que sucede. Obviamente, hacen falta un gran número de operacionesen coma flotante. ¿Cómo pueden acelerarse? COnvertir estas operaciones de coma flotante a operaciones con enteros es posible, pero difícil de realizar. Una mejor solución sería utilizar las rutinas aritméticas del Amiga con el "Motorola Fast Floating Point Package" (Paquete de Rutinas Rápidas de Coma Flotante). El programa que yo he utilizado para generar las pantallas que acompañan este artículo utiliza estas rutinas. Las rutinas de Motorola son menos precisas pero trabajan casi diez veces más deprisa. Con esto los dibujos necesitan tan solo unos pocos días para generarse.

El siguiente paso en la optimización es pensar cómo si fueras el ordenador y seguir los pasos de sus cálculos. Lo primero que se observa es que la mayoría de los cálculos son innecesarios. Muchos rayos no intersecan ningún objeto en su camino. Muchas líenas completas de pantalla tampoco intersecan ningún objeto. Si puedes detectar esto, se puede ahorrar un montón de tiempo, aunque el programa se complique un poco.

Con estos nuevos cálculos, la velocidad del programa aumenta en proporción de 1 a 100. Ahora ya se puede calcular una imagen completa en una hora, más o menos. Aunque se podrían realizar más mejoras, algunas requerirían grandes esfuerzos de programación. La simulación profesional en superordenadores como el Cray (con más resolución y objetos más complicados) necesita varios minutos, ¡de modo que no lo estamos haciendo nada mal con nuestro Amiga!

El método de ray-tracing permite un truco más todavía: Imagina que generas un gráficos más pequeño, tan sólo de un cuarto de pantalla. Así sólo hay que calcular el color de la cuarta parte de los pixels. Estas mini-imágenes pueden generarse en un minuto o dos y utilizarse para asegurarse de que el observador está colocado en el lugar correcto y ajustar las lámparas con el brillo correcto.


Un programa de ray-tracing

La primera parte del programa que acompaña este artículo, el Simulador de Escenas Gráficas, define las estructuras de datos que describen el mundo de las esferas. Los comentarios que se incluyen en el listado deberían ser suficientes para explicar cómo funciona, pero haré unos cuantos comentarios sobre el fondo matemático para que sea un poco más comprensible. He utilizado aritmética vectorial para la geometría tridimensional, así que no hay que complicarse con trígonometría u otras cosas parecidas.

Un vector es un objeto que tiene magnitud y dirección. Piensa en él como en una flecha. Puede apuntarse en cualquier dirección, y su longitud representa su magnitud. Un vector puede representarse mediante tres números, o componentes. Los vectores pueden sumarse o restarse simplemente sumando o restando sus componentes. En este programa ¡, la función VECSUB() realiza la resta de vectores.

La multiplicación de dos vectores puede hacerse de tres formas distintas. Si un vector se multiplica por un número, cada componente se multiplica por ese número. Se pueden multiplicar dos vectores para formar un solo número: esto se llama producto escalar, y la función DOT() es la que lo realiza en el programa. El producto escalar de dos vectores es igual a los productos de sus magnitudes multiplicados por el coseno del ángulo entre ellos (¡como puedes ver, la función DOT() es más fácil de programar que de explicar!) Es útil saber que la magnitud de un vector b viene dada por SQR(DOT(a,b)). Los vectores cuya magnitud es 1 son conocidos como vectores unitarios, y son muy útiles cuando lo único que quieres es indicar una dirección. Los vectores UHAT[] y VHAT[] son vectores unitarios que representan la orientación de la pantalla desde el punto de vista del observador.

La tercera forma de multiplicar vectores se llama producto vectorial, porque el resultado también es un vector. La función VECPROD() realiza esta tarea. El resultado es un vector perpendicular a los otros dos, cuya magnitud es igual al producto de las magnitudes de los vectores y el seno del ángulo entre ellos. Este producto es muy útil en la función REFLECT(), que calcula la dirección que torna un rayo después de ser reflejado.

Los rayos que estamos utilizando son líneas rectas pero, ¿cómo los podemos representar en el ordenador? Utilizaremos algo que se conoce como ecuación paramétrica de una recta. Es verdaderamente simple. Las coordenadas de un punto que esté situado dentro de una recta se calculan con siete números: Seis sirven para definir la recta (tres para identificar la posición y otros tres para la dirección), el séptimo, llamado parámetro, indica en qué punto de la recta está. La matriz LINE[6] se utiliza para representar una recta, y la función GENLINE() calcula sus seis componentes. La función POINT() toma una recta y el valor del parámetro, al que en el programa se llama normalmente t, y calcula las coordenadas de ese punto dentro de la recta.

Otra función geométrica importante es INTSPLIN(), que comprueba si una recta toca en una esfera en particular. Como todos los puntos de la esfera están a la misma distancia de centro, sólo hay que comprobar si alguno de los puntos de la recta está a la misma distancia. Normalmente, la recta pasa sin tocar la esfera o la atraviesa por dos puntos, uno para entrar y otro para salir. Utilizando la ecuación paramétrica de la recta pueden distinguirse estos dos casos. El que nos interesa a nosotros es el que esté más cerca del observador, porque es el único que se ve.

Se puede animar las
imágenes creada con las
técnicas de ray-tracing
grabando las imágenes
fotograma a fotograma en
un vídeo

Hay otras tres funciones importantes en el proceso de generación de una imagen ray-tracing. La función PIXLINE() genera la ecuación de una recta que corresponde a un pixel en particular de la pantalla. La función RAYTRACE() toma esa recta y mira dónde acaba. La recta puede interceptar un objeto., una lámpara o acabar en el suelo. Lo que importa en realidad es dónde toca primero, porque es lo que se ve. Si no acaba en ninguno de estos sitios, es que está apuntando al cielo, y puede calcularse el color apropiado para ese pixel de la pantalla. La última función que hay que describir es PIXBRITE(), que calcula el brillo de un pixel asociado con el rayo que toca una esfera o el suelo. Hay que calcular la iluminación de ese punto de la superfice de la esfera o del suelo.

La función PIXBRITE() comprueba cuál, si hay alguna, de las lámparas, iluminan el punto en cuestión, pues podría estar ensombrecido por otros objetos. La iluminación depende del brillo y el color de las lámparas además de su distancia y del ángulo entre la lámpara y el punto de la superfice. En el programa se indican otros detalles: por ejemplo, GINGHAM() calcula la forma del suelo ajedrezado y SKYBRITE() el color del cielo.

Como ya mencioné antes, se ha eliminado la parte del programa que genera la imagen en HAM. El procedimiento se describe en el "Amiga ROM Kernel Reference Manual". Además, es cosa tuya preparar los datos que describen los objetos del universo. Estos datos, numéricos, se almacenan en un fichero ASCII.

Los que estén interesados en conseguir un disco con el programa completo de ray-tracing (incluidas rutinas HAM), los listados fuente, las versiones compiladas, pantallas de demostración y algunos escenarios de ejemplo, pueden conseguirlo de forma gratuita al comprar el disco AmigaWorld número 2. Encontrarás información completa sobre el contenido de estos discos y la forma de pedirlos en las páginas de publicidad de este número.

           RT1.C Programa de Ray-tracing escrito en C
        Copyright 1987 by Eric Graham
        Copyright 1988 by Commodore World
 
        NOTA: Este prorgama no funciona si no se dispone de las
        rutinas necesarias para crear pantallas HAM y de los datos
        que forman los objetos para el universo de Ray-tracing.
 
        Se autoriza la copia y modificacion de este prorgama, siempre
        que se incluya este mensaje de Copyright
 
 
#define BIG 1.0e10
#define SMALL 1.0e-3
#define DULL    0
#define BRIGHT  1
#define MIRROR  2
 
double dot();          /* Producto escalar de vectores */
struct lamp {
    double pos[3];     /* posicion de la lampara */
    double color[3];   /* color de la lampara */
    double radius;     /* tamano de la lampara */
};
 
struct sphere {
    double pos[3];      /* posicion de esfera */
    double color[3];    /* color de esfera */
    double radius;      /* tamano de esfera */
    int type;           /* tipo de superficie, OPACA, BRILLANTE o ESPEJO */
};
 
struct patch {          /* objeto (o algo) visible */
    double pos[3];      /* posicion */
    double normal[3];   /* direccion 90 grados hacia la superficie */
    double color[3];    /* color */
};
 
struct world {          /* todo el universo, excepto el observador */
    int numsp;          /* numero de esferas */
    struct sphere *sp;  /* matriz para las esferas */
    int numlmp;         /* numero de lamparas */
    struct lamp *lmp;   /* matriz para las lamparas */
    struct patch horizon[2]; /* ajedrezado del suelo */
    double illum[3];    /* iluminacion difusa de fondo */
    double skyhor[3];   /* color del cielo en el horizonte */
    double skyzen[3];   /* color del cielo en el cenit */
};
 
struct observer {        /* ahora el observador */
    double obspos[3];    /* su posicion */
    double viewdir[3];   /* direccion en la que mira */
    double uhat[3];      /* derecha/izquierda en el plano de vision */
    double vhat[3];      /* arriba/abajo en el plano de vision */
    double fl,px,py;     /* distancia focal y tamano de los pixels */
    int nx,ny;           /* numero de pixels */
};
 
main()
{
    double line[6],brite[3];
    struct observer o;  strcut world w;
    int i,j,ii,jj,skip; short int si,sj;
 
    setup(&o,&w,&skip);  /*  Esta función sirve para inicializar
                    el observador y el mundo */
    si=1+(o.nx-1)/skip;
    sj=1+(o.ny-1)/skip;
    initsc(si,sj);       /*  Inicializar pantalla para modo HAM.
                             (Ver el ROM Kernel manual) */
    for (jj=j=0; j<o.ny; j+=skip,jj++)  {
        for (ii=i=0; i<o.nx; i+=skip,ii++){
            pixline(line,&o,i,j); raytrace(brite,line,&w)
            ham(ii,jj,brite);     /* Funcion para dibujar un pixel */
        }
    }
    loop: goto loop;
    cleanup(0);         /* Preparar variables con INITSC() */
}
 
raytrace(brite,line,w)  /* Hacer raytracing */
double brite[3],*line;  struct world $w;
{
    double t,tmin,pos[3];   int k;
    struct patch ptch;  struct sphere $spnear;
    struct lamp *lmpnear;
 
    tmin=BIG;  spnear=0;       /* podemos ver algunas esferas */
    for (k=0; k<w->numsp;  ++k)
        if (intsplin(&t,line,w->sp+k))  {
            if (t<tmin) {tmin=t; spnear=w->sp+k;}
        }
    lmpnear=0;                  /* mirando a la lampara */
    for (k=0; k<w->numlmp; ++k)
        if (intsplin(&t,line,w->lmp+k)) {
            if (t < tmin) {tmin=t; lmpnear=w->lmp+k;}
        }
    if (lmpnear)  {             /* vemos una lampara! */
        for (k=0; k<3; ++k)
            brite[k]=lmpnear->color[k]/(lmpnear->radius*
                     lmpnear->radius);
         return 0;
        }
    if (inthor(&t,line))        /* vemos el suelo? */
        if (t<tmin) {
 
           point(pos,t,line);  k=gingham(pos); /* ajedrezado */
           veccopy(w->horizon[k].pos,pos);
           pixbrite(brite,&(w->horizon[k]),w,0);
           return 0;
       }
    if (spnear) {              /* vemos una esfera */
       point(ptch.pos,tmin,line);  setnorm(&ptch,spnear);
       colorcpy(ptch.color,spnear->color);
       switch(spnear->type) {  /* tratar el tipo de superficie */
           case BRIGHT:        /* es brillante? */
               if (glint(brite,&ptch,w,spnear,line)) return 0;
           case DULL;
               pixbrite(brite,&ptch,w,spnear); return 0;
           case MIRROR;
               mirror(brite,&ptch,w,line); return 0;
       }
        return 0;
    }
    skybrite(brite,line,w);    /* nada mas, debe ser el cielo */
}
 
skybrite(brite,line,w)         /* calcular color del cielo *^/
double brite[#],*line;
struct world *w;
(   /* Modificar color del cuelo desde el cenit hasta el horizonte */
    double sin2,cos2;  int k;
    sin2=line[5]*line[5];
    sin2/=(line[1]*line[1]+line[3]*line[3]+sin2);
    cos2=1.0-sin2;
    for (k=0; k<3; ++k)
        brite[3]=cos2*w->skyhor[k]+sin2*w->skyzen[k];
}
 
pixline(line,o,i,j)             /* calcular rayo para el pixel i,j */
double *line;  struct observer *o;  int i,j;
{
    double x,y,tp[3];  int k;
    y=(0.5*o->ny-j)*o->py;
    x=(i-0.5*o->nx)*o->px;
    for (k=0; k<3; ++k)
        tp[k]=0->wiewdir[k]*o->fl+y*o->vhat[k]+
              x*o->uhat[k]+o->obspos[k];
    genline(line,o->obspos,tp); /* generar ecuacion de la recta */
}
 
vecsub(a,b,c)                   /* a=b-c para vectores */
double *a,*b,*c;
{
    int k;
    for (k=0; k<3; ++k) a[k]=b[k]-c[k];
}
intsplin(t,line,sp)     /* interseccion de una esfera y una recta */
double $t,$line;  struct sphere *sp;
{/* t contiene el parametro para el lugar del punto en la recta */
    double a,b,c,d,p,q,tt,sqrt();  int k; /* toca una esfera */
    a=b=0.0;  c=sp->radius; c=-c*c;
    for (k=0; k<3; ++k) {
        p=(*line++)-sp->pos[k];  q=*line++;
        a=q*q+a;  tt=q*p;  b=tt+tt+b;  c=p*p+c;
    } /* a,b,c son los coeficientes de la ecuacion cuadratica de t */
    d=b*b-4.0*a*c;
    if (d <= 0) return 0;       /* la recta no toca la esfera */
    d=sqrt(d);  *t=-(b+d)/(a+a);
    if (*t<SMALL) *t=(d-b)/(a+a);
    return *t>SMALL;    /* esfera delante del observador */
}
 
qintsplin(line,sp)      /* como antes, pero no hace falta t */
double *line;
struct sphere *sp;
{
    double a,b,c,d,p,q;  int k;
    a=b=0.0;  c=sp->radius; c=-c*c;
    for (k=0; k<3; ++k) {
        p=(*line++)-sp->pos[k];  q=*line++;
        a+=q*q;  b+=2.0*p*q;  c+=p*p;
    }
    d=b*b-4.0*a*c;  return d > 0.0;
}
 
inthor (t,line)  /* interseccion de la recta con el suelo */
double *t,*line;
{
    if (line[5] == 0.0) return 0;
    *t=-line[4]/line[5];  return *t > SMALL;
}
 
genline(l,a,b)  /* generar la ecuacion de la recta a traves de los */
double *l,*a,*b;/* puntos a y b */
{
    int k;
    for (k=0; k<3; ++k)  {*l++=a[k]; *l++=b[k]-a[k];)
}
 
double dot(a,b)      /* producto escalar de los vectores */
double *a,*b;
{
 return a[0]*b[0]+a[1]*b[1]+a[2]*b[2];
}
 
point(pos,t,line)    /* calcular posicion de un punto en la recta */
double *pos,t,*line; /* con el parametro t */
{
    int k;  double a;
    for (k=0; k<3; ++k) {
        a=*line++;  pos[k]=a+(*line++)*t;
    }
}
 
glint(brite,p,w,spc,incident)   /* mirando hacia esfera brillante? */
double brite[3];
struct patch *p;  structu world *w;  struct sphere *spc;
double *incident;
{
    int k,l,firstlite;  static double minglint=0.95;
    double line[6],t,r,lp[3],*pp,*ll,cosi;
    double incvec[3],refvec[3],ref2;
    firstlite=1;
    for (l=0; l<w->numlmp; ++l) {
       ll=(w->lmp+l)->pos;  pp=p->pos;
       vecsub(lp,ll,pp);  cosi=dot(lp,p->normal);
       if (coni <= 0.0) continue; /* no con esa lampara! */
       genline(line,pp,ll);
       for (k=0; k<w->numsp; ++k) {
           if (w->sp+k == spc) continue;
           if (intsplin(&t,line,w->sp+k)) goto cont;
       }
       if (firstlite) {
           incvec[0]=incident[1];  incvec[1]=incident[3];
           incvec[2]=incident[5];
           reflect(refvec.p->normal,incvec);
           ref2=dot(refvec,refvec);  firstlite=0;
       }
       r=dot(lp,lp);  t=dot(lp,refvec);
       t*=t/(dot(lp,lp)*ref2);
       if (t > minlint) { /* t es brillante */
           fot (k=0; k<3; ++k) brite[k]=1.0;
           return 1;
       }
   cont:
   }
   return 0;
}
 
mirror(brite,p,w,incident) /* rayo rebotado en un espejo */
double brite[3];  struct patch *p;
struct world *w;  double *incident;
{
    int k; double line[6],incvec[3],refvec[3],t;
    incvec[0]=incident[1];  incvec[1]=incident[3];
    incvec[2]=incident[5];  t=dot(p->normal,incvec);
    if (t >= 0) { /* estamos dentro de una esfera, esta oscuro */
        for (k=0; k<3; ++k) brite[k]=0.0;
        return 0;
    }
    reflect(refvec,p->normal,incvec);  line[0]=p->pos[0];
    line[2]=p->pos[1];  line[4]=p->pos[2];  line[1]=refvec[0];
    line[3]=refvec[1];  line[5]=refvec[2];
    raytrace(brite,line,w);  /* la recursion nos ahorra un dia */
    for (k=0; k<3; ++k) brite[k]=brite[k]*p->color[k];
    return 1;
}
 
pixbrite(brite,p,w,spc)  /* cuan brillante es el punto? */
double brite[3];  struct patch *p;
struct world *w;  struct sphere *spc;
{
    int k,l;  double line[6],t,r,lp[3],*pp,*ll,cosi,diffuse;
    double sqrt();
    static double zenith[3]={0.0,0.0,1.0},fl=1.5,f2=0.4;
    diffuse=(dot(zenith,p->normal)+f1)*f2;
    for (k=0; k<3; ++k) brite[k]=diffuse*w->illum[k]*p->color[k];
    if (p && w) {
        for (l=0; l<w->numlmp; ++l) {
            ll=(w->lmp+1)->pos;  pp=p->pos;  vecsub(lp,ll,pp);
            cosi=dot(lp,p->normal);  if (cosi <= o.0) goto cont;
            genline(line,pp,ll);
            for (k=0; k<w->numsp; ++k) {
                if (w->sp+k == spc) continue; 
                if (inteplin(&t,line,w->sp+k)) goto cont;
            }
            r=sqrt(dot(lp,lp));  cosi=cosi/(r*r*r);
            for (k=0; k<3; ++k)
                brite[k]=brite[k]+cosi*p->color[k]
                         *w->lmp[l].color[k];
            cont;
        }
    }
}
 
setnorm(p,s)    /* direccion radial de la esfera (normal) */
struct patch *p;  struct sphere *s;
{
    double *t,a;  int k;
    vecsub(t=p->normal,p->pos,s->pos);  a=1.0/s->radius;
    for (k=0; k<3; ++k) {*t=(*t)*a; ++t;)
}
 
colorcpy(a,b)  /* a=b para colores *
double *a,*b;
{
 int k;
 for (k=0; k<3; ++k) a[k]=b[k];
}
 
veccopy[a,b)  /* a=b para vectores */
double *a,*b;
[int k;
 for (k=0; k<3; ++k) a[k]=b[k];
}
 
gingham(pos) /* casilla blanca o ngera del ajedrezado? */
double *pos;
{       /* las casillas tienen tres unidades de lado */
    double x,y;  int kx,ky;
    kx=ky=0;  x=pos[0]; y=pos[1];
    if (x < 0) {x=-x; ++kx;}
    if (y < 0.0) {y=-y; ++ky;}
    return ((((int)x)+kx=/3+(((int)y)+ky)/3)%2;
}
 
reflect(y,n,x)   /* ley de la reflexion, n es la normal, */
double *y,*n,*x; /* x es el rayo incidente, y el rayo reflejado */
{
    double u[3],v[3],vv,xn,xv;  int k;
    vecprod(u,x,n);      /* normal al plano formado por n e y */
    if (veczero(u)) {    /* rebota en la misma direccion */
        y[0]=-x[0];  y[1]=-x[1];  y[2]=-x[2];  return 0;
    }
    vecprod(v,u,n);          /* u,v y n son ortogonales */
    vv=dot(v,v);  xy=dot(x,v)/vv;  xn=dot(x,n);
/*  si (k=0; k<3; ++k) y[k]=xv*v[k]/(xn*n[k]); equivocado! */
    for (k=0; k<3; ++k) y[k]=xv*v[k]-(xn*n[k]); /* correcto */
}
 
vecprod(a,b,c)          /* producto vectorial a=b^c */
double *a,*b,*c;
{
    a[0]=b[1]*c[2]-b[2]*c[1];
    a[1]=b[2]*c[0]-b[0]*c[2];
    a[2]=b[0]*c[1]-b[1]*c[0];
}
 
veczero(v)              /* vector nulo? */
double *v;
{
    if (v[0] != 0.0) return 0;  if (v[1] != 0.0) return 0;
    if (v[2] != 0.0) returb 0; return 1; }


El futuro de las simulaciones

El Amiga es una máquina con gran capacidad para la generación de simulaciones de escenas gráficas. Sólo he tanteado algunas de las posibilidades. No sería difícil reemplazar las esferas por otros objetos más complicados. ¿Y qué tal en utilizar una semana de tiempo para que tu Amiga genere los fotogramas de una película de 10 segundos? Cada imagen HAM ocupa 48K de memoria, de modo que necesitas algo de memoria extra si quieres realizar una película medianamente larga. Yo he conseguido "comprimir" las pantallas y conseguir efectos de animación en este tipo de escenas. A 30 imágenes por segundo, el efecto de las sombras, luces y reflejos da una alta impresión de realidad o, tal vez debería decir super-realidad, la surrealista realidad alternativa del universo que existe dentro de tu Amiga.

Eric Graham es ex astrónomo y diseñador de software. Vive en las montañas de Nuevo México, rodeado de ordenadores. Es autor de Sculpt-3D, uno de los programas de diseño 3D y ray-tracing más conocidos para el Amiga. El programa de este artículo es una de las primitivas versiones de Sculpt-3D.


Envía esta página web a un amigo:
Esta opción está desactivada temporalmente, rogamos disculpen las molestias

Volver a la página anterior

Al menú principal