8. Iluminacion |
![]() |
![]() |
Videojuegos - Curso de Programación de juegos | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Escrito por Vicengetorix | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
![]() Lo primero es lo primero. La funcion para habilitar la iluminacion en OpenGL es glEnable(GL_LIGHTING), siguiendo el estilo de hacer las cosas habitual. Antes de esta funcion se pinta solo el color del pixel segun el relleno del poligono, sea color plano o una textura. A partir de esta funcion OpenGL ademas tendra que hacer calculos, partiendo de este color base, teniendo en cuenta las fuentes de luz, su posicion, sus componentes, y el vector normal a la superficie del objeto.... tranquilos, OpenGL hace todos los calculos (para eso esta), pero nosotros entenderemos que hace para poder usarlo a nuestra conveniencia. A partir de habilitar la iluminacion, si no hacemos mas, veremos como nuestro programa pasa a verse en negro. Eso es porque, como es logico, donde no hay luz no se ve. OpenGL estara haciendo sus calculos pero de momento no hay luces encendidas y el resultado es oscuridad. ¿logico no?. Para encender una luz basta con la funcion glEnable(GL_LIGHT0) que con la logica habitual de OpenGL, lo que hace es encender la luz 0. OpenGL dispone de 8 luces, de la 0 a la 7. Al habilitar la luz 0 se encendera con valores por defecto como la posicion (0,0,0). Cuando habilitemos la luz 0 en nuestro programa y no hacemos mas cosas, veremos de nuevo como tampoco se vera mucho mas claro. Tal vez se adivine la forma del cubo pero nada mas. Eso se debe a que a OpenGL le falta un dato basico para calcular el color, iluminado por nuestra luz 0, los vectores normales a la superficie de nuestro cubo (a partir de ahora "las normales"). Sin estos vectores, no sabe como rebota la luz en la superficie y no puede hacer el calculo. Veremos ahora pues, las normales: ![]() Como se ve en el dibujo, el vector N es el vector normal (es la normal) del vertice P0 del triangulo. La normal es perpendicular a la superficie del triangulo y hacia fuera del objeto. Ademas OpenGL exige que sea un vector de longitud 1 para hacer el calculo correctamente. Se puede habilitar una funcion de OpenGL que convierte en unitarios (longitud 1) las normales, es glEnable(GL_NORMALIZE), pero nosotros no la vamos a usar porque consume tiempo de computo y porque no lo necesitamos. Una vez definidas las normales de los vertices de nuestro cubo, si que se vera nuestro objeto. Asi pues, el proceso sera:
Para OpenGL existen 3 componentes de la luz y son: Ambiente: La luz que llega rebotada de las paredes, los muebles, ... no se sabe de donde viene. Es una luz mas debil que las otras: ![]() Difusa: La luz que llega directamente desde la fuente de luz pero rebota en todas direcciones (junto con la ambiente definen el color del objeto): ![]() Especular: La luz que llega directamente de la fuente de luz y rebota en una direccion, segun la normal de la superficie (provoca el brillo, por ejemplo, del circulito blanco de la bola de billar negra): ![]() Cada una de estas componentes de la luz se pueden definir para cada fuente de luz de OpenGL, de forma que podemos definir una luz con bombilla de color verde o rojo, o con mas o menos intensidad. Estos parametros de la fuente de luz se definen con la funcion glLightfv. Veamos un ejemplo:
Aqui vemos como definimos los valores de luz ambiente en una matriz de 4 valores, los 3 primeros son las componentes RGB del color en un rango de 0.0 a 1.0 (con punto por que son float's) y el cuarto sera 1.0 (recordad las coordenadas homogeneas pero aplicado a los colores). Luego en la funcion glLightfv le indico como primer parametro que la funcion es para modificar los valores de la luz 0 ( GL_LIGHT0, GL_LIGHT1, ..., GL_LIGHT7 son las 8 luces de OpenGL). Como segundo parametro le digo que voy a modificar la componente de luz ambiente de esa fuente de luz (GL_AMBIENT, mas adelante veremos mas parametros), y como tercer parametro le paso el puntero a la matriz que contiene los valores de tipo float (de ahi la f -float- del nombre de la funcion y la ultima v de glLightfv -vector-) De forma analoga modificare los demas parametros de una luz. En este cuadro se ven las posibilidades del segundo parametro de glLight y sus valores por defecto: ![]() Esto nos lleva a ver los tipos de luces en OpenGL, que son tres:
![]() El parametro GL_SPOT_EXPONENT se usara solo en caso de ser una luz focal y controla si la luz se concentra mas en el centro del cono de luz o se distribuye. El vector de direccion del foco se indica en GL_SPOT_DIRECTION (estos valores se tienen en cuenta solo si es luz focal -GL_SPOT_CUTOFF no 180.0-) Los tres ultimas opciones del cuadro, son para controlar la atenuacion de la luz, o como la luz ilumina menos cuanto mas lejos esta. De momento las dejaremos como estan (tu si que puedes probar valores si quieres, como en todo). Llegado a este punto pensamos que la iluminacion no tiene secretos para nosotros pero... ¡sorpresa! falta la parte de como el material de nuestro objeto refleja la luz. Ya vimos la necesidad de definir normales a la superficie de nuestro objeto pero no vimos como el material con el que esta "hecho" refleja cada componente de la luz que le llega. Esto se hace con la funcion glMaterial:
Al igual que con los componentes de la fuente de luz, hemos guardado los 4 valores en coma flotante en una matriz. Estos 4 componentes determinan como refleja la superficie esta componente de la luz (las cuatro 0.0 sera que no refleja y se vera negro, otras combinaciones haran la superficie de otros colores -RGB- ) . El primer parametro indica a que caras del objeto se aplica, GL_FRONT, GL_BACK y GL_FRONT_AND_BACK. La de delante, la de atras o las dos. Si es un objeto cerrado, con la de delante basta. El segundo parametro indica la caracteristica del material a cambiar. Puede ser GL_AMBIENT, GL_DIFFUSE y GL_SPECULAR para cada componente de la luz en OpenGL. GL_AMBIENT_AND_DIFFUSE para cambiar los valores de luz ambiente y difusa a la vez (son las que determinan el color del objeto). GL_SHININESS es un parametro que modifica la forma de presentar la luz especular, representa el brillo del objeto y es un valor unico de 0 a 255. Por ultimo GL_EMISSION representa la luz que emite el propio objeto y que normalmente sera 0,0,0 para objetos normales pero, por ejemplo, para representar una bombilla pondriamos una esfera en el mismo lugar que una luz puntual y dariamos valor a este parametro. Merece la pena hacer un resumen de estas opciones de la funcion glMaterial, ya que seran muy usadas y combinadas podremos emular materiales como el oro, plastico brillante o mate, ...:
El tercer parametro sera el puntero a los valores para esa caracteristica del material (en el caso de GL_SHININESS no sera un puntero sino el valor derectamente). Solo queda por ver algunas funciones que modifican la iluminacion de forma general con glLightModel:
Este parametro modifica la forma de hacer el calculo del brillo de luz especular. En el primero (GL_TRUE) usara para este calculo un punto fijo del mundo OpenGL con lo que el calculo sera mas lento pero mejor el resultado. En el segundo (GL_FALSE) tomara un punto en el infinito para el calculo, lo que es lo mismo que una direccion. Sera menos costoso pero menos vistoso.
Este permite que OpenGL dibuje los poligonos por los dos lados correctamente invirtiendo las normales en caso de verlos desde el otro lado, si lo habilitamos (GL_TRUE). En objetos cerrados nunca veremos la cara interior y no sera necesario.
Este ejemplo nos permite habilitar una luz ambiente general para todo lo que dibujemos, de forma que se vera con cierta claridad general. Con todo lo que sabemos, ahora entenderemos mejor la funcion glShadeModel que ya hemos usado, en realidad sin mucha utilidad hasta ahora. Esta funcion tiene un parametro que puede ser:
En un cubo el tipo de Shademodel no se notara porque cada vertice de una cara tiene la misma normal que las otras y el mismo tono, atendiendo a la luz que le llega, pero una esfera es otra cosa y la diferencia sera patente. Una imagen vale mas que mil palabras:
Ahora lo que apetece es poner manos a la obra y empezar a incluir iluminacion a nuestro programa. Aprovecho para recordar que la mejor forma de aprender a cocinar es mancharse de harina y huevo, asi que para esto, lo mejor sera que cambiemos de mil formas nuestro programa para ver que hace con cada cambio. Este capitulo es particularmente propicio a probar y ver que pasa. Tambien aprovecho para colgar en la zona de descargas las librerias de OpenGL que uso con Visual C++ 2005 Express, por si a alguien le hacen falta (incluida glaux.lib y su cabecera). Estas son: LibOpenGLvc2005.zip Antes de nada vamos a incluir la libreria GLAux a nuestro programa para poder hacer uso de los objetos predefinidos que trae, ya que no podemos de momento cargar nuestro propios modelos. Es una forma de tener rapidamente objetos para ir probando:
Lo primero sera introducir fuera de toda funcion (declarar como global) algunas matrices con datos que vamos a usar mas tarde:
Despues añadiremos cosas a la funcion IniciaGL(), al final para no liarnos. Modificamos la posicion de la luz 0 de OpenGL (GL_LIGHT0). Para ello definimos una matriz con los datos y se la pasamos a la funcion glLightfv con la opcion GL_POSITION:
Como pone en el comentario, la cuarta coordenada de la posicion es un 1.0, lo que quiere decir que es un punto de luz. Lo situamos un poco por encima de los objetos. Cambiando la posicion de la luz, la escena cambia totalmente. Pruebalo. Habilitamos la iluminacion y encendemos la luz 0 (como poner el interuptor en ON).
Definimos las componentes de la luz emitida por nuestra fuente de luz (0) pasando los punteros de las matrices que definimos al principio del codigo, para almacenar estos datos.
Por ahora dejaremos asi definida nuestra bombilla. Es una luz puntual y la hemos puesto el color, la intensidad y la posicion que hemos querido. Despues, como siempre, nos vamos a la funcion Pinta(). En un punto, antes de pintar nuestro cubo, definimos el material con el que se pintara. En este momento pensaras ¿para que si le habiamos puesto una textura?. Tiene su explicacion. En la definicion de la textura pusimos el parametro GL_DECAL (en la funcion IniciaGL() ): glTexEnvf(GL_TEXTURE_2D,GL_TEXTURE_ENV_MODE,GL_DECAL); y ahora lo cambiaremos a GL_MODULATE: glTexEnvf(GL_TEXTURE_2D,GL_TEXTURE_ENV_MODE,GL_MODULATE); Con esto conseguimos que el color e intensidad del material afecte a como se aplica la textura y asi el efecto de la iluminacion sera completo. Podremos hacer el objeto mas brillante o menos aunque este con textura, o darle color y asi mezclarlo con la textura. Asi pues, definimos el material del cubo:
Despues nos vamos a la definicion de los vertices y añadimos las normales de cada vertice o de cada cara.
Este codigo merece una explicacion mas detallada. El unico cambio respecto a anteriores programas es la inclusion de buen numero de funciones glNormal3f(...) , la funcion que usaremos para definir las normales (las que explicamos al principio del capitulo). El calculo de las normales (no el que hace OpenGL para la iluminacion, si no el nuestro para escribirlas en el codigo), es en el caso del cubo es trivial. Por ejemplo la cara frontal, mirando hacia nosotros, solo tendra una componente en el eje z de 1 y positiva (0.0, 0.0, 1.0). Asi de facil conseguimos un vector perpendicular a la superficie, hacia fuera del objeto y de 1 de longitud (normalizado). Cuando poner la direccion correcta de la normal, y ademas de modulo (longitud) 1, no sea tan facil, tendremos que programarnos un calculo o cargarlas con nuestros modelos. Con el cubo, de momento, podemos. Observad como en algunas caras del cubo hemos puesto una funcion glNormal en antes de cada vertice, y en otras hemos puesto una funcion glNormal por cara, antes de los vertices de esa cara. En un cubo las 4 normales de los vertices de una cara son iguales, con lo que al pasar a OpenGL una normal, la usara hasta que le pasemos otra. Ahora tenemos una idea clara de los datos que son necesarios para definir un vertice:
LLega el momento de usar la libreria GLAux que hemos cargado al principio. Vamos a pintar con ella una esfera sin textura y con el segundo material que habiamos guardado en matrices globales (material amarillo). Para ello seguiremos estos pasos.
Para terminar de pintal nuestra escena ponemos el cartel pero esta vez un poco mas pequeño, y como hemos añadido iluminacion, la desabilitaremos para que el cartel se vea tal y como lo hemos cargado de disco.
Tambien habilito la aplicacion de texturas que habia desabilitado para pintar la esfera antes de pintar el cartel:
Y un poco mas pequeño:
Despues habilito de nuevo la iluminacion, que sera la situacion normal en el programa:
Por fin, depues de todo este trabajo, solo queda compilar y ejecutar para disfrutar de nuestra nueva escena con la iluminacion de una bombilla (luz puntual) sobre los dos objetos: ![]() El codigo completo hasta este punto sera: usw8a.cpp Pero como el lector atento habra notado, no estaria completo este manual sin comprobar como quedaria nuestra escena con los otros dos tipos de luz alumbrandola, asi que vamos a probar los dos. El segundo tipo que vamos a probar es la luz direccional y para ello solo hemos de cambiar los parametros de posicion de la luz 0. Ahora el cuarto componente de la posicion sera un 0.0 para indicarle a OpenGL que se trata de una luz direccional. Los tres primeros componentes ya no determinan la posicion de la luz en el espacio, si no el vector que indica la direccion hacia donde esta la fuente de luz que nos ilumina. En nuestro caso la luz vendra de arriba:
Y el resultado sera este: ![]() El codigo completo hasta este punto sera: usw8b.cpp Solo queda probar la luz de un foco. Tendremos que volver a colocar la luz en el espacio de nuestra escena y en el cuarto componente de la posicion poner un 1.0. Pondremos en el parametro GL_SPOT_CUTOFF un valos distinto a 180, que sera la apertura del cono de luz en grados. Terminaremos definiendo la direccion hacia la que ilumina el foco con el parametro GL_SPOT_DIRECTION de la funcion glLightfv. El codigo para definir la luz sera asi:
Y el resultado sera este: ![]() El codigo completo hasta este punto sera: usw8c.cpp Una sugerencia a modo de practicas de este capitulo es que proveis a poner mas luces y ver como queda. En el proximo capitulo intentaremos mover un poco la escena, que de momento esta muy parada. ¡Sólo los usuarios registrados pueden escribir comentarios!
3.26 Copyright (C) 2008 Compojoom.com / Copyright (C) 2007 Alain Georgette / Copyright (C) 2006 Frantisek Hliva. All rights reserved." |