Luces y Lámparas Universidad de Salamanca Departamento de Informática y Automática Informática Gráfica
Mercedes Sánchez Marcos Almudena Sardón García
Contenido 1. Introducción ..................................................................................................... 1 2. Los modelos de iluminación .......................................................................... 3 2.1. Luz ambiente ............................................................................................... 5 2.2. Reflexión difusa ........................................................................................... 5 2.3. Reflexión especular ..................................................................................... 6 2.4. Modelo de Pong .......................................................................................... 6 2.5. Modelo básico de iluminación ..................................................................... 9 2.6. Modelo básico de iluminación con n fuentes múltiples ............................... 9 2.7. Atenuación de la intensidad a lo largo de la distancia a la luz .................. 10 3. Rendering. Determinación de superficies visibles .................................... 11 3.1. Método de la normal ................................................................................. 11 3.2. Método de buffer con profundidad. Buffer Z ............................................. 12 3.3. Algoritmo del pintor ................................................................................... 13 3.4. Método de traza de rayos ......................................................................... 14 4. Métodos de rendering de los polígonos ..................................................... 15 4.1. Sombreado con intensidad constante ....................................................... 15 4.2. Método de sombreado de Gouraud (Gouraud Shading) ........................... 15 4.3. Método de sombreado de Phong (Phong Shading) .................................. 16 5. Rendering con alta calidad ........................................................................... 17 5.1. El método de ray-tracing ........................................................................... 17 5.2. El método de radiosidad ........................................................................... 19 5.3. Método radiance ....................................................................................... 21 6. La iluminación en OpenGl ........................................................................... 22 6.1. Tipos de iluminación en OpenGl ............................................................... 22 6.2. Características de las fuentes de luz ........................................................ 23 6.3. Vectores normales a una superficie .......................................................... 24 6.4. Colocar las luces ....................................................................................... 26 6.5. Atenuación de la distancia ........................................................................ 27 6.6. Área de cobertura ..................................................................................... 28 7. Los materiales en OpengGl .......................................................................... 29 7.1. Las propiedades de los materiales ........................................................... 29 7.2. La luz en los materiales ............................................................................ 29 7.3. Especificación de los materiales en OpenGL . ......................................... 30 8. Render óptimo en OpenGl ............................................................................ 32 8.1. Z-Buffer y superficies ocultas . .................................................................. 32 8.2. El canal alfa . .................................................. .......................................... 34 8.3. Efecto de niebla . .................................................. ................................... 35 9. Efectos de iluminación ................................................................................. 37 9.1. Componente especular . .................................................. ........................ 37 9.2. Luz especular . .................................................. ....................................... 38 9.3. Reflectancia especular . .................................................. ......................... 39 9.4. Técnicas de iluminación . .......................................................................... 40 9.5. Promedio normal .. 40
9.6. CUBE ENVIRONMENT MAPIN . .............................................................. 41 9.7. BI-DIRECTIONAL REFLECTANCE DISTRIBUTION FUNCTION ………. 42 10. Focos de luz . ................................................. ............................................... 47 10.1. Iluminación global . .......................................................................... 48 11. Sombras . ................................................. ..................................................... 51 11.1. Sombras proyectadas . .................................................................... 51 11.2. STENCIL BUFFER O BUFFER PLANTILLA . ................................. 51 12. Apéndice A . ................................................. ................................................. 54 12.1. Efectos de niebla . .................................................. ........................ 54 13. Conclusiones . .................................................. .............................................. 57
Luces y lámparas
1. INTRODUCCIÓN En este capitulo se verá cómo actúa la luz al chocar con una superficie y cómo OpenGL es capaz de representar la luz y todos sus fenómenos. Los primeros apartados son mas teóricos y en ellos se verá cual es la composición de la luz, cómo actúa ésta al chocar con otros cuerpos, etc. Todo desde un punto físico pero con vistas a lo que luego se usará en OpenGL. A continuación, se irá introduciendo cómo se usa la luz para construir escenas. Se explicarán los modelos de renderizado más usados, que posteriormente se usarán al hablar de OpenGL. Más adelante, se empezará a ver cómo OpenGL hace uso de la iluminación. Se hablará del modo de activar y desactivar la iluminación, de cómo se puede crear una fuente de luz y de la manera en que ésta se puede colocar. También se hablará de las propiedades de la luz y de una parte muy importante, que es cómo especificar las propiedades de los materiales de la escena donde va a incidir la luz. Obviamente, la luz no actúa de igual manera al chocar con un objeto que al chocar con otro. Por ejemplo, la luz no se refleja del mismo modo en una barra de oro que en una manzana. Por ultimo, se tratarán todos los efectos de iluminación que se pueden conseguir con OpenGL, que son bastantes. Se empezará haciendo una pequeña introducción a los modelos de iluminación. Un modelo de reflexión, representa la interacción o forma de actuar de la luz al llegar a una superficie. Un modelo de iluminación, consiste en ver cómo se comporta la luz cuando sale de una fuente de luz. Los modelos de iluminación y reflexión suelen ser muy sencillos. En principio, se van a considerar modelos locales, es decir, modelos que tienen una fuente de luz y una superficie. No se tendrá en cuenta la posición.
Figura 8.1 Cuando la luz llega un punto cumple una ecuación: Luz incidente = luz reflejada + luz dispersa + luz absorbida + luz transmitida Los algoritmos van a intentar representar todos estos parámetros de la luz. Los factores de los que dependen los elementos de la ecuación son los siguientes: • Longitud de la onda de la luz incidente. • Rugosidad de la superficie. • Tipo de material, para ver cuanta luz absorbe. • Angulo de incidencia de la luz.
1
Luces y lámparas
Es obvio que todos estos elementos afectan al modo en que la luz actúa. Está claro que cuanto más lejos esté la fuente de luz de un objeto menos luz le llega a ese objeto. Por ello, es importante conocer la distancia a la que se encuentra la luz respecto de un objeto. También es importante saber si una superficie es rugosa o no, ya que, en caso de serlo la luz se dispersará en más direcciones al incidir en ella. Otro factor importante es el tipo de material de un objeto. Al indicarlo es necesario especificar el color del material, el modo en que la luz va a actuar al chocar con él, si el material es brillante o no, etc. También hay que indicar el ángulo de incidencia de la luz. Cuanto mayor sea el ángulo de incidencia menos luz se reflejara en el objeto.
2
Luces y lámparas
2. LOS MODELOS DE ILUMINACIÓN El fenómeno de iluminación y reflexión indica cómo la luz interactúa con un sólido. Se puede clasificar la luz en: • Luz incidente. • Luz de reflexión: Luz difusa: o Luz difusa global. o Luz difusa local. Luz especular. • Luz de refracción. • Luz de transmisión.
Figura 8.2 Los diferentes tipos de iluminación producen diferentes efectos. Estos direrentes efectos de brillo y color se llaman reflectancia. El brillo de una superficie es regulado por su orientación respecto al foco de luz. El ángulo entre los rayos de luz y la perpendicular de la superficie determina en que medida la superficie aparecerá brillante a los ojos del espectador. Esto es llamado a veces Ley del coseno de Lambert. La perpendicular de una superficie es la línea que sale de la superficie plana con un ángulo de 90 grados respecto a dicha superficie. A veces recibe el nombre de normal a la superficie.
Figura 8.3: El brillo de una superficie se determina de acuerdo con su orientación con respecto de la fuente de luz.
3
Luces y lámparas Una superficie plana aparecerá más brillante cuando su perpendicular apunte directamente hacia el foco de luz. Cuanto mayor sea el ángulo entre la perpendicular de la superficie y el rayo de luz, más apagada aparecerá. La superficie no reflejará la luz cuando esté a más de 90 grados de los rayos de luz, ni tampoco cuando esté mirando hacia el lado opuesto del foco luminoso. En tal caso, la superficie estaría solo iluminada por la luz ambiental, no por la fuente de luz específica que se esté analizando.
Figura 8.4: Cuanto más grande sea el ángulo entre la perpendicular a la superficie y el rayo de luz, más oscura aparecerá la superficie.
En el modelo básico de iluminación se consideran sólo las reflexiones. La intensidad de iluminación de cada punto de las superficies será por tanto: Intensidad = luz_ambiente + luz_difusa + luz_especular =
I ambiente + I difusa + I especular
4
Luces y lámparas 2.1.
LUZ AMBIENTE
La luz distribuida es una fuente de luz amplia que está relativamente cerca del objeto. Una bola de ping-pong que descansa sobre una mesa cerca de una ventana orientada al norte es un buen ejemplo de luz distribuida. Si la bola estuviera iluminada por un tubo de luz fluorescente, estaría iluminada por luz distribuida. La luz distribuida, también llamada banco de luz, produce unas sombras determinadas. La luz que vemos en un día encapotado pero brillante o en un día nebuloso es luz distribuida. Se dice que un modelo sólido bañado por luz distribuida muestra reflectancia ambiental o reflexión ambiental. La luz ambiente es el resultado de la reflexión múltiple de todos los objetos en la escena. Viene representada por la siguiente ecuación:
I ambiente = k a I a donde k a es la constante de ambiente del objeto, dependiente del material y I define por el usuario para dar el efecto de luz de fondo de la escena. 2.2.
a
se
REFLEXION DIFUSA
La luz ambiental es la luz indirecta, por ejemplo la luz que se ve en un día muy encapotado o nuboso es la luz ambiental. La luz que hay en el interior de una habitación con una ventana orientada al norte es, normalmente, luz ambiental. En teoría, la luz ambiental no produce sombras en el mundo real, sin embargo, es a menudo más fuerte cuando viene de arriba, de manera que algunas veces se producen sombras suaves y delicadas. En el modelo no se ven matices de luz o puntos brillantes porque no hay focos de luz intensos. Un modelo sólido bañado en luz ambiental se dice que muestra reflectancia difusa o reflexión difusa. La luz difusa es la que la superficie de un objeto refleja y dispersa en todas las direcciones hacia fuera de la cara del objeto debido a la iluminación directa. Es la responsable del color del objeto. Es una característica propia de los objetos mates tales como paredes, telas, papel, etc.
Figura 8.5
5
Luces y lámparas
Ley de Lambert: “la componente difusa de la luz reflejada por una superficie es proporcional al coseno del ángulo de incidencia”.
I difusa = k d I l cos θ = k d I l (N·L)
donde: k d es la constante difusa del objeto, dependiente del material del mismo. I l es la intensidad de la fuente de luz puntual (point light source). L es el vector unitario de la luz incidencia. N es el vector unitario de la normal a la superficie en este punto. θ es el ángulo entre N y L. 2.3.
REFLEXIÓN ESPECULAR
Un punto de luz es una sola fuente de luz, como, por ejemplo, una bombilla, una linterna, o incluso el sol. Un punto de luz produce un fuerte matiz de luz y marcadas sombras. El matiz de luz es llamado reflexión especular. Mientras que la reflectancia difusa y la reflectancia ambiental normalmente son varias sombras del color del objeto, las reflexiones especulares (brillo) son normalmente del mismo color que la fuente de luz. La luz especular es la luz con alta intensidad que una superficie brillante refleja a lo largo de la dirección del espejo. Esta luz es la que produce el efecto espejo. Además tiene el color de la fuente de luz. Cabe destacar que la reflexión especular depende de la posición del observador. En las superficies normales el brillo decae cuando el observador se aleja de la dirección de reflexión perfecta (R en la figura 8.6). 2.4.
MODELO DE PHONG
Según este modelo, la luz en un punto tiene tres componentes y es igual a la suma de las tres: Luz difusa + luz especular + luz ambiental El modelo de Phong consiste en modelar el efecto de la luz especular. Propone potencias del coseno de (R·V) para determinar la intensidad del brillo visto.
Figura 8.6 6
Luces y lámparas N: vector unitario de la normal de la superficie. L: vector unitario de la luz incidente. R: vector unitario de la dirección de la reflexión especular. V: vector unitario de la dirección del observador. θ: ángulo de incidencia que es igual al ángulo de reflexión especular. α: ángulo de observación entre R y V. El efecto de espejo se puede observar alrededor de una zona de la dirección R si la superficie no es perfectamente especular. El modelo de Phong modela la luz especular como:
I esp = k s I l cos n φ = k s I l (V·R)n
donde, n es el parámetro de reflexión especular (desde 1 hasta mas que 100). Si la superficie es más brillante, n es mas grande. φ es el ángulo entre R y V, es decir la desviación de la dirección de reflexión perfecta. k s es la constante de reflexión especular del objeto, dependiente del tipo del objeto.
Figura 8.7
Figura 8.8 Para calcular R se puede tener en cuenta la siguiente relación y realizar los cálculos oportunos. 7
Luces y lámparas L+R =N |L+R|
Figura 8.9 En el caso de superficie curva, N se cambia según el lugar del punto. Se complica el calculo de R. Se puede simplificar el calculo de V · R de la siguiente forma: Se utiliza H · N para sustituir R · V , y el cosα sustituye al cos φ H es el vector equidistante entre L y V. Dicho de otra forma, es la orientación de la zona de máximo brillo (highlight). L +V H= | L +V |
Figura 8.10 En el caso de que el observador y la luz estén lejos, L y V son constantes. Esto significa que:
I especular = k s I l (V · R)n = k x I l (N · H)n La componente difusa toma el color de la superficie. Y el rayo reflejado (componente especular), toma el color de la fuente de luz. Se obtiene de esta forma la siguiente ecuación:
8
Luces y lámparas
I d = I i k d cos θ
donde,
I d es la luz difusa. I i es la luz incidente. k d es la constante de difusión. Se obtiene esta otra ecuación:
I d = I i k d (N · L) Si únicamente se consideran la componente difusa y la componente especular, el objeto queda como si estuviera iluminado por un flash. Es decir, tenemos objetos muy brillantes y otros objetos que apenas están iluminados. Para evitar esto, se usa la tercer componente, que es la componente ambiental:
Ig=Iaka En la componente ambiental se engloban las otras componentes. Resumiendo el modelo de Phong, se puede decir que las fuente de luz son puntuales. Las componentes de difusión y especular se consideran como componentes locales. Todos los factores que aparecen en la fórmula son empíricos. La componente difusa va a ser del color de la superficie. La componente reflejada es del color de la fuente de luz. 2.5.
MODELO BÁSICO DE ILUMINACIÓN
Considerando el modelo de Phong se puede definir el modelo básico de iluminación de la siguiente forma:
I = I ambiente + I difusa + I especular = k a I a + k d I l (N · L) + k s I l (N · H)n 2.6.
MODELO BÁSICO DE ILUMINACIÓN CON n FUENTES MÚLTIPLES
De la misma forma, se puede definir el modelo básico de iluminación si se dispone de n fuentes de luz: n
I = Iambiente + Idifusa + Iespecular = ka Ia +
∑ II { kd(N · Ll) + ks (N · Hl) l=1
9
n
}
Luces y lámparas
2.7.
ATENUACIÓN DE LA INTENSIDAD A LO LARGO DE LA DISTANCIA A LA LUZ
A veces se usa un término de atenuación. Para tener en cuenta que los objetos más cercanos son más brillantes que los más lejanos se usa esta atenuación. Según la ley de física, el factor de la atenuación es 1/d2, donde d es la distancia recorrida por la luz. Este factor tiene el problema de que se atenúa demasiado rápido para el dibujo. Por ello, se utiliza una función de atenuación empírica: El modelo básico con atenuación de distancia y fuentes múltiples: n
I = ka Ia +
∑ f(dl)II { kd(N · Ll) + ks (N · Hl)
n
}
l=1
donde,
⎞ ⎛ 1 ⎟ f (d ) = min⎜⎜ 2 ⎟ a a d a d + + 1 2 ⎠ ⎝ 0 a0 puede impedir que f(d) sea muy alto cuando d es muy bajo. f(d) se limita a no ser mas grande de 1.
10
Luces y lámparas
3. RENDERING. DETERMINACIÓN DE SUPERFICIES VISIBLES “Rendering” es el proceso de usar el modelo de un objeto o escena para crear su imagen, o sea un “bitmap” de “pixeles”. Para ello, se toma un modelo tridimensional y se obtiene una imagen bidimensional. Esto involucra proyecciones (ortográficas o de perspectiva). Al principio sólo se dibujaban las aristas de los objetos, obteniendo un “render” alambrado. Un problema de esto era que las imágenes así generadas eran confusas. Una parte importante de la generación de gráficos realistas es la identificación de las partes de una escena que son visibles desde un punto de vista determinado. Hay muchos planteamientos que se pueden manejar para solucionar este problema y se han desarrollado algunos algoritmos con el fin de identificar con eficiencia objetos visibles para diferentes tipos de aplicaciones. Algunos métodos requieren mucha memoria, otros implican mucho tiempo de procesamiento y otros sólo se aplican en objetos específicos. Seleccionar un método para una aplicación en particular puede depender de factores como la complejidad de la escena, el tipo de objetos a desplegar, el equipo del que se dispone y la necesidad de generar despliegues gráficos animados o estáticos. Los diversos métodos de algoritmos se conocen como métodos de detección de superficie visible o como métodos de eliminación de superficies ocultas (a pesar de que puede ser diferente identificar superficies visibles y eliminar superficies ocultas). Los algoritmos de detección de superficies visibles se pueden clasificar dependiendo de si manejan definiciones de objetos de manera directa o con sus imágenes proyectadas. Estos planteamientos se denominan métodos de objeto-espacio y métodos de imagen-espacio, respectivamente. • •
Imagen-espacio: Se basan en la imagen que tenemos del objeto. Van a ir viendo en la pantalla qué objeto es visible y cuál no. La decisión se toma a nivel de pixel. Se toma un pixel y se dice qué color se le pone. Son muy engorrosos. Objeto-espacio: La decisión se toma con la propia definición de los objetos. Hay que tener en cuenta qué objetos están delante de otro.
A pesar de que hay importantes diferencias en el planteamiento básico que se utiliza en los diversos algoritmos de detección de superficie visible, la mayoría de éstos usan métodos de clasificación y coherencia para mejorar el resultado. La clasificación sirve para hacer comparaciones de la profundidad al ordenar las superficies visibles en una escena de acuerdo con su distancia desde el plano de visión. 3.1.
METODO DE LA NORMAL
Este método se aplica en figuras planas. Permite ocultar la mitad de las superficies que hay en escena. Para un poliedro converso, este método permite definir todas las superficies ocultas. Se basa en calcular la normal a todas las superficies. Las normales cuya proyección en la dirección de visión sean positivas son superficies ocultas. Y las que son negativas son visibles. 11
Luces y lámparas
Figura 8.11: Se hace el producto escalar del vector de visión por el vector normal de cada cara, para ver si es positivo o negativo
Si un objeto es poliédrico, las caras de dicho objeto, son totalmente visibles o totalmente ocultas. Con este algoritmo se determinan las caras visibles de dicho objeto. Si hay varios objetos de este tipo, entonces se realiza el algoritmo para todos los objetos. Lo que hace este algoritmo es eliminar la cara posterior de los objetos. 3.2.
METODO DE BUFFER CON PROFUNDIDAD. BUFFER Z
El método Z-Buffer de eliminación de superficies ocultas es un método de espacioimagen basado en el buffer de pantalla del adaptador de gráficos. Se mantiene una base de datos que corresponde a cada pixel de la pantalla de visión. Por ejemplo, si se esta usando el modo gráfico de 640*480 de 16 colores, se necesitará una base de datos de 614400 bytes, lo bastante para mantener 307200 enteros. La base de datos contiene información sobre la profundidad de la coordenada visual z para cada parte de un objeto que ocupe una posición determinada de pixel. Cuando se están preparando las superficies o los objetos para ser visualizados, el programa primero comprueba el buffer z. Si al pixel que se está considerando le ha sido asignado un valor z superior a la superficie que se está considerando, entonces no se realiza ningún cambio sobre el pixel. Si el valor de z de la superficie es mayor que el valor en el buffer z para ese pixel en concreto, entonces la superficie está más cerca del punto de vista. En este caso, habrá que actualizar el pixel y el buffer z, por supuesto. Para cada pixel (x,y) de la imagen, el punto con menor coordenada z es visible.
12
Luces y lámparas
Figura 8.12 Para implementar este método, la mayoría de los sistemas usa dos buffer. Para cada pixel se almacena en el primer buffer, llamado buffer de profundidad, la coordenada z. En otro buffer, llamado de redundancia, se almacena bien el color o bien la intensidad. El inconveniente de este método es que, si se usa una pantalla de 1024*1024, se utiliza mucha memoria. Partiendo de este método se ha llegado a definir modelos, que lo que hacen es un muestreo a escala subpixel. Se divide el pixel en un rectángulo de 4*8. Se analiza el color de los 32 subpixel y luego se considera el color medio que define al pixel. De esta forma si se dispone de dos figuras superpuestas, se obtendrá una mezcla. Se muestrea con un 1/32 de pixel en lugar de un pixel. La media de los 32 colores será el color predominante. El método del Z-Buffer es infalible para todas las escenas 3D, no importa cuál sea su complejidad. Sin embargo, es de memoria intensiva y de informatización también intensiva. Cuando se está dibujando un plano, se debe utilizar trigonometría y geometría muy complicadas para determinar las coordenadas x, y, z de la parte de la superficie del plano que será mapeada en un pixel determinado. Entonces la coordenada z debe compararse con la coordenada z para el pixel de la base de datos del buffer z. La generación de una escena compleja en el ordenador personal puede llevar horas, aunque los resultados son impecables. 3.3.
ALGORITMO DEL PINTOR
Se basa en pintar primero el fondo. Sobre este fondo, se dibuja la siguiente figura. Si sobre esa figura va otra, se dibujaría encima. Es decir, se ordenan todas las figuras según su distancia al plano de visión. Luego se van pintando todas, empezando por las alejadas.
13
Luces y lámparas
Figura 8.13 Este método tiene un inconveniente cuando las superficies se oscurecen alternativamente.
Figura 8.14 La figura 1 esta detrás de la figura 3. La figura 3 detrás de la figura 2. Y la figura 2 detrás de la figura 1. Cuando ocurre esto, este método tiene problemas. Lo que se hace es dividir la figura en trozos. Si se divide la figura por la mitad, ya quedaría bien definida. 3.4.
METODO DE TRAZA DE RAYOS
En este método lo que se hace es aplicar la definición del algoritmo de superficie oculta (método de imagen-espacio). Para cada pixel se traza un rayo. Si no intercepta ningún objeto se le da el color de fondo. Si intercepta un objeto, se le da el color del primer objeto interceptado. Este método es muy lento. Pero tiene la ventaja de dar imágenes muy realistas. Por ejemplo, usando varias fuentes de luz, se obtienen unos resultados muy buenos, casi fotográficos.
14
Luces y lámparas
4. MÉTODOS DE RENDERING DE LOS POLÍGONOS 4.1.
SOMBREADO CON INTENSIDAD CONSTANTE
Este método consiste en calcular una única intensidad para cada polígono. Una vez calculada la intensidad de cada polígono, se visualizan todos los puntos del mismo polígono con una única intensidad. Este método es muy rápido y preciso si las luces y el observador están lejos. Pero presenta el inconveniente de que hay discontinuidad de intensidad entre los polígonos. Este método es el más ineficiente y menos usado de los tres. 4.2.
METODO DE SOMBREADO DE GOURAUD (GOURAUD SHADING)
El método consiste en calcular la intensidad de los pixeles a lo largo de la línea de escaneo por interpolación. Este método elimina la discontinuidad de la intensidad entre los polígonos que presentaba el método anterior. Para cada polígono hay que hacer los siguientes cálculos: • Determinar el vector unitario de la normal de los vértices. Este vector es el promedio de las normales de las caras que comparten el vértice.
n
Nv =
∑N
k
∑N
k
k =l n k =l
Figura 8.15 • •
Calcular la intensidad de cada vértice aplicando el modelo de iluminación. Interpolar linealmente las intensidades sobre las aristas del polígono.
I4 =
y + y4 y4 + y2 I2 I1 + 1 y1 + y 2 y1 + y 2 Figura 8.16
•
Interpolar las intensidades de los pixeles entre las aristas.
15
Luces y lámparas Ip = 4.3.
x5 + x p x5 + x 4
I4 +
x p + x4 x5 + x 4
I5
METODO DE SOMBREADO DE PHONG (PHONG SHADING)
Este método, en vez de interpolar las intensidades, lo que hace es interpolar las normales para cada punto de la superficie. El método consistirá en realizar los siguientes pasos: • Paso 1. Determinar el vector unitario de la normal de los vértices. Este vector es el promedio de las normales de las caras que comparten el vértice. • Paso 2. Interpolar linealmente las normales sobre las aristas del polígono N1
N=
y + y2 y +y N1 + 1 N2 y1 + y 2 y1 + y 2
Figura 8.17 • •
Paso 3. Interpolar linealmente las normales de los pixeles entre las aristas. Paso 4. Aplicar el modelo de iluminación a lo largo de cada línea de escaneo para calcular las intensidades de cada pixel.
Con este método es con el que se consiguen resultados mas realistas, pero tiene el inconveniente de que necesita realizar entre 6 y 7 veces mas cálculos que para el método de Gouraud.
16
Luces y lámparas
5. RENDERING CON ALTA CALIDAD 5.1.
EL MÉTODO DE RAY-TRACING
El método de ray-tracing (rastreo de rayos) consiste en asignar la intensidad a cada pixel según el efecto de todas las iluminaciones acumuladas en la escena hacia este pixel. La idea del método es emitir un rayo de luz desde la pantalla a lo largo de la dirección de observación hacia la escena 3D para buscar las contribuciones de iluminación acumulada.
Figura 8.18 La ventaja del método consiste en combinar todos los cálculos siguientes en un solo modelo: • Eliminación de superficies invisibles • Sombreado debido a la iluminación directa y local. • Sombreado debido a la iluminación global. • Cálculo de sombras.
Figura 8.19 17
Luces y lámparas
Hay que tener en cuenta unas consideraciones básicas antes de entrar con más detalle en el método: • El color y la intensidad de cualquier punto visible en la pantalla debe venir de una superficie en la escena 3D. • La dirección de rastreo: se puede encontrar en las contribuciones en el punto por rastrear un rayo a través del sentido inverso desde el pixel a la escena. • Si el rayo intersecciona con un objeto, el rayo es el resultado de la luces que el punto de intersección recibe. La contribución de la luz en este punto se puede rastrear más lejos recurrentemente. • El rastreo se para cuando el rayo intersecciona con el fondo o a una profundidad de rastreo predeterminada. Cálculo recurrente de ray-tracing
Figura 8.20 El color e intensidad del pixel son la acumulación del árbol de rastreo desde las hojas. Hay tres contribuciones de luces de un punto de una superficie: • Intensidad local, debida a la iluminación de la luz directa y la luz ambiente. • Luz de la dirección de la reflexión. • Luz de la dirección de transmisión de refracción Hay una atenuación de la intensidad según la distancia del nodo hasta la raíz del árbol. Para antialias, hay que crear más rayos para cada pixel y utilizar la técnica jittering.
Figura 8.21 18
Luces y lámparas 5.2.
EL MÉTODO DE RADIOSIDAD
Se considera que todos los objetos están dentro de un sistema cerrado donde se conserva la energía. Cada superficie será un reflector o un emisor o ambas cosas a la vez. La energía emitida desde un punto de superficie es la suma de la contribución de todas las direcciones sobre un hemisferio centrado en el punto.
Figura 8.22
Figura 8.23
19
Luces y lámparas
Figura 8.24 Rendering por el método de radiosidad • Paso 1. Calcular los factores de forma F jk . Este paso se puede simplificar utilizando el hemicubo para sustituir el hemisferio. • Paso 2. Calcular los Bk resolviendo la matriz de ecuaciones (usando por ejemplo el método de Gauss-Seidel). Los pasos 1 y 2 son independientes del punto de vista. Sólo hay que realizarlos una vez para cada escena. •
•
Paso 3. Para un punto de vista dado, realizar el rendering por un método de sombreado, por ejemplo, el método de Gouraud. Utilizar los Bk como la intensidad de luz incidente a todas las superficies que ven la superficie k para el cálculo de interpolación. Paso 4. Repetir los pasos 2 y 3 para cada banda de color.
Cada superficie se puede subdividir en muchos polígonos (pataches). La imagen aparece más real si los polígonos son mas pequeños. El método de radiosidad produce imágenes muy realistas con alta calidad, pero necesita una gran cantidad de cálculo, sobre todo, el cálculo de los factores de forma. Una variante a este método sería el método de refinamiento progresivo. Este método consiste en calcular los factores de forma y producir imágenes de la misma escena cada vez con más luces durante el procesamiento. La forma de ir añadiendo luces a la escena seria la siguiente: • Aplicar primero la luz (el emisor de energía) más fuerte. • Visualizar inmediatamente el efecto de esta luz. • Añadir las luces a la escena progresivamente según sus intensidades. • Visualizar la escena cada etapa hasta que el usuario este satisfecho.
20
Luces y lámparas
5.3.
MÉTODO RADIANCE
Radiance es un algoritmo de renderizado basado en el comportamiento físico de la luz. Radiance es el nombre de un sistema de renderizado desarrollado por Gregory J.Ward durante más de nueve años en el Laboratorio Lawrence Berkeley (LBL) en California y el EPFL ( Ecole Polytechnique Federale de Lausanne ) en Suiza. Comenzó como un estudio de algoritmos de trazado de rayos (ray- tracing) y después de demostrar su potencial para el ahorro de energía a partir de un mejor diseño de iluminación, adquirió fondos del Departamento de Energía de Estados Unidos y más tarde del gobierno suizo. Entre los objetivos principales de diseño de Radiance están asegurar el cálculo preciso de la luminancia y modelar tanto interiores como exteriores en escenas con una geometría complicada. Radiance, al igual que el método de Radiosidad, son técnicas de iluminación global utilizadas en el campo de renderización de entornos 3D. Estas técnicas de iluminación global consideran la influencia de todos los objetos en la escena. Es decir, la iluminación que recibe una superficie se debe tanto a las fuentes de luz que existan en la escena como a la que reflejan otras superficies. En la siguiente figura se muestra un ejemplo de una escena renderizada con Radiance.
Figura 8.25 El algoritmo de Radiance ofrece grandes ventajas en el diseño real de iluminación tanto en espacios interiores como exteriores, no obstante el programa presenta una serie de desventajas entre las cuales se pueden señalar las siguientes:
21
Luces y lámparas •
•
• •
Aunque hay software conversores de formatos que permiten traducir desde y hacia el formato de descripción de escenas de Radiance, se utilizan como programas independientes del propio renderizador, lo cual hace más tediosa su utilización. Carece de una interfaz adecuada para la introducción de los objetos a modelar, para lo cual es necesario editar un fichero ASCII con la descripción adecuada de los mismos. Esto resulta una tarea bastante tediosa y difícil al no poder ver de forma interactiva las posiciones relativas entre los objetos. La descripción y mapeo de las texturas en Radiance es bastante limitada, incluyendo un solo tipo de formato de imagen, propio de este software, que es el formato PIC. Las posibilidades que ofrece de animación de una escena son bastante pobres, incluyendo sólo la posibilidad de animar la cámara para realizar recorridos virtuales dentro de la escena, dejando de lado la modificación de las características o posición de los objetos a lo largo del tiempo.
22
Luces y lámparas
6. LA ILUMINACION EN OPENGL 6.1.
TIPOS DE ILUMINACIÓN EN OPENGL Vamos a ver que tipos de iluminación soporta OpenGL. Básicamente son tres tipos: • Iluminación plana o FLAT. Es la más simple e ineficiente. En ella todo el polígono presenta el mismo color pues OpenGL evalúa sólo un color para todos sus puntos. Puede activarse mediante: glShadeModel(GL_FLAT ); No es muy recomendable para aplicaciones dónde el realismo sea importante. Por otra parte es muy eficiente dado que los cálculos son mínimos. •
Iluminación suave o SMOOTH / GOURAUD. Se activará así: glShadeModel(GL_SMOOTH ); En este caso OpenGL si efectúa cálculos de color para cada uno de los puntos del polígono. Se asocian las normales a los vértices, OpenGL calcula los colores que éstos deben tener e implementa una interpolación de colores para el resto de puntos. De esta forma ya empezamos a presenciar escenas granuladas, con degradados en la geometría. La calidad ya empieza a ser notable.
•
6.2.
Iluminación PHONG. Es la mejor de todas y por supuesto la que requiere de más cálculos. En el modelo de PHONG se utiliza una interpolación bilineal para calcular la normal de cada punto del polígono. A diferencia del caso anterior, no se evalúan las normales sólo en los vértices y luego se interpolan colores sino que a cada punto del polígono se le asocia una normal distinta (se interpolan las normales) con lo cuál su color asociado será muy cercano a la realidad. OpenGL no implementa PHONG directamente. Así pues, deberíamos de ser nosotros los que interpoláramos normales y las asociáramos a todos los puntos.
CARACTERISTICAS DE LAS FUENTES DE LUZ.
OpenGL soporta en principio hasta 8 luces simultáneas en un escenario. Esto también depende de la máquina que poseamos y de la RAM que usemos Las luces cuentan con nombre propio del estilo GL_LIGHT0, GL_LIGHT1, GL_LIGHT2, etc. Para activar / desactivar una de ellas se hace lo siguiente: glEnable(GL_LIGHT3); glDisable(GL_LIGHT3); También podemos activar y desactivar todo el cálculo de iluminación con: glEnable(GL_LIGHTING); 23
Luces y lámparas glDisable(GL_LIGHTING); Para definir las fuentes de luz tenemos que decirle a OpenGL cuáles son las propiedades de cada una de nuestras luces. En la figura 7.29 podemos ver algunos ejemplos de objetos iluminados usando OpenGL:
Figura 8.26 Para ello utilizaremos la función: GLvoid glLightfv(GLenum light, GLenum pname, const GLfloat *params); El valor de light será siempre la luz a la que nos estemos refiriendo (GL_LIGHT0, GL_LIGHT1,GL_LIGHT2, etc.). En cuanto a *params, le pasamos un array de valores RGBA reales que definen la característica en concreto. Estos valores RGBA definen el porcentaje de intensidad de cada color que tiene la luz. Si los tres valores RGB valen 1.0 la luz es sumamente brillante; si valen 0.5 la luz es aún brillante pero empieza a parecer oscura, gris. 6.3.
VECTORES NORMALES A UNA SUPERFICIE
Para iluminar una superficie (plano) necesitamos información sobre su vector normal asociado. La normal de un plano es un vector perpendicular a este. Necesitamos saber como calcular ese vector y como especificárselo a OpenGL. De hecho en el caso de OpenGL, es necesaria la definición de un vector normal para cada uno de los vértices de nuestra geometría. Por ejemplo, en el caso de la figura 7.30:
24
Luces y lámparas
Figura 8.27: Para obtener la normal de la superficie ABCD buscamos dos vectores pertenecientes a esta y realizamos su producto vectorial. Supongamos que tenemos un objeto 3D como el de la figura 7.30. Vamos a situarnos en su cara superior, la formada por los vértices A, B, C y D. Queremos encontrar la normal de esta cara, que por lo tanto es la asociada a cada uno de los vértices que la forman. Sólo tenemos que calcular dos vectores pertenecientes a la cara y hacer su producto vectorial (el resultado será un vector perpendicular a ambos y por lo tanto una normal del plano). Lo podéis ver en la figura. A partir de tres vértices (A, B, C) creo dos vectores que tras su producto vectorial me generan el vector normal. Este vector ya puede asociarse a la correspondiente cara en 1. Una vez calculada la normal tenemos que normalizar, es decir, dividir ese vector por su propio módulo para que sea unitario. De esta forma tenemos un vector normal de módulo igual a la unidad que es lo que OpenGL necesita. Hay que tener cuidado con el orden en que se multiplica porque de esto depende que el vector normal apunte hacia fuera o hacia dentro de la cara. En nuestro caso, queremos que nuestras normales apunten hacia fuera de la cara visible (FRONT). OpenGL utilizará la normal asociada a cada vértice para evaluar la luz que incide sobre éste. Si un vértice pertenece a más de una cara (en la figura anterior, el vértice A, por ejemplo, pertenece a tres áreas distintas), en este caso hay que promediar para obtener unos cálculos correctos por parte de OpenGL. Tendremos que calcular la normal de cada una de las caras a las que pertenece el vértice, promediarlas y después normalizar el resultado con lo cuál ese vértice presentará un vector normal al cuál han contribuido todas las caras a las que pertenece. Para definir normales con OpenGL: glBegin(GL_POLYGON ); glNormal3f(CoordX, CoordY, CoordZ); glVertex3f ( ... ) ; glVertex3f ( ... ) ; glVertex3f ( ... ) ; glVertex3f ( ... ) ; ... glEnd( ) ;
25
Luces y lámparas En este caso estamos definiendo un polígono de N vértices que comparten la normal, es decir, todos tienen la misma. Si no fuera el caso lo podríamos hacer así: glBegin ( GL_POLYGON ) ; glNormal3f ( CoordX, CoordY, CoordZ ) ; glVertex3f ( ... ) ; glNormal3f ( CoordX, CoordY, CoordZ ) ; glVertex3f ( ... ) ; glNormal3f ( CoordX, CoordY, CoordZ ) ; glVertex3f ( ... ) ; glNormal3f ( CoordX, CoordY, CoordZ ) ; glVertex3f ( ... ) ; ... glEnd( ) ; y entonces cada vértice tendría su propia normal asociada. A la función le pasamos las tres coordenadas XYZ del vector en cuestión. Como siempre hay variaciones sobre la función dado que es del tipo glNormal*. En el caso de que no normalicéis los vectores normales, podéis utilizar: glEnable(GL_NORMALIZE); glDisable(GL_NORMALIZE); Para que OpenGL lo haga automáticamente por nosotros. No es para nada recomendable pues se carga al sistema con cálculos innecesarios que ralentizarán aún más lo que ya de por sí es computacionalmente exigente, es decir, el cálculo automático de la iluminación. 6.4.
COLOCAR LAS LUCES
Tenemos que especificar dónde queremos colocar cada una de las fuentes de luz. Para ello utilizamos la siguiente función: GLvoid glLightfv (GLenum light, GL_POSITION, const GLfloat *params); Notar el uso de la constante GL_POSITION. En este caso *params se corresponde con el valor de la coordenada homogénea (X, Y, Z, W) dónde colocar la luz. Si w = 0.0 se considerará que la luz se encuentra infinitamente lejos de nosotros. En ese caso su dirección se deduce del vector que pasa por el origen y por el punto (X, Y, Z). Si w = 1.0 se considera su posición con toda normalidad. Por defecto la luz se encuentra en (0.0, 0.0, 1.0, 0.0) iluminando en la dirección negativa de las Z's. Los rayos de la luz se asumen paralelos. Podemos mover una luz a gusto por una escena. Incluso podemos movernos con ella como si fuera una linterna. Para ello tan sólo tenemos que considerarla como un objeto 3D más que se ve afectado por cambios en la matriz de transformación "MODEL-VIEW" de OpenGL. Podemos rotarla, trasladarla, escalar su posición como si de un polígono se tratara.
26
Luces y lámparas
6.5.
ATENUACION CON LA DISTANCIA
La atenuación con la distancia hace referencia a la atenuación que sufre la luz a medida que se desplaza. Está claro que a más lejos esté un objeto de una fuente luminosa, menos iluminado resultará. Para modelar esto contamos con tres parámetros a definir: • GL_CONSTANT_ATTENUATION (por defecto igual a 1.0). En la formula es "a". • GL_LINEAR_ATTENUATION (por defecto igual a 0.0). En la formula es "b". • GL_QUADRATIC_ATTENUATION (por defecto igual a 0.0). En la formula es "c". La función que atenúa la iluminación con la distancia es:
Es decir que se reduce la intensidad de la luz que llega a un determinado lugar con esta fracción, evidentemente inversamente proporcional a la distancia. Los valores los pasamos a OpenGL usando la función de siempre. Por ejemplo: GLvoid glLightfv ( GL_LIGHT5, GL_CONSTANT_ATTENUATION, 0.8 ) ; GLvoid glLightfv ( GL_LIGHT5, GL_LINEAR_ATTENUATION, 0.5 ) ; GLvoid glLightfv ( GL_LIGHT5, GL_QUADRATIC_ATTENUATION, 0.1 ) ; En este caso hemos dado más importancia a los coeficientes a y b de la función. Los hemos aplicado a la sexta de las luces. 6.6.
AREA DE COBERTURA.
El área de cobertura hace referencia al área que abarca el haz de luz que surge de la fuente. Podemos definir los siguientes parámetros: •
GL_SPOT_CUTOFF para definir un "cono" de luz. Podemos especificar el ángulo de abertura del cono con un valor de 0.0 a 90.0 grados. Si optamos por el valor 180.0º estaremos desactivando esta opción. • GL_SPOT_DIRECTION para restringir la dirección de la luz emitida. Es un vector de tres valores reales XYZ. Por defecto la dirección es la de las Z's negativas. • GL_SPOT_EXPONENT que regula la pérdida de intensidad de la luz a medida que nos alejamos del centro del cono. Valores entre 0.0 y 128.0. Para más claridad veamos la figura:
27
Luces y lámparas
Figura 8.28
28
Luces y lámparas
7. LOS MATERIALES EN OPENGL Todos los materiales tienen color por si mismos. Por ejemplo, un objeto de color verde refleja mayoritariamente los fotones verdes y absorbe la mayoría de los demás. Por eso decimos que la luz que brilla sobre el objeto tiene fotones verdes reflejados que el observador puede detectar. La mayoría de las escenas que se nos pueden presentar, están iluminadas por una luz blanca que contiene una mezcla de todos los colores. De esta forma, casi todos los objetos bajo una luz blanca aparecen con sus colores naturales. Pero si ponemos un objeto verde en una sala oscura, iluminada solamente por una luz amarilla, el objeto aparecerá negro para el observador. Esto es debido a que el objeto absorberá toda la luz amarilla y no habrá ninguna luz verde que reflejar. 7.1.
LAS PROPIEDADES DE LOS MATERIALES
Cuando se usa iluminación, al describir un objeto no se dice que se objeto sea de un color en particular. Se describe el objeto indicando que esta hecho de un material que tiene ciertas propiedades reflectivas. Por ejemplo, en vez de decir que un objeto es verde, deberíamos decir que el objeto esta hecho de un material que mayoritariamente refleja la luz verde. Dicho de otra manera mas formal, podemos decir que la superficie del objeto es verde, pero también debemos especificar las propiedades reflectivas del material del que esta compuesto el objeto para las fuentes de luz ambiental, difusa y especular. Un material puede ser brillante y reflejar muy bien la luz especular, al mismo tiempo que absorbe la mayor parte de la luz ambiente y difusa. Pero también puede ocurrir que un objeto pueda absorber toda la luz especular y no ser brillante. 7.2.
LA LUZ EN LOS MATERIALES
Nuestra percepción del modelo se ve influenciada por los distintos tipos de superficies que tiene el modelo. Una superficie transparente permitirá que la mayor parte de los rayos penetren la superficie. El cristal, las láminas de plástico, las superficies acrílicas claras y el agua son ejemplos de superficies relativamente transparentes. Por otro lado, una superficie traslúcida solo permite el paso de algunos rayos de luz, y reflejar los otros. El plástico coloreado que cubre las luces de freno de la mayoría de los automóviles, y el cristal esmerilado, son ejemplos de superficies traslúcidas. Una superficie opaca reflejara algunos de los rayos y absorberá otros, pero no permitirá que pase ningún rayo a través del objeto. Un ladrillo es un ejemplo de superficie opaca.
29
Luces y lámparas
Superficie transparente
Superficie translúcida Figura 8.29
Superficie opaca
La luz pura es la blanca, compuesta de todos los colores. Los colores que son absorbidos por las superficies traslúcidas y las superficies opacas se pierden para el ojo humano; los colores que son reflejados por las superficies traslúcidas y las superficies opacas son los colores que llegan al ojo humano. Estos colores que se reflejan son el color del objeto, por decirlo de otra manera. 7.3.
ESPECIFICACIÓN DE MATERIALES EN OPENGL
Antes de empezar a activar luces hay que definir nuestros materiales. Para cada polígono de la escena hay que definir un material de forma que su respuesta a la incidencia de luz varíe según sea el caso. Está claro que no se refleja igual la luz en un pedazo de oro que en una manzana. Por tanto tenemos que decirle a OpenGL de que forma tendrá que tratar a cada geometría. Se definen cinco características fundamentales para un material. Estas componentes son: • • • • •
Reflexión difusa (diffuse) o color de base que reflejaría el objeto si incidiera sobre él una luz pura blanca. Reflexión especular (specular), que se refiere a los puntos brillantes de los objetos iluminados. Reflexión ambiental (ambient), define como un objeto (polígono) determinado refleja la luz que no viene directamente de una fuente luminosa sino de la escena en sí. Coeficiente de brillo o "shininess". Define la cantidad de puntos luminosos y su concentración. Digamos que variando este parámetro podemos conseguir un objeto más o menos cercano al metal por ejemplo. Coeficiente de emisión (emission) o color de la luz que emite el objeto.
Las componentes ambiental y difusa son típicamente iguales o muy semejantes. La componente especular suele ser gris o blanca. El brillo nos determinará el tamaño del punto de máxima reflexión de luz. Se pueden especificar diferentes parámetros en cuanto a material para cada polígono. Es una tarea ardua pero lógicamente a más variedad de comportamientos más real será la escena. El funcionamiento es el normal en OpenGL. Cada vez que se llama a la correspondiente función se activan esos valores que no cambiarán hasta llamarla de nuevo
30
Luces y lámparas con otros. Por tanto todo lo que se "renderice" a partir de una llamada heredará esas características. La función es: GLvoid glMaterialfv(GLenum face, GLenum pname, const GLfloat *params); Glenum face GL_FRONT GL_BACK GL_FRONT_AN D_BACK
Glenum pname GL_DIFFUSE GL_AMBIENT GL_AMBIENT_AND_ DIFFUSE GL_EMISSION GL_SPECULAR GL_SHININESS
const GLfloat *params ( R, G, B, 1.0 ) ( R, G, B, 1.0 ) ( R, G, B, 1.0 ) ( R, G, B, 1.0 ) ( R, G, B, 1.0 ) [ 0, 128 ]
En la tabla se observan los valores que pueden adoptar los parámetros de la función. En el caso de face tenemos tres posibilidades dependiendo de si la característica en cuestión debe aplicarse al lado visible (FRONT), al no visible (BACK) o a ambos. En cuanto a pname se define aquí cuál es la característica que vamos a definir en concreto. Las posibles son las que hemos comentado para un material. Por último *params, donde damos los valores concretos de la característica. Son tres valores, de hecho tres números reales que especifican un color RGB. Ese color define exactamente como debe verse el objeto que se renderice después en cuanto a color ambiente, difusión, componente especular, etc... Hay una excepción en el caso de GL_SHININESS. Si usamos esta constante como segundo parámetro, el tercero tendrá que ser un número entre 0 y 128 que controlará la concentración del brillo. Por defecto este valor vale 0. La misma función tiene también las formas glMaterialf, glMateriali y glMaterialiv. No suelen usarse por eso las versiones llamadas escalares (enteras) ya que sólo son útiles para definir GL_SHININESS. Valores típicos, son los usados por defecto, son de 0.8 para las tres componentes en GL_DIFFUSE, de 0.2 para GL_AMBIENT y de 0.0 en GL_EMISSION y GL_SPECULAR. Por supuesto tendremos que retocar estos valores hasta conseguir el efecto deseado. El cuarto valor, es decir el 1.0, se refiere al valor del canal alfa del color RGB.
31
Luces y lámparas
8. RENDER ÓPTIMO EN OPENGL 8.1.
Z-BUFFER Y SUPERFÍCIES OCULTAS
Los polígonos más alejados de la cámara pueden estar parcial o totalmente ocultos debido a la geometría más cercana. Eso es totalmente intuitivo y obvio para nosotros pero no para un ordenador. Al hacer el render tendremos que tener en cuenta el orden en que proyectamos la geometría. Por ejemplo, si tenemos un árbol a 100 unidades de distancia y un coche a tan sólo 10, tenemos que ver el coche delante del árbol, literalmente encima, en el plano de proyección. Para evitar esto tenemos dos opciones fundamentales: • Algoritmo del pintor (Painter's algorithm). Este algoritmo es tan simple como ordenar los objetos según su distancia a la cámara para después hacer el render de los más alejados primero y de los más cercanos después. Seria un render "Back to Front", desde atrás hacia adelante. • Algoritmo del Z-Buffer que OpenGL ya implementa automáticamente y que solventa los problemas del anterior. El algoritmo del pintor presenta problemas cuando hay polígonos que se interceptan entre ellos. Siempre pone uno totalmente delante del otro sin respetar esos casos en que se ve un pedazo de cada uno. Como he dicho esto lo solventa la segunda opción. El Z-Buffer opera de la siguiente manera. Tenemos un nuevo buffer que no almacenará colores, sino valores de profundidad, o distancias Z, respecto de la cámara.
Figura 8.30 En este caso tenemos 3 polígonos a distintas profundidades. Queda claro que el pixel P sólo puede ser de un color, o bien es rojo o amarillo o azul. El Z-Buffer analiza polígono a polígono de la siguiente manera:
32
Luces y lámparas
•
Mirará el cuadro azul y decidirá que P tiene que ser azul. En ese momento guardará el valor de profundidad Z3 asociándolo al pixel. • Más tarde encontrará el polígono amarillo y se dará cuenta de que este también contribuye coloreando a P. Dado que Z2 < Z3 decidirá que realmente P es amarillo ya que el segundo polígono está más cerca de la cámara. Almacenará Z2 para P en el Z-buffer. • De nuevo encontrará otro polígono que también colorea a P, el rojo, y como Z1 < Z2, finalmente almacenará Z1 y pintará al pixel de rojo. El algoritmo es muy simple e inteligente a la vez. De esta forma se tiene control a nivel de cada pixel individual de forma que seguro que acabará siendo coloreado de su color real sin fallo alguno. Para activar el Z-Buffer en OpenGL tenéis que añadir un parámetro a la función glutInitDisplayMode del main, dejándola así: GLvoid glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH); De forma que habilitáis a la librería para que reserve memoria RAM para guardar los reales de profundidad. Por otra parte y si utilizáis doble buffer, cosa altamente probable, tendréis que limpiar el Z-Buffer además del frame buffer. Lo podéis hacer con el glClear de siempre en el callback de display: GLvoid glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); Por cierto que podéis activar y desactivar el Z-Buffer a gusto según os convenga dejando las definiciones que os acabo de comentar de forma permanente y tan sólo llamando a: GLvoid glEnable(GL_DEPTH_TEST); GLvoid glDisable(GL_DEPTH_TEST); También podéis definir cual es el valor de profundidad que se almacena en el Z-Buffer al limpiarlo. Por defecto este valor es 1.0. Para cambiarlo: GLvoid glClearDepth(GLclampd profundidad); No suele cambiarse nunca este valor pero si se hace debe estar entre 0.0 y 1.0. Hay ciertos objetos que contienen caras que nunca serán visibles. Por ejemplo una esfera. ¿El interior hace falta renderizarlo?...pues la verdad es que no si nadie va a mirarlo. Si utilizamos GLUT para llamar a: GLvoid GLUTSolidSphere(GLdouble radio); Nos pintará una esfera sólida del radio deseado. El problema radica en que también estará pintando todas las caras interiores que no se ven, ralentizando así al sistema. Tenemos una función que optimiza en este sentido, es: GLvoid glCullFace(GLenum modo); Donde el modo puede ser: • GL_FRONT, que implica no pintar las caras frontales. • GL_BACK, que implica no pintar las caras traseras. • GL_FRONT_AND_BACK, que implica no pintar caras. Sólo se pintaran puntos y líneas. Activaremos este proceso de "Culling" mediante la función: 33
Luces y lámparas GLvoid glEnable(GL_CULL_FACE); 8.2.
EL CANAL ALFA
El modelo RGBA de color dota a cada punto de una cuarta componente llamada canal alfa. El canal alfa sirve para decidir que debe hacerse con ese punto si contribuye junto con otros a colorear un pixel. Hasta ahora el citado pixel se pintaría del color del polígono más cercano pero si activamos el canal alfa obtendremos "mezclas de colores" para el pixel. Cada polígono contribuirá en un cierto tanto por ciento a colorear el pixel de forma que podemos obtener interesantes efectos como emular un cristal tintado o un papel de celofán rojo, por ejemplo.
Figura 8.31 En esta imagen se observa que mezclando dos colores generamos un tercero o lo que es lo mismo, que si tengo un objeto de color rojo y le pongo delante un papel translúcido de color azul, la intersección de ambos se verá de color lila. Eso ocurre porque el polígono que está más cerca tiene un cierto grado de transparencia por lo cuál nos deja ver lo que tiene detrás. A esto le llaman los anglosajones "Blending". Siempre se habla de una fuente (source) y de un destino (destination). El destino es el valor de color que un pixel ya tiene asignado en el frame buffer. La fuente es el color que viene, el color que se mezclará con el que ya tiene. Se entiende que un objeto es totalmente opaco si está al 100% de alfa o lo que es lo mismo, ésta vale 1.0. Si alfa vale 0.0 quiere decir un 0% de opacidad, o sea un 100% de transparencia. Se combinan los colores de la fuente y del destino según el valor de alfa que tiene cada uno de ellos. Se define una función de mezclado, o de "blending" que, según sea, aplicará de una forma o de otra el canal alfa de cada color. Para que quede claro vamonos directamente a OpenGL. Utilizaremos la función: GLvoid glBlendFunc(GLenum factorFuente, GLenum factorDestino); Dos ejemplos: 34
Luces y lámparas
1. glBlendFunc(GL_ONE, GL_ZERO); 2. glBlendFunc(GL_SRC_ALPHA, GL_ONE); En el primer caso estamos dando una importancia de 1 (máxima) al canal alfa de la fuente y de 0 (nula) al canal alfa del destino. Eso equivale a decir que el color final se compone de un 100% del de la fuente y de un 0% del color destino por lo cual el pixel acaba siendo del color de la fuente. En el segundo caso le decimos a OpenGL que multiplique a la fuente por su valor de alfa y sume el resultado al color destino. En este caso y asumiendo un valor de alfa igual a 0.75 para la fuente, podríamos decir que: Color Resultante = (Color de la fuente x 0.75) + Color del Destino O sea un color que tiene un 75% del color fuente y un 100% del color destino. Por supuesto también podemos activar o desactivar el mezclado de colores con: GLvoid glEnable(GL_BLEND); GLvoid glDisable(GL_BLEND); Hay un problema y consiste en lo siguiente. Digamos que el Z-Buffer no sabe absolutamente nada de los valores de alfa y se dedica a eliminar polígonos ocultos, no a mezclarlos. Esto hay que solucionarlo con ingenio y no a base de funciones de OpenGL. Se puede seguir una secuencia de este estilo: 1. Dibujar los objetos opacos con el Z-Buffer activo. 2. Utilizar la función glDepthMask(GL_FALSE); para desactivar la escritura en el ZBuffer. Se siguen consultando valores pero no se re-escriben. 3. Dibujar todos los polígonos transparentes. 4. Reactivar la escritura en el Z-Buffer con glDepthMask(GL_TRUE);. Esto no es una solución definitiva ya que muchas veces se nos plantean nuevas situaciones difíciles de arreglar, según sea la escena. Soluciones más robustas ya son más complicadas e implican usar estructuras de datos como los árboles BSP o dibujar siempre en orden "Back to Front". 8.3.
EFECTO DE NIEBLA
Podemos añadir niebla a nuestras escenas de una forma sencilla. El realismo aumenta considerablemente si además tenemos luces. Se puede producir la ilusión de distancia pintando aquello lejano de un color más tenue que lo cercano. Esta idea es ya muy antigua y se aplicaba en los primeros sistemas gráficos, aquellos que sólo pintaban líneas y además en un sólo color. Disminuían la intensidad del color de la línea a medida que ésta se alejaba del punto de vista dando una clara sensación de profundidad aún sin tener Z-Buffer ni polígonos con color de relleno. A esto se le llama "Depth Cueing". Se puede emplear el mismo paradigma para crear niebla. La niebla no es más que un espacio parcialmente opaco entre un objeto y la cámara. Podemos hacerla más espesa cerca de nosotros y menos lejos. Si partimos de tener un cierto factor de niebla F podemos hablar de una relación así:
35
Luces y lámparas ColorFinalObjeto = F * ColorInicialObjeto + ( 1 - F ) * ColorNiebla Modificamos el color del objeto al hacer el render. Lo alteramos con el color y el factor de la niebla. Se observa que esta relación nos permite crear un nuevo color para el objeto que está formado por un determinado tanto por ciento del original al que se le suma algo del color de la niebla. La idea tiene una relación clara con el canal alfa. Para complicar un poco el tema podemos hacer que el factor F varíe linealmente entre un valor máximo y otro mínimo según la distancia del objeto a la cámara. Así añadimos el efecto "Depth Cue" al sistema y "espesamos" más o menos la niebla según la distancia. El factor también puede variar de forma exponencial y es en este caso en el que se obtienen las más altas cotas de realismo. Veamos un ejemplo:
Figura 8.32 Fijarse como la densidad de la niebla va disminuyendo a medida que crece la distancia del objeto al observador. Esa disminución puede ser lineal, exponencial o Gausiana si es OpenGL quien se encarga de ello. En casos distintos tendremos que ser nosotros los que implementemos el efecto. Es increíblemente fácil añadir niebla con OpenGL: GLfloat colorNiebla[4] = { ... }; ... glEnable(GL_FOG); ... glFogf(GL_FOG_MODE, GL_EXP); glFogf(GL_FOG_DENSITY, 0.75); glFogfv(GL_FOG_COLOR, colorNiebla); Fijarse que definimos un color para la niebla y un tipo de función para F (exponencial en este caso y de expresión e^exp donde exp = -0.75*z^2).
36
Luces y lámparas
9. EFECTOS DE ILUMINACIÓN Dependiendo del tipo de material con el que esté hecho el objeto que estamos modelando, será suficiente o no con aplicar sólo las componentes de luz difusa y ambiente para darnos la ilusión de tener nuestro objeto iluminado. Por ejemplo, si estamos modelando un objeto de arcilla cuyo color es plano, con esto será suficiente. Pero para dotar de verdadero realismo a una escena, es necesario introducir la componente especular de la luz. Basándonos en ella, conseguiremos importantes efectos sobre nuestros objetos, como por ejemplo difuminar la composición de polígonos de nuestra escena, dotar de mayor realismo al desplazamiento de los objetos, o bien, aportar los efectos de brillo de los diferentes materiales, para distinguir por ejemplo un objeto metálico de uno que no lo es. Además debemos hablar de algunos efectos importantes de la iluminación como son las sombras, y algunos que aunque menos importantes si son muy vistosos, como la niebla. 9.1.
COMPONENTE ESPECULAR
Dependiendo de cómo apliquemos la componente especular en una escena, la sensación de realismo será mayor o menor. El principal problema es que esta componente la debemos calcular en tiempo real, lo cuál nos lleva a la eterna contradicción entre calidad y rendimiento de nuestro ordenador. Cuando añadimos una componente especular a una escena, esto se va a traducir en un “blanqueamiento del color” de los objetos, y va a depender mucho del material con que estemos calculando el objeto. Este “blanqueamiento” se denomina reflejo especular, y va a depender del punto de vista que tengamos sobre el objeto, por lo que se hace imposible calcular estos efectos a priori, hay que calcularlos en tiempo real, de ahí que la mayoría de procesadores gráficos tengan optimizadas las funciones de cálculo de reflejos especulares. Al introducir estos efectos conseguiremos dos grandes cosas en nuestra escena: dotar de mayor sensación de movimiento a los objetos, al variar estos efectos junto con el movimiento de los objetos y dotar de mayor realismo a la apariencia de los objetos, eliminando ese “efecto sintético” que se produce en las escenas en 3D. La iluminación especular y las propiedades del material añaden el brillo necesario a la superficie de nuestros objetos. Este brillo, que, como ya hemos dicho, tiene un efecto blanqueador sobre el color, produce reflejos especulares cuando el ángulo de reflexión es nítido con respecto al observador. Un reflejo especular consiste en que casi toda la luz que incide sobre un material es reflejada, produciendo los famosos puntos blancos de luz sobre la superficie de los objetos.
37
Luces y lámparas
Figura 8.33 En estas dos imágenes se observa claramente la diferencia entre una escena con iluminación ambiente y difusa (izquierda) y una escena con reflejos especulares (derecha). Otro aspecto a tener en cuenta es el tipo de luz que estamos aplicando a nuestra escena. Podemos aplicar un foco de luz puntual, también llamado “efecto foco”, cuyos rayos de luz son divergentes, o bien podemos añadir un foco de luz situado en el infinito, en cuyo caso, los rayos serán paralelos. En cada caso, el punto brillante obtenido en cada caso será diferente dada la naturaleza paralela o divergente de los rayos. Vamos ahora a ver cómo se incluyen todas estas cosas con OpenGl: En primer lugar hay que aclarar que desde el punto de vista de OpenGl, los efectos de luz especular se consiguen en dos partes: en primer lugar añadiendo un foco de luz especular, y luego ajustando el brillo del objeto mediante las propiedades del material con el que está hecho el mismo. 9.2.
LUZ ESPECULAR
Para añadir la componente de luz especular a una escena, solo tenemos que: • Definir las matrices de los diferentes efectos de luz: ambiente, difusa, especular. • Ir añadiendo cada una de las componentes al foco de luz mediante la función glLight. Estas matrices de efectos de luz contienen cuatro valores RGBA (rojo, verde,azul,alfa), y dependiendo los valores que seleccionemos, el efecto producido será de una u otra forma de color. Así por ejemplo, si queremos una componente de luz blanca muy brillante de luz especular, la matriz de efectos para esa componente será por ejemplo: Espec[] = 1.0f,1.0f,1.0f,1.0f}; GlLight(GL_LIGHT0,GL_SPECULAR,Espec);
38
Luces y lámparas De esta manera ya hemos añadido a muestra fuente de luz la componente especular, que en este caso, y debido a los valores de la matriz que hemos introducido como último parámetro, la componente especular es blanca y muy brillante. 9.3.
REFLECTANCIA ESPECULAR
Este efecto se refiere a ese “punto brillante” del objeto. Para añadir reflectancia especular a un objeto, no sólo es suficiente con añadir la componente especular a nuestro foco de luz, sino que además, hay que modificar las propiedades del material con que está hecho el objeto para que lo soporte. Así pues, debemos invocar a la función glMaterial en alguna de sus variantes para que en lo sucesivo, los objetos que se dibujen contengan esta componente de reflejo especular. Esto tiene su lógica, ya que, por ejemplo, si estamos representando un objeto hecho de arcilla, la arcilla no es brillante, y apenas tiene reflectancia especular. Ahora bien, si estamos representando un objeto metálico, el metal es muy brillante, y por tanto hay que ponerle una reflectancia especular con una componente de luz muy brillante. Sirva de ejemplo el siguiente: Glfloat espec[4] = {1.0f,1.0f, 1.0f,1.0f}; GlEnable(GL_COLOR_MATERIAL), GlColorMaterial(GL_FRONT,GL_AMNBIENT_AND_DIFFUSE); GlMaterialfv(GL_FRONT,GL_SPECULAR,espec); GlMateriali(GL_FRONT,GL_SHININESS,128); En primer lugar, estamos definiendo la matriz de la componente especular, toda a unos, lo que indica un reflejo muy brillante de luz blanca. Posteriormente activamos el seguimiento de color para el material que estamos empleando, y con la siguiente sentencia selecciona que ese seguimiento se produzca solamente para la luz difusa y la ambiente, y no para la especular. De esta manera lo que conseguiremos es “blanquear el color” para las componentes difusa y ambiente. Luego define las propiedades del material para que contenga esa componente especular, y con los valores RGBA definidos por la matriz espec[], de tal manera que el material va a reflejar casi toda la luz que incida de manera directa sobre él, de tal manera que a partir de ahora, todos los materiales que se dibujen, tendrán esta propiedad. En definitiva, hemos definido la escena de tal manera que, a partir de ahora, todos los materiales que se dibujen, sean del color que sean, seguirán a ese color en lo que a componentes de luz ambiente y difusa se refiere, pero para la componente especular, no hay seguimiento del color, con lo cual, solo depende de los valores de la matriz de componente especular. Por último, queda definir el tamaño del punto de reflexión. La reflexión especular produce un blanqueamiento del color allá donde incide de manera directa. El tamaño de la superficie donde queremos que incida de manera directa lo definimos con la última sentencia del ejemplo. Este tamaño oscila en un rango entre 0 (un punto) y 128 (toda la superficie expuesta al foco de luz), es decir, cuanto más grande es este valor, más grande y más brillante es la superficie que brilla. Esto lo conseguimos con la llamada a la función GlMaterial con el parámetro GL_SHININESS seguido de ese valor entre 0 y 128 que determina el tamaño del punto brillante.
39
Luces y lámparas
Figura 8.34 En esta imagen hemos seleccionado el valor 128 para el tamaño del punto luminoso.
Figura 8.35 En esta otra hemos seleccionado el valor 0. Vemos en las figuras, tomadas ambas del mismo programa, cómo difiere el tamaño del punto brillante sobre la esfera según seleccionamos un valor u otro en la función anteriormente descrita. 9.4.
TÉCNICAS DE ILUMINACIÓN.
Añadir un reflejo especular a un objeto, como hemos visto antes, consiste, a grandes rasgos, en añadir ese punto brillante cuyas características van a venir dadas por las propiedades del material y del foco de luz. Pero esto es solo el principio, ya que la parte más complicada del tema aún no la hemos abordado, como es el realizar los cálculos para determinar la intensidad del foco luminoso, calcular normales, sombras, etc. A continuación vamos a ver algunas técnicas relacionadas con la iluminación de una escena. 9.5.
PROMEDIO NORMAL
En otros capítulos, veremos que para modelar un objeto complejo, vamos a utilizar una serie de polígonos ensamblados, dependiendo la calidad del objeto del número de 40
Luces y lámparas polígonos utilizados. Para difuminar el efecto que producen las esquinas en las que estamos juntando los polígonos, y dar la sensación de ser una figura curva, se utiliza la técnica del promedio normal. Esta técnica consiste en que dada una superficie quebrada, compuesta por varios polígonos, cada vértice va a limitar con dos superficies, y se puede calcular la normal en ese vértice como el promedio de las normales de las superficies que limitan con ese vértice. Si usamos esta normal en las especificaciones de las superficies, cuando apliquemos el sombreado, hará que la unión aparezca menos nítida, causando efectos ópticos interesantes, que pueden hacernos pensar que se trata de superficies curvas en lugar de aristas.
Figura 8.36 La gran problemática de esta técnica es de tipo geométrico, y consiste en calcular para cada vértice las normales de todas sus caras y promediarlas. 9.6.
CUBE ENVIRONMENT MAPIN
Queda claro que si queremos obtener imágenes realistas, lo más parecido a como lo captaría una cámara de vídeo, no podemos olvidarnos de los reflejos y demás efectos especulares. Pero este tipo de efectos es muy dependiente del punto de vista y de la situación de los objetos de tal manera que cualquier cambio en el punto de vista o cualquier movimiento. Utilizando otras técnicas como el Ray tracing, nos lleva a cálculos muy costosos y complejos que además han de volver a hacerse cada vez que cambiamos el punto de vista o movemos el objeto. El mapeo del entorno, que consiste en mapear el entorno como una imagen predefinida sobre una figura geométrica que envuelve el objeto, se convierte en una buena alternativa. Pero el mapeo del entorno utilizado actualmente, basado en una esfera como figura geométrica envolvente a nuestra escena es muy limitado, es muy costoso de implementar y solo produce resultados satisfactorios bajo unas condiciones muy determinadas dado la gran dependencia del punto de vista que tiene. 41
Luces y lámparas La solución a este problema es el mapeo cúbico del entorno, o cube environment mapping, que consiste principalmente en la utilización de un cubo como figura geométrica envolvente de nuestra escena. Esta técnica, está soportada por la mayoría de entornos de programación gráfica en tres dimensiones como OpenGl o DirectX está ademas contemplada su aceleración hardware en los procesadores gráficos más conocidos.
Figura 8.37 Como hemos dicho, el mapeo cúbico, consiste en proyectar el entorno en las 6 caras de un cubo, de tal manera que podemos implementar con exactitud y sin excesivos cálculos las reflexiones y efectos especulares independientemente del punto de vista utilizado siempre y cuando nuestra escena tenga un entorno estático. No obstante, como los mapeos son más sencillos que en el caso del mapeo tradicional, y como además muchos de los procesadores gráficos ya tienen contemplada la aceleración hardware de esta técnica, los cambios en el entorno pueden ser captados y mapeados en tiempo real sin mayor problema. Para el caso que nos concierne, que es la consecución de unos efectos especulares más realistas, también podemos utilizar esta técnica: si en lugar de aplicar la iluminación con sus efectos, para cada polígono de nuestra figura, renderizamos la luz especular sobre el mapa cúbico, y luego lo aplicamos sobre nuestra figura, podemos conseguir mayor precisión en los efectos, y además la posibilidad de implementar efectos tan interesantes como ese aura que se forma entorno a los puntos brillantes cuando estamos aplicando iluminación de gran intensidad. 9.7.
BI-DIRECTIONAL REFLECTANCE DISTRIBUTION FUNCTION
A continuación vamos a dar una visión muy general de lo que es la función de distribución bidireccional de la reflectancia. Para obtener una información más detallada de cómo trabaja esta técnica, en la sección de bibliografía tenemos referencias a documentación que amplía todo este tema.
42
Luces y lámparas Esta es una técnica de iluminación que, para comprenderla, hay que volver al principio. La luz, cuando interacciona con la materia, producirá una serie de efectos que dependerán tanto de la naturaleza de la luz como de la naturaleza del material sobre el que incide. Pero en general, por el principio de conservación, tendremos que: Luz incidente = luz reflejada + luz absorbida + luz transmitida. En apartados anteriores hemos hablado de todos estos tipos de luz, pero la parte complicada, que es el cálculo de la cantidad de luz que refleja un objeto, aún no hemos profundizado lo suficiente. La cantidad de luz reflejada por un material va a depender de los siguientes factores: • Punto de vista desde el que estemos observando la escena, • Las propiedades de reflexión del material para cada una de las diferentes longitudes de onda de la luz • La heterogeneidad de la superficie sobre la que incide, que dependiendo del punto iluminemos tendrá unas propiedades u otras. Así pues, tendremos que la cantidad de luz reflejada, sigue la siguiente función:
Donde: λ= Longitud de onda. θ y Ф= Luz incidente y reflejada en coordenadas esféricas. u y v = Coordenadas parametrizadas del plano sobre el que incide. Esta función se simplifica en su aplicación, ya que en general se van a omitir las dós últimas variables, y con respecto a la longitud de onda, calcularemos esta función para cada uno de los canales de color Rojo, Verde y Azul, con lo que tendremos que BDFR es un vector de tres componentes. Cálculo de BDRF: La función BRDF calcula cuanta luz es reflejada para un determinado punto de vista. Pero para calcular esto, previamente es necesario calcular cuanta es la luz que incide en una determinada dirección. Cuando hablamos de la cantidad de luz que incide en una determinada dirección, más bien hay que hablar de la cantidad de luz que atraviesa una determinada porción de superficie, también conocido como ángulo sólido diferencial. La siguiente figura lo ilustra claramente:
43
Luces y lámparas
Figura 8.38 Como nosotros estamos trabajando en coordenadas esféricas, básicamente y omitiendo los cálculos, vamos a considerar como luz incidente aquella que atraviesa una pequeña porción de esfera cuya superficie viene determinada por las coordenadas esféricas del ángulo de incidencia de la luz, tal y como se muestra en la siguiente figura:
Figura 8.39 Para nosotros, omitiremos los cálculos matemáticos, y partiremos de un haz de luz entrante en la dirección de wi cuyo ángulo sólido diferencial será d wi y lo mismo para el haz de luz reflejado siendo wo y d wo la dirección y ángulo sólido diferencial respectivamente. Si la cantidad de luz que llega en la dirección wi es Ei y la cantidad de luz reflejada en la dirección wo es Lo entonces, podemos definir la función de la siguiente forma: 44
Luces y lámparas Lo BRDF = Ei Esto en general es la descripción teórica, a grandes rasgos, de la función BRDF. Pero a priori resulta muy engorroso de aplicar, teniendo en cuenta que todos estos cálculos se han de hacer en tiempo real, cada vez que se modifique el punto de vista, el objeto, etc. Necesitamos una función más sencilla, para lo cual podemos aproximar la función en una textura de cuatro dimensiones, que a su vez podemos dividir en dos texturas bidimensionales, cuyos cálculos pueden ir acelerados por hardware, obteniendo la siguiente ecuación: Li = G (θ i , φi )·H (θ o , φ o ) L i ·Cosφi O aproximando para nosotros: BDFR = G · H Observamos que la función BDRF de cuatro dimensiones la hemos descompuesto en dos funciones de dos dimensiones. Estas dos funciones se corresponden con dos texturas bidimensionales en las que se descompone BRDF y que luego hay que reconstruir y aplicar mediante por ejemplo dos mapas cúbicos de los que hemos hablado antes. Paso 1: separar la función en producto de dos funciones. Esta es la primera fase, la de separación, y que se puede realizar durante el preproceso de la escena. El proceso de separación es bastante complejo, pero podemos interpretar que estamos intentando mapear una textura de 4 dimensiones en un espacio de dos dimensiones. Si queremos mapearlo en una textura de 16x16x16x16, debemos samplear cada parámetro 16 veces en cada dominio, que como estamos hablando de coordenadas esféricas tenemos que el dominio de ? oscila entre 0 y ? ?? y el de ? entre 0 y ?? . Si además almacenamos los resultados en una matriz N2 x N2, el pseudocódigo podría ser como sigue: double deltat = (0.5 * M_PI) / (N-1); double deltap = (2.0 * M_PI) / N; double theta_i, phi_i; double theta_o, phi_o; for ( int h = 0; h < N; h++ ) for ( int i = 0; i < N; i++ ) for ( int j = 0; j < N; j++ ) for ( int k = 0; k < N; k++ ) { theta_o = h * deltat; phi_o = i * deltap; theta_i = j * deltat; phi_i = k * deltap; /* Compute or lookup the brdf value. */ val = f( theta_i, phi_i, theta_o, phi_o ); /* Store it in a N2 x N2 matrix. */ BRDFMatrix[h*N+i][j*N+k] = val; } 45
Luces y lámparas
Ahora, debemos de trabajar con esta matriz, en primer lugar calculando la norma para cada una de las filas obteniendo así un vector Nx1 que va a ser nuestra función H; en segundo lugar, para cada columna calculamos la media utilizando los valores del vector normal obtenido antes proporcionándonos con esto un vector 1xN que consideraremos como nuestra función G. Todo este proceso lo tenemos que repetir para cada uno de los canales de color: el rojo, el verde y el azul, y para el caso de OpenGl que utiliza también el valor alfa, también habría que calcular las funciones G y H para ese valor. Una vez calculadas las funciones G y H habrá que crear con ellas la textura de cubo para aplicar por ejemplo el mapeo cúbico. En primer lugar debemos samplear el cubo de la misma manera que hemos sampleado la esfera en N valores. Entonces, para cada segmento, calculamos las coordenadas esféricas del centro y con ellas obtenemos los valores RGBA mediante las funciones G y H. Hay que apuntar que en ciertos entornos como OpenGl en los cuales los valores RGB oscilan entre 0 y 1 habrá que adaptar las funciones G y H para que oscilen entre esos valores.
46
Luces y lámparas
10. FOCOS DE LUZ A la hora de definir la iluminación de una escena, uno de los puntos importantes a tener en cuenta es dónde colocar la fuente o fuentes de luz. La fuente de luz puede ser de diversas maneras, un punto luminoso que radia por igual en todas direcciones, un efecto foco, un punto de luz situado en el infinito, y que provoca que los rayos de luz sean paralelos y no divergentes, etc. La decisión de qué tipo de foco de luz utilizar dependerá mucho de la escena que estemos modelando, así por ejemplo, si queremos modelar una escena de iluminación solar, debemos escoger una aproximación a un foco de luz situado en el infinito y de rayos de luz paralelos o también llamado fuente de luz direccional. En este caso, desaparecería el efecto del punto de luz sobre los objetos, o más bien, tendríamos un punto de luz que abarcaría toda la superficie iluminada del objeto. En general, como hemos dicho antes, tenemos dos tipos de luz: luz propia y luz impropia. • Luz propia es aquella proporcionada por un foco de luz bien situado, del cual conocemos su ubicación, y que emite sus rayos de luz de forma radial o divergente (figura de la izquierda). • Luz impropia es aquella que emite sus rayos de luz desde una ubicación muy distante, de forma que podemos considerar sus rayos como paralelos entre si (figura de la derecha).
Figura 8.40
Ni que decir tiene que los cálculos que implica el uso de un foco de luz propia son mucho más complejos por lo que aunque proporciona efectos mucho más realistas, en general se tiene en lo posible al uso de luz impropia. Veamos un ejemplo de foco de luz en OpenGl: En primer lugar determinamos la posición y el tipo de foco. Para ello inicializamos un vector de 4 valores donde tenemos las coordenadas x, y, z y un valor que oscila entre 0 y 1 47
Luces y lámparas y que nos indica el tipo de foco. Los valores próximos a 0 indican luz impropia y los próximos a uno luz propia. GlFloat LightPos [] = {0.0f,150.0f,150.0f,1.0f}; En este caso hemos elegido luz propia. Suponemos que ya lo hemos inicializado con los valores correctos de luz ambiente, difusa y especular, y ahora inicializamos la posición: glLightfv (GL_LIGHT0, GL_POSITION,LightPos); Ahora imaginemos que además de un foco de luz propia queremos darle ese efecto de foco de luz dirigido, que emite un cono de luz en una determinada dirección: glLight (GL_LIGHT0, GL_SPOT_CUTOFF, 60.0f); Además debemos inicializar el brillo del foco mediante la siguiente instrucción: glLight (GL_LIGHT0, GL_SPOT_EXPONENT, 100.0f); En este caso hemos elegido un foco de luz muy brillante. Hasta aquí hemos visto cómo colocar uno o varios focos de luz, pero aún no hemos hablado de cómo esos focos de luz interaccionan con nuestra escena. En general, cuando iluminamos una escena aplicamos dos tipos de iluminación, a saber, iluminación local e iluminación global. La primera, habla de cómo se comporta cada superficie al ser iluminada de forma individual. La segunda habla de la luz como un todo, de cómo interacciona esa luz que es reflejada por un objeto sobre el objeto sobre el que es proyectado ese reflejo. 10.1. ILUMINACIÓN GLOBAL De iluminación local ya hemos comentado lo que es y como calcularla y aplicarla utilizando técnicas como BDFR, por tanto vamos ha hablar ahora de lo que es iluminación global. Podemos hablar de luz como una serie de partículas llamadas fotones que viajan con una determinada longitud de onda y que, al impactar sobre un objeto, estos son absorbidos, transmitidos o reflejados dependiendo de la longitud de onda y de las propiedades del objeto. De esta manera podemos decir que la iluminación global de una escena está formada por la interacción de billones de fotones viajando por la escena con las diferentes superficies y objetos que componen la escena. Así pues tenemos dos posibles técnicas para el modelado de la iluminación global: Ray tracing Este algoritmo se basa en la circulación de los fotones a lo largo de una escena, computando sólo aquellos rayos que llegan a nuestro punto de vista, y siguiéndolos en sentido inverso. Esta computación se realiza para cada píxel de la siguiente forma: 1.- Para cada píxel trazamos un rayo desde nuestro punto de vista hasta que impacta con algún objeto. 2.- Desde el punto donde impacta con el objeto se trazan rayos a todos los puntos de luz de la estancia, y para cada uno, si este no es bloqueado por ningún objeto intermedio, se calcula el color en función de las propiedades del material y del foco de luz en cuestión. 3.- Si la superficie sobre la que impacta es un espejo o una superficie transparente, habrá que aplicar los pasos 1 y 2 siguiendo la dirección en que el rayo es transmitido o reflejado, teniendo en cuenta la reflexión o refracción del medio. De la misma manera si el objeto hacia el que es reflejado o transmitido el rayo es a su vez
48
Luces y lámparas reflectante o transparente, se vuelve a aplicar 1 y 2, y así sucesivamente hasta un número de iteraciones determinado. Esta técnica permite crear multitud de efectos como sombras, espejos, transparencias, etc. Pero tiene el problema del elevado coste computacional que conlleva dado que todos estos cálculos han de realizarse para cada píxel. Radiosity. Esta técnica difiere fundamentalmente de la anterior en que no calcula el color para cada píxel de la pantalla, sino para una serie de puntos discretos de la misma, y se basa en los métodos ideados por los ingenieros técnicos para el cálculo del calor transmitido por los objetos a principios de los 60, eso sí, aplicado a la transmisión de la luz en una estancia. El funcionamiento de esta técnica es muy sencillo: • En primer lugar dividimos las superficies originales en un conjunto de superficies más pequeñas formando con ellas una malla. Durante el proceso de cálculo, calcularemos la luz transmitida por cada elemento de la malla sobre el resto de elementos de la malla. • Mediante este algoritmo, calculamos la transmisión de cada elemento de forma inicial, sin mostrar ningún resultado válido hasta que no se ha realizado el cálculo de todos los elementos de la malla, lo cual permitía hacerlo en el preproceso. • Pero este algoritmo fue sucesivamente refinado hasta obtener el que se muestra a continuación: o Cada superficie de divide en elementos relativamente largos, de tal manera que cada uno se puede subdividir automáticamente si se observa que hay una gran diferencia de intensidad con los elementos adyacentes si estos son por ejemplo sombras. o Cada complejo luminoso (pueden ser lámparas compuestas de diversos focos) distribuye la luz sobre todas las superficies, pudiendo unos elementos de la malla bloquear a otros, generando de esta manera las sombras. o Se asume que unas superficies absorben más luz que otras, pero también que todas ellas reflejan la luz de forma ideal, es decir, por igual en todas las direcciones. o Después de distribuir la luz sobre todas las superficies, tomamos aquel elemento de superficie que más reflexión produce y lo tomamos a su vez como otra fuente de luz, realizando los cálculos oportunos sobre el resto de elementos de superficie. o Este proceso continúa hasta que al realizar los cálculos obtengamos que la mayor parte de la energía es absorbida. Llegados a este punto tenemos que cada fuente de luz y cada superficie son una iteración, hasta llegar al momento en que la energía absorbida por cada superficie es mayor que la que le llega, de tal manera que la luz transmitida es imperceptible. Combinando las dos. Estas dos técnicas que hemos visto, son diferentes entre sí, cada una con sus ventajas y sus desventajas, pero en cierto modo complementarias entre si. • Ray tracing: o Ventajas:
49
Luces y lámparas Precisión para modelar la iluminación directa, sombras, espejos y transparencias. Bajo consumo de memoria.
•
o Desventajas: Computacionalmente caro, con cálculos muy costosos y muy dependientes del número de focos de luz de la escena. Dependiente del punto de vista, lo que obliga a realizar de nuevo todos los cálculos cada vez que este cambia. Sin precisión al trabajar con las reflexiones difusas. Radiosity: o Ventajas: Calcula los interreflejos difusos entre superficies. Independiente de vista, para poder visualizar rápidamente superficies arbitrarias. Rápidos resultados, pudiendo ir visualizando los resultados intermedios e ir refinando estos progresivamente en exactitud y calidad. o Desventajas: El sampleado o división en superficies pequeñas requiere más memoria que las imágenes originales. Este sampleado es a su vez más propenso a la introducción de errores en la escena. No hay precisión en imágenes especulares ni en efectos de transparencia.
Vistas ventajas e inconvenientes de ambas técnicas, la manera de combinar ambos depende de la decisión del programador y de la composición de la escena, así por ejemplo, si estamos modelando una habitación llena de espejos utilizaremos Ray tracing como técnica principal completándola con Radiosity para generar los efectos de las reflexiones difusas.
50
Luces y lámparas
11. SOMBRAS Añadir sombras a una escena va a redundar en un mayor realismo de la misma, pero va a implicar una serie de cálculos adicionales, que, dependiendo del realismo que queramos, serán más o menos complejos. En general, la forma más sencilla de dibujar una es proyectar el objeto y oscurecer la región de proyección. Pero tiene sus limitaciones, sobre todo cuando esas proyecciones de sombra se realizan sobre volúmenes y no sobre el plano que conforma el suelo, lo cual es más habitual de lo que parece. Por este motivo se utilizan técnicas más complejas, como la utilización de sombras volumétricas, para lo cual necesitamos disponer de información adicional, tal y como se muestra con la técnica que describiremos a continuación. 11.1. SOMBRAS PROYECTADAS Es la manera más sencilla que hay para dibujar una sombra en OpenGl. La idea consiste en calcular la proyección del modelo sobre el plano sobre el que queremos calcular la sombra. Una vez calculada esta matriz, si la multiplicamos por la matriz del modelo vamos a obtener todo nuestro modelo proyectado sobre ese plano. Ahora solo queda elegir el color adecuado para nuestra sombra y volver a dibujar aquellas figuras de las que queramos dibujar su sombra. El pseudocódigo del proceso sería algo así: • Calculamos la matriz de proyección desde el foco de luz sobre el plano. • Dibujamos nuestra escena. • Deshabilitamos la iluminación. • Guardamos la matriz del modelo. • Multiplicamos la matriz del modelo por la matriz de proyección. • Seleccionamos el color adecuado para nuestras sombras. • Redibujamos aquellas figuras de las que queramos proyectar su sombra. • Recuperamos la matriz del modelo y activamos la iluminación. Dependiendo del color que elijamos según para que objetos de los que estamos dibujando proyectados sobre el plano podemos incluso simular efectos de sombra-penumbra, aunque no muy realistas. 11.2. STENCIL BUFFER O BUFFER PLANTILLA El stencil buffer o buffer cliché o buffer plantilla consiste en añadir una serie de planos de bits con información extra que nos permite en un momento dado aceptar o rechazar el procesado de un determinado píxel. Es un test soportado por la mayoría de implementaciones como OpenGL, que está acelerado por hardware, y que es completado con el test de profundidad. Se trata de evitar en los renderizados el cálculo de aquellos pixeles que van a ser ocultados en una escena o bien utilizar su valor para aportar información extra a la escena que estamos modelando.
51
Luces y lámparas
Como trabaja el test de plantilla por píxel.
El funcionamiento del buffer de plantilla es muy similar al de profundidad. Se trata de añadir unos planos de bits no visibles, donde para cada bit tendremos almacenado un valor que nos indicará el valor de plantilla del mismo. Este valor será utilizado para aceptar o rechazar la visualización de un determinado píxel ahorrándonos los cálculos que ello conlleva. El valor se almacena como un entero sin signo, y la forma de tratarlo será con operaciones de comparación de tal manera que si su valor iguala o supera al de un determinado valor de referencia, el píxel es aceptado, y si no lo supera es rechazado. Estas operaciones consisten en operaciones de comparación y desplazamiento a nivel de bit, por lo que la carga computacional es muy pequeña y, sin embargo, si el píxel es rechazado, nos ahorramos el resto de operaciones, con lo cual queda evidente el ahorro computacional que supone. También se pueden realizar otras operaciones sobre este buffer como resetear su valor, incrementarlo o decrementarlo lo que nos permitirá ajustar oportunamente este valor en tiempo de ejecución. Utilización del buffer plantilla para crear sombras.
Hasta ahora hemos visto a muy grandes rasgos en qué consiste el buffer plantilla, ahora vamos a ver alguna aplicación práctica, en concreto su aplicación al cálculo de sombras. Hasta ahora la forma más común de calcular sombras era mediante la proyección de los objetos sobre el plano. Esta es la forma más conocida y fácil de implementar, pero tiene una pequeña limitación: todos los polígonos proyectados son coplanares entre si, y al mismo tiempo, coplanares con el “suelo” que estamos representando, lo cual ocasiona problemas con el test de profundidad, ya que todos los polígonos proyectados tienen el mismo valor en el buffer de profundidad. Más limitaciones de esta técnica son que no se pueden proyectar sombras sobre cualquier objeto, han de ser proyectadas obligatoriamente sobre el suelo; si queremos utilizar el efecto sombra-penumbra, los bordes nos van a aparecer muy fuertes; se nos produce un efecto de doble sombra en aquellos lugares donde se superponen varios polígonos en la proyección; etc. La utilización del buffer de plantilla, junto con esta técnica de cálculo de sombras, nos va a solucionar varios de estos problemas, sirvan los siguientes ejemplos: • Para el problema de limitar la proyección a una determinada superficie, podemos asignar unos valores a los pixeles que están dentro de la superficie, y otros a los que quedan fuera, de tal manera que al aplicar el test, los pixeles que quedan fuera serán rechazados y no se calculará la sombra sobre ellos. • Para el problema de la doble sombra, podemos, por ejemplo, resetear a cero el valor de plantilla de cada píxel una vez que ha sido procesado, de tal manera que en las zonas donde tengamos polígonos superpuestos, sólo se aplicará una vez el sombreado, ya que cuando se vaya a aplicar por segunda vez, se va a encontrar el valor de plantilla para ese píxel a cero y no aplicará el sombreado por segunda vez. Veamos cuál es la secuencia de operaciones a realizar: 1.- En primer lugar, vaciamos el buffer de plantilla, asignándole el valor cero o cualquier otro valor no utilizado teniendo en cuenta que el siguiente valor consecutivo también debe estar sin usar. 52
Luces y lámparas 2.- En segundo lugar activamos la iluminación y los focos de luz. 3.- En tercer lugar renderizamos todos los objetos, pero aún no lo hacemos con las superficies planas sobre las que vamos a proyectar las sombras. 4.- Ahora renderizamos las superficies planas con los focos de luz desactivados asignándole a los pixeles de dichas superficies los valores de plantilla correspondientes a una superficie plana. 5.- Ponemos la luz ambiente a valor cero y vamos procesando cada foco de luz de la siguiente manera: 6.- Para cada superficie plana y cada foco de luz: • Hacemos un push de la matriz del modelo sobre la pila. • Renderizamos todas las sombras proyectadas por ese foco de luz. Durante este proceso, se van a incrementar los valores de plantilla de los pixels sombreados. • Hacemos el pop de la pila y ajustamos el test del buffer de plantilla de tal manera que solo se evalúen aquellos pixels cu yo buffer de plantilla no ha sido incrementado. • Debemos de alguna manera hacer un swap de estos valores del buffer plantilla, ya que en la siguiente pasada del bucle vamos a resetear estos valores. Mediante este algoritmo, podemos renderizar las sombras de múltiples objetos sobre superficies planas, pero no sobre otros objetos. Sombreado de volúmenes utilizando el buffer plantilla.
Para realizar el sombreado de volúmenes con el buffer plantilla, en primer lugar debemos escoger otros métodos de proyectar las sombras, como por ejemplo proyectar las mismas como volúmenes. Modelar una sombra como un volumen puede resultar a priori imposible dado que la sombra proyectada por un cuerpo es infinita, pero como lo que a nosotros nos interesa son las intersecciones de esa sombra con los objetos y con nuestro campo de visión, eso si que compone una superficie finita. Por otra parte, nosotros para representar los volúmenes, nosotros estábamos utilizando composiciones poligonales para representar su superficie, por lo que queda bastante claro que las sombras las vamos a representar de la misma forma. Con las sombras representadas de esta forma, podemos utilizar el buffer plantillla para determinar qué pixels y cuales no quedan dentro del volumen de la sombra. En primer lugar lo que haremos será resetear todos los búferes, activar los focos de luz y renderizar la escena para así poder calcular los valores de profundidad, determinando los objetos más cercanos y, por lo tanto, visibles. El segundo paso consiste en determinar cuales van a ser los pixeles que estarán dentro de la sombra, para lo cual, en primer lugar pondremos a uno el bit menos significativo del buffer plantilla, a continuación pasaremos el test de profundidad, invirtiendo el valor del buffer en aquellos pixeles en los que se pasa el test, es decir, los visibles, quedando el bit menos significativo del buffer plantilla a cero. En este momento renderizamos el volumen de la sombra de tal forma que cada vez que se renderice un polígono de la sombra sobre un píxel se va a incrementar en uno el valor del buffer plantilla. No debemos olvidar dibujar también 53
Luces y lámparas las caras ocultas para así poder conseguir determinar basándonos en la paridad del valor del buffer plantilla, si un píxel está dentro o fuera de este volumen y, por tanto si ha de ser sombreado o no. El valor par del buffer plantilla corresponderá a un píxel no sombreado si el punto de vista está fuera de la sombra, y viceversa si está dentro.
12. APÉNDICE A 12.1. EFECTOS DE NIEBLA La niebla es un efecto especial utilizado para difuminar una escena dando la impresión de que se halla realmente cubierta por una masa de niebla. En general consiste en difuminar el color de la escena, oscureciendo los objetos más lejanos llegando incluso a desaparecer, mientras que los objetos más cercanos aparecen mucho más claros. Es un efecto interesante, ya que aparte de añadir realismo a la escena nos ayuda a reducir su complejidad ya que los objetos más lejanos que aparecen ocultos por la niebla no es necesario renderizarlos. El efecto niebla lo podemos aplicar de dos formas principalmene, basado en vértices o basado en tablas. La primera forma consiste en calcular el valor de atenuación de niebla para cada vértice de cada triángulo y luego interpolar para el resto del triángulo, y en el segundo caso mantendremos tablas con los valores de atenuación para cada píxel de la pantalla. En cualquier caso, habrá que calcular esos valores de atenuación, bien para los vértices de cada triángulo, bien para cada píxel. Como el efecto de atenuación va a depender de la distancia, habrá que calcular la distancia de la niebla. Para ello tenemos entre otras dos técnicas: basada en planos o basada en rangos, mediante las cuales determinamos los puntos que aparecerán sin niebla y los que aparecerán totalmente oscurecidos. En ambos casos tendremos dos límites a tener en cuenta: el límite de inicio de la niebla, de tal manera que a los objetos que se encuentren más cerca de ese límite no se le va a aplicar niebla; y el límite de final de la niebla, de tal manera que los objetos que se encuentren más allá de ese límite serán ocultados. Niebla basada en planos.
Esta técnica consiste principalmente en considerar tres planos: el plano de visión, el plano de inicio de la niebla y el de fin de la misma, todos ellos paralelos entre si, de tal manera que sólo se calculará la niebla para aquellos puntos situados entre los planos de inicio y fin de niebla, ya que los que están situados antes del plano de inicio no se les aplica niebla, y los que están más allá aparecen totalmente oscurecidos y no se renderizan. Pero esta técnica tiene sus inconvenientes, ya que si cambiamos de orientación el plano de visión, puntos que antes estaban en región de niebla ahora quedan fuera, y viceversa, todo ello sin variar la distancia. Las siguientes figuras lo ilustran claramente:
54
Luces y lámparas
Plano de visión inicial
Plano de visión rota 90º a la izda Figura 8.41
Niebla basada en rangos.
Esta técnica surge para eliminar el problema anteriormente descrito, y consiste en calcular los umbrales de niebla de forma radial desde el punto de vista, y no de forma lineal como se hacía en la técnica anterior, de tal manera que nos queda un aro de niebla en torno al punto de vista tal y como se muestra en la figura:
Figura 8.42
La desventaja de esta técnica es su mayor coste computacional y que no puede ser utilizada si estamos mapeando la niebla en tablas de niebla, hay que usarla con niebla basada en vértices.
55
Luces y lámparas Por otro lado tenemos que calcular el factor de atenuación para los puntos que se encuentran dentro del área de niebla. Este factor lo podemos calcular de forma lineal o de forma exponencial. Atenuación lineal. Depende de los umbrales de inicio y final de niebla y se calcula aplicando la siguiente fórmula: niebla inicio- niebla fin f= observ dist- niebla fin De esta manera interpolamos los valores de atenuación entre los umbrales de inicio y final de niebla. Atenuación exponencial. Este tipo de atenuación se utiliza para producir transiciones muy rápidas o intensas del valor de atenuación. Tenemos dos posibilidades: 1 1 f= o bien: f = e(
densidad dist)
e(
densidad dist 2
Con la primera fórmula conseguimos una atenuación rápida, dependiendo del valor que le demos al factor de densidad, y en el segundo caso producimos una atenuación más intensa dependiendo también del factor de densidad que le demos. En ninguno de los dos casos la atenuación depende de los umbrales de inicio y fin de la niebla. De esta forma ya tenemos como calcular los valores de atenuación para cada vértice y luego poder interpolar para el resto del triángulo si estamos aplicando la niebla basada en vértices; o bien tenemos el método para calcular la atenuación correspondiente a cada píxel en el caso de utilizar tablas de niebla. Nota: Si estamos aplicando niebla basada en vértices, se nos puede dar el caso de que uno de los vértices esté dentro de la zona visible y otro fuera, muy alejado del umbral de fin de niebla, de tal manera que al interpolar nos aparezcan como visibles zonas del objeto que, por distancia, deberían aparecer ocultas por la niebla, tal y como se muestra en la figura:
56
Luces y lámparas
Figura 8.43
Para solucionarlo, podemos crear nuevos vértices haciéndolos coincidir con los umbrales de inicio y fin de niebla.
13. CONCLUSIONES En una primera parte de este capitulo hemos explicado como actúa la luz al chocar contra un objeto y como OpenGL simula este comportamiento de la luz. Para ello, hemos hablado de cómo esta formada la luz, que modelos de iluminación existen, que técnicas de ocultación de superficies se suelen usar, que técnicas de sombreado son las mas usadas. También hemos hablado de como se comporta la luz ante los diferentes materiales de los que pueden estar compuestos un objeto. Todo esto lo hemos explicado de una forma teórica para después ver como OpenGL hace uso de todo eso de una forma muy sencilla. Hemos nombrado todas las funciones que usa OpenGL para activar la iluminación en una escena, para representar un foco de luz, para indicar el tipo de material de un objeto y otras funciones para crear efectos muy interesantes relacionados con la iluminación. En la segunda parte de este capítulo hemos hablado de algunas de las principales técnicas que hay para aplicar una iluminación que dote de realismo a nuestras escenas. No hemos entrado en demasiado detalle al hablar sobre las mismas ya que sobrepasa el carácter introductorio de este capítulo a lo que es la iluminación de escenas en OpenGL. Algunas de ellas como el Cube Environment Mapping no son en si una técnica de iluminación, sino más bien un medio a través del cual aplicar otras como la BRDF. Otras, como el Promedio Normal nos hablan de cómo jugar con la iluminación para ofrecer efectos ópticos interesantes. En general, el capítulo de iluminación es uno de los más interesantes de la programación gráfica, porque es uno de los que más nos va a permitir dotar de fotorrealismo a las escenas que estemos modelando, pero exige un gran dominio del resto de temas como el manejo de texturas, perspectivas, buffers auxiliares, además de un profundo conocimiento matemático de todo el tema de geografía descriptiva, cálculo de matrices, etc. Para profundizar más en este tema de efectos de iluminación se pueden ver algunos ejemplos que hemos escogido de los que hay disponibles en la red, así como algunos muy sencillos que hemos modificado para demostrar alguno de los efectos de los que estamos hablando. Por último hemos incluido un apéndice comentando un poco por encima en qué 57
Luces y lámparas consiste y las diferentes técnicas utilizadas para aplicar la niebla, ya que es un efecto muy a tener en cuenta ya que del mismo modo que la niebla difumina las figuras más cuanto más lejanas están, también atenúa la iluminación y los efectos que conlleva, es decir, sombras, focos de luz, etc.
58