Jueves, 22 de octubre de 2009
Hoy empezamos a tratar el tema de la iluminación 3D, y para entender un poco el código de ejemplo que se expondrá en este artículo, vamos a comenzar con un poco de teoría sobre la iluminación 3D y como se aplica en la programación. Como en el anterior capítulo, el código ha sido desarrollado con la librería lwjgl para java, basada en el standar openGL.

El standar openGL, define un número mínimo de luces de 8. Esto quiere decir, que podemos manejar en una escena 3D, un mínimo de 8 fuentes de luz diferentes. En el ejemplo que nos ocupa, solo necesitamos una.
El sistema de iluminación de openGL se basa en luces y materiales. Una luz es una fuente de iluminación para una escena, que emitirá un haz de luz de un color determinado, dividido en las 3 componentes de color RGB. Un material determinará la cantidad de cada componente de color que reflejará un objeto al recibir dicho haz de luz. Por ejemplo, si un objeto tiene un material de color rojo RGB(1, 0, 0), es decir, que refleja todo el color rojo y es iluminado por una luz blanca RGB(1, 1, 1), reflejará toda la componente de luz roja, pero nada de luz verde ni azul. Por lo tanto, el objeto se verá rojo. Si por el contrario, recibe una luz azul RGB(0, 0, 1), el objeto se verá de color negro, al no tener nada de componente roja que poder reflejar. Además, dependiendo del tipo de luz que incida sobre el objeto, el color final de éste, se verá afectado, por el ángulo de incidencia de la luz, la distancia a ésta, etc, ...

A partir de aquí para entender un poco mejor lo que queda de teoría, vamos a definir "fuente de luz", como una entidad(ejem: lampara) que proporciona iluminación a una escena, y "luz", como la aportación de esa fuente a la iluminación de la escena.

   Así pues, podemos decir, que una fuente de luz, puede emitir diferentes tipos de luz, que pueden complementarse entre sí. Los principales tipo de luz son los siguientes:

  • "Emitted"(emitida): es la luz emitida por un objeto. No se ve afectada por ningún otro tipo de luz. Por ejemplo, el fuego emite un determinado tipo de luz, y si lo miramos siempre lo veremos del mismo color, independientemente del color de las luces que estén apuntando al fuego. Incluso en la oscuridad total, el fuego seguirá viendose del mismo color.
  • "Diffuse"(difusa): Es la luz que incide sobre un objeto, desde un determinado punto del espacio. La intensidad con la que se refleja sobre el objeto, depende del ángulo de la luz, de la distancia, etc. Una vez que incide sobre el objeto, se refleja en todas direcciones.
  • "Specular"(Especular): Es la luz, que una vez que incide sobre un objeto, se ve reflejada con un ángulo similar al de incidencia. Se podría decir, que es la luz que produce los brillos.
  • "Ambient"(ambiental): Iluminación global de la escena. Es imposible determinar su procedencia.

La iluminación en OpenGL, se calcula a nivel de vértice. Es decir, por cada vértice se calcula su color, a través del material activo, las luces activas, y como estas luces inciden sobre el vértice. Para saber como afecta la luz al vértice, empleamos un vector normal al vértice, que normalmente será perpendicular a la cara que estemos dibujando, aunque se podrá variar, para conseguir distintos efectos.

LUCES

Veamos primero la configuración de las luces. Como habíamos dicho anteriormente, la implementación debe poder manejar un mínimo de 8 luces. Cada luz se identifica por una constante del tipo GL_LIGHTn, donde n indica el número de luz. Para utilizar las luces, se tiene primero que activar la iluminación. Esto se hará con la función glEnable(GL_LINGHTING).
Para definir las características de las luces tenemos disponibles varias funciones, que dependerán de las propiedades de las luces que queramos activar. Estas propiedades son:

  • Posición/Dirección: Indica la posición y dirección de la luz. Una luz puede ser posicional o direccional.
    • Luz posicional: Tiene una posición concreta en el espacio. Un ejemplo de luz posicional sería una lámpara.
      • Luces puntuales: que emiten luz a su alrededor de manera radial y en todas direcciones.
      • Luces focales: emiten luz en una dirección concreta, y cuyo radio de acción tiene forma de cono, como un foco.
    • Luz direccional: Conjunto de haces de luz paralelos. Un ejemplo sería el sol.
                 
                Para establecer esta propiedad utilizaremos la función glLight(GL_LIGHTn, GL_POSITION, val_ptr)  donde val_ptr es un vector del tipo (x, y, z, w). En el      
                 caso  de que w = 1 estaremos ante una luz posicional. Y su posición vendrá dada por (x,y,z). En el caso de que w = 0, tenemos una luz direccional, y  
                 su dirección vendrá dado por (x,y,z)
  • Luces focales: es una luz posicional, cuya posición se establece con la función glLight(GL_LIGHTn, GL_POSITION, val_ptr).
    • La dirección del foco se establece con la función glLight(GL_LIGHTn, GL_SPOT_DIRECCION, val_ptr) done val_ptr es un vector del tipo (x,y,z) que indicará la dirección del foco.
    • Apertura del foco: se define mediante la función glLight(GL_LIGHTn, GL_SPOT_CUTOFF, val) donde val expresa la mitad del ángulo de apertura del foco.
    • Atenuación del foco: es la degradación de la intensidad de la luz, a medida que nos acercamos al borde de la frontera de iluminación del foco. Se define mediante la función glLight(GL_LIGHTn, GL_SPOT_EXPONENT, val).
  • Intensidad de la Luz: Define el color Ambiental, difuso y especular de la luz. Se define mediante la función
            glLight(GL_LIGHTn, GL_[AMBIENT | DIFFUSE | SPECULAR], val_ptr)
                donde val_ptr es un vector de 4 componentes de color RGBA.

  • Atenuación de la luz: Es la pérdida de la intensidad de la luz a medida que nos alejamos del foco. No afecta a las luces direccionales. Se establece mediante la función   glLight(LIGHTn, GL_[CONSTANT | LINEAR | QUADRATIC]_ATTENUATION, val)    

 Una vez establecidas las propiedades de una luz, estarán activas mediante la funcion glEnable(LIGHTn)

MATERIALES

Una vez definidas las luces, antes de dibujar el objeto, debemos definir su material. Para esto, al igual que se hizo para las luces, hay que configurar las propiedades del material. Se utilizaran funciones del tipo glMaterial*(), que reciben 3 parámetros de entrada:
  1. Caras del polígono a las que se aplica el material. Cara frontal (GL_FRONT) o a ambas caras (GL_FRONT_AND_BACK)
  2. Característica que defininimos mediante su correspondiente constante.
  3. Valor de la característica.
Las diferentes características son:
  • Color del material: Define el comportamiento del material para los distintos tipos de luz descritos anteriormente. Se utiliza para esto la función
    glMaterial(GL_FONT[_AND_BACK], [GL_AMBIENT | GL_DIFFUSE | GL_AMBIENT_AND_DIFFUSE | GL_SPECULAR], val_ptr) 
              Es decir, el material reflejará un color RGBA determinado definido mediante el vector val_ptr, cuando recibe un tipo de luz que podrá ser luz ambiental, o luz difusa, o luz ambiental y difusa o luz especular. La luz ambiental y difusa suelen definirse juntas, de ahi la constate GL_AMBIENT_AND_DIFFUSE.

  • Brillo de los reflejos: la intensidad del brillo se define mediante la función glMaterialf(GL_FRONT[_AND_BACK], GL_SHININESS, val)  
                el parámetro "val" es un valor entre 0 y 1

      La propiedad del "color del material" define el comportamiento del material en general, para los distintos tipos de luz, pero si queremos cambiar el comportamiento de un material específico para un determinado tipo de luz, lo debemos hacer mediante la función glColorMaterial(). Primero hay que activar esta función mediante la llamada glEnable(GL_COLOR_MATERIAL) y una vez activada, establecemos el tipo de luz :
                                           
                 glColorMaterial( GL_FRONT[_AND_BACK], [GL_AMBIENT | GL_DIFUSSE | GL_AMBIENT_AND_DIFUSSE | GL_SPECULAR] )

   y ahora cambiamos el color reflejado del material para el tipo de luz seleccionado, mediante la función:
                  glColor4f(r_val, g_val, b_val, val)

    Ahora dibujaríamos el polígono afectado por este material y a continuación hay que desactivar de nuevo esta propiedad : glDisable(GL_COLOR_MATERIAL)  


VEAMOS NUESTRO EJEMPLO

   El ejemplo que vamos a comentar se basa simplemente en una esfera y un foco de luz, que va rotando alrededor de esta esfera. A medida que va rotando irá desplazando también la zona iluminada de la esfera. Pero como una imagen vale más que 1000 palabras, aqui teneis un video de la ejecución del ejemplo en mi sistema.

 

El código completo del ejemplo, lo podeis encontrar aqui. Es un solo fichero y una sola clase. Voy a comentar ahora los dos métodos principales donde se configuran los tipos de luces, y se hace el renderizado de la animación del foco.

  •    Configuración de las luces (método init())

public void init() {
   
      GL11.glEnable(GL11.GL_DEPTH_TEST);
      GL11.glClearColor((float)0.0, (float)0.0, (float)0.0, (float)0.0);
      
      GL11.glLightModeli(GL11.GL_LIGHT_MODEL_TWO_SIDE, GL11.GL_FALSE); // la cara trasera de los polígonos no se iluminan
      
      // establecemos las luces y sus características
      GL11.glEnable(GL11.GL_LIGHTING);
      GL11.glLight(GL11.GL_LIGHT0, GL11.GL_AMBIENT, FloatBuffer.wrap(light_ambient));      
      GL11.glLight(GL11.GL_LIGHT0, GL11.GL_DIFFUSE, FloatBuffer.wrap(light_diffuse_specular));
      GL11.glLight(GL11.GL_LIGHT0, GL11.GL_SPECULAR, FloatBuffer.wrap(light_diffuse_specular));
      GL11.glLightf(GL11.GL_LIGHT0, GL11.GL_SPOT_CUTOFF, spot_cutoff);
      GL11.glLightf(GL11.GL_LIGHT0, GL11.GL_SPOT_EXPONENT, spot_exponent);
      GL11.glEnable(GL11.GL_LIGHT0);
      
      // Establecemos el material de la esfera
      GL11.glMaterial(GL11.GL_FRONT, GL11.GL_AMBIENT_AND_DIFFUSE, FloatBuffer.wrap(mat_ambient_diffuse));
      GL11.glMaterial(GL11.GL_FRONT, GL11.GL_SPECULAR, FloatBuffer.wrap(mat_specular));
      GL11.glMaterialf(GL11.GL_FRONT, GL11.GL_SHININESS, mat_shininess);

      // establecemos la perspectiva y...
      GL11.glMatrixMode(GL11.GL_PROJECTION);
      GL11.glLoadIdentity();
      GLU.gluPerspective((float)60.0, (float)1.0, (float)1.0, (float)100.0);
      
      // ...trasladamos la cámara
      GL11.glMatrixMode(GL11.GL_MODELVIEW);
      GL11.glTranslatef((float)0.0, (float)0.0, (float)-5.0);     
      
   }

  
   Fijemonos en las dos partes principales de este método. Donde se establecen las características de las luces primero se activa la iluminación, luego se definen
las caracteristicas de la luz GL_LIGHT0 y por último se activa esta luz. La otra parte define las caracteísticas del material de la esfera.

  •   Renderizado de la escena (método drawScene)
public void drawScene() {
      
      // Una vez borrada la pantalla, rotamos la luz, la posicionamos, y establecemos su dirección,
      // que serán relativas a su rotación. Después, dibujamos un cono en la posición de la
      // luz. Este cono tendrá como color de emisión el definido como tal. El cambio lo
      // hacemos con glColorMaterial(). Una vez dibujado, deshacemos estas
      // transformaciones, dibujamos la esfera, dibujamos y cambiamos los buffers

      GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
      GL11.glPushMatrix();
         GL11.glRotatef((float)30.0, (float)0.0, (float)0.0, (float)1.0);
         GL11.glRotatef((float)rot_angle_y, (float)0.0, (float)1.0, (float)0.0);
         GL11.glRotatef((float)rot_angle_x, (float)1.0, (float)0.0, (float)0.0);
         GL11.glLight(GL11.GL_LIGHT0, GL11.GL_POSITION, FloatBuffer.wrap(light_pos));
         
         GL11.glLight(GL11.GL_LIGHT0, GL11.GL_SPOT_DIRECTION, FloatBuffer.wrap(spot_dir));
            GL11.glTranslatef(light_pos[0],light_pos[1],light_pos[2]);
            GL11.glColorMaterial(GL11.GL_FRONT, GL11.GL_EMISSION);
            GL11.glEnable(GL11.GL_COLOR_MATERIAL);
               GL11.glColor4f(focus_emission[0], focus_emission[1], focus_emission[2], focus_emission[3]);         
               Cylinder cyl = new Cylinder();
               cyl.draw((float)0.2, (float)0.0, (float)0.5, 7, 7);
               GL11.glColor4f(mat_emission[0], mat_emission[1], mat_emission[2], mat_emission[3]);
            GL11.glDisable(GL11.GL_COLOR_MATERIAL);
      GL11.glPopMatrix();
      Sphere esfera = new Sphere();
      esfera.setDrawStyle(GLU.GLU_FILL);
      esfera.draw((float)1.0, 20, 20);
             
   }


Como siempre, lo primero que se hace antes de redibujar la escena es limpiar la pantalla. A continuación, como lo que se va a mover es el foco, y no queremos que las operaciones que se hagan sobre este, afecten a la esfera en ninguna manera(no olvidemos que en openGL, todo se hace internamente con matrices, y las diferentes operaciones se van concatenando matriz tras matriz). Por eso se guarda primero el estado interno de las matrices, con la función glPushMatrix(). A continuación se define la rotación que va tomar el foco en cada uno de los ejes(x,y,z), con las funciones glRotate. Las variables rot_angle_y y rot_angle_x se van actualizando en método runLoop().
Luego con la functión  LGL11.glLight(GL11.GL_LIGHT0, GL11.GL_POSITION, FloatBuffer.wrap(light_pos))

   se define la posición de la luz. Fijaos que según el vector light_pos, la emisión de la luz parte del punto (0, 0, 2). Un poco más abajo, la función glTranslate también utiliza esta posición de la luz, para definir el factor de traslación del foco en el eje z. Vemos también como se utilizan las funciones glColorMaterial, para cambiar en este caso las propiedades del material para el foco que emite la luz. Luego se dibuja este foco, y finalmente se desactiva glColorMaterial, con la función GL11.glDisable(GL11.GL_COLOR_MATERIAL)

   Y finalmente se reestablece el estado de las matrices con glPopMatrix(), para a continuación dibujar la esfera. ¿Pero en que posición se dibuja la esfera? Si te fijas bien, al final del método init, tenemos la función
GL11.glTranslatef((float)0.0, (float)0.0, (float)-5.0);  Es decir, la matriz inicial con la que arranca el sistema "dice", que todo lo que se dibuje se aleje 5 puntos en el eje z. Como la posición inicial de referencia del sistema en openGL, es la (0, 0, 0) y esto es el centro de la pantalla, la esfera se dibujará en la posición centrada en pantalla (0, 0, -5). La posición (0, 0, 0) te la podrías imaginar como justo el punto intermedio entre tus dos ojos.

   Bueno, hasta aqui todo por hoy. En el próximo artículo, seguiremos un poco más con este tema de la iluminación, aplicándolo sobre el ejemplo que vimos en el primer artículo, de las esferas rebotando contra las caras de un cubo. ¡¡ No te lo pierdas !!
   




Tags: lwjgl, programación 3D, openGL, iluminación, luz, luces

Publicado por antoniopf @ 22:53  | Programaci?n 3D
Comentarios (1)  | Enviar
Comentarios
Con estos art?culos acercas de una forma clara y amena las 3d en la inform?tica a los ne?fitos en el tema. Enhorabuena y espero ver m?s art?culos en esta linea.
Publicado por NESTOR HERRERA
Viernes, 23 de octubre de 2009 | 22:33