Domingo, 25 de octubre de 2009
El presente artículo viene a ser una continuación del anterior. Por eso no vamos a explicar de nuevo la teoría sobre iluminación. En este artículo vamos a continuar el ejemplo que vimos en el primer artículo, añadiéndole la característica de la iluminación y que afectará a como ser verán las esferas. El aspecto del ejemplo en ejecución sería algo como:


Captura de pantalla de la ejecución del ejemplo de este artículo


   Lo primero que observamos sobre el ejemplo del cubo del primer artículo, es que ahora las esferas tienen colores diferentes. Estos colores han sido aplicados mediante las funciones openGL que definen los materiales. Solo hay fuente de iluminación direccional que viene desde arriba en la dirección del vector (1, 1, 1)., y  que causa sobre las esferas que éstas tengan más brillo en su cara superior que en su cara inferior. Esta iluminación no le afecta al cubo que se renderiza de la misma forma que en el ejemplo del primer artículo. Aunque si se ha introducido como novedad que ahora el cubo tambien pueda ir girando sobre sus ejes x e y.

   El código correspondiente a la clase que representa una esfera, sigue prácticamente igual. La novedad es que ahora en su constructor se acepta un parámetro más, que es el radio que va tener la esfera(aunque en este ejemplo, todas tienen el mismo radio) y además se genera el color de la esfera de manera aleatoria. A continuación se muestra el código del método constructor de la clase Esfera.java.

public Esfera(float maxpos, float speed, float radio) {
            
      this.maxpos = maxpos;
      this.speed = speed;
      this.radio = radio;
   
      Random r = new Random();
      //if (!pass) {
//Math.random(time(NULL));
         pass = true;         
      //   r.setSeed(new java.util.Date().getTime());
     // }
            
      pos[0] = (float) ((r.nextFloat() % (int) maxpos) - maxpos / 2);
      pos[1] = (float) ((r.nextFloat() % (int) maxpos) - maxpos / 2);
      pos[2] = (float) ((r.nextFloat() % (int) maxpos) - maxpos / 2);
 
      dir[0] = (float) r.nextFloat();
      dir[1] = (float) r.nextFloat();
      dir[2] = (float) r.nextFloat();  
    
      float dirmod = (float) Math.sqrt(dir[0] * dir[0] + dir[1] * dir[1] + dir[2] * dir[2]);

      dir[0] /= dirmod;
      dir[1] /= dirmod;
      dir[2] /= dirmod;
      dir[0] *= speed;
      dir[1] *= speed;
      dir[2] *= speed;
 
      // generamos el color de la esfera aleatoriamente.   
      color[0] = (float) (r.nextFloat());
      color[1] = (float) (r.nextFloat());   
      color[2] = (float) (r.nextFloat());
      color[3] = (float)1.0;

   }


   El método java nextFloat genera un número aleatorio entre 0 y 1. Como este rango es precisamente el que maneja las funciones openGL para los colores, no tenemos
que hacer ninguna conversión, como podemos ver en las últimas líneas del constructor.

  Veamos ahora la clase principal RebotesIluminacionEsferas.java, y concretamente su método de inicialización, donde definimos la característica de la luz, la perspectiva y posición de la cámara e inicializamos las posiciones de las esferas.

public void init() {
            
      angle_x = (float)0.5;
      angle_y = (float)0.5;
            
      // definimos la posición y características de la luz      
      GL11.glLight(GL11.GL_LIGHT0, GL11.GL_AMBIENT, FloatBuffer.wrap(light_ambient));      
      GL11.glLight(GL11.GL_LIGHT0, GL11.GL_DIFFUSE, FloatBuffer.wrap(light_color));
      GL11.glLight(GL11.GL_LIGHT0, GL11.GL_SPECULAR, FloatBuffer.wrap(light_color));      
      GL11.glLight(GL11.GL_LIGHT0, GL11.GL_POSITION, FloatBuffer.wrap(light_pos));      
      GL11.glEnable(GL11.GL_LIGHT0);

      
      // activamos el test de profundidad, inicializamos las matrices de proyección y del modelo de vista,
      // ajustamos el ángulo de perspectiva
      GL11.glEnable(GL11.GL_DEPTH_TEST);
      GL11.glClearColor((float) 0.0, (float) 0.0, (float) 0.0, (float) 0.0);
      GL11.glMatrixMode(GL11.GL_PROJECTION);
      GL11.glLoadIdentity();
      GLU.gluPerspective((float) 55.0, (float) 1.0, (float) 1.0, (float) 100.0);

      GL11.glMatrixMode(GL11.GL_MODELVIEW);
      
      // determinamos la posición de la cámara
      GLU.gluLookAt((float) 2.0, (float) 2.0, (float) 2.5, (float) 0, (float) 0, (float) 0, (float) 0, (float) 1, (float) 0);
            
      // inicializamos las bolas, y ...
      sphere = new Esfera[NUM_BOLAS];
      java.util.Random srand = new java.util.Random();
            
      // ...las colocamos dentro del cubo con velocidades iniciales aleatorias y colores aleatorios
      for (int i = 0; i < NUM_BOLAS; i++) {
         sphere[i] = new Esfera(1, ((srand.nextFloat() % 10) / (float)20) + (float)0.1, (float)0.05);       
      }
            
   }


    En las líneas donde definimos las características de la luz, estamos definiendo la luz difusa, la especular y la ambiental, como la luz blanca. Fijate en que los vectores
light_color y light_ambient tienen los mismos valores. Luego definimos la posición de la fuente de iluminación en el vector light_pos que tiene los valores (1, 1, 1, 0).
el valor 0 es para indicar que es una luz direccional.
    El resto de líneas siguen igual que en el ejemplo del primer artículo, salvo por el último parámetro al constructor de cada esfera, para pasar el valor del radio. Cada esfera tendrá el mismo radio de valor 0.05. Aquí eres libre de experimentar y configurar el radio de las esferas a tu gusto.

   Y vamos ya a explicar el método más importante, el que renderiza la escena.

public void display() {
           
      // al cubo no le debe afectar el sistema de iluminación
      GL11.glDisable(GL11.GL_LIGHTING);

     
      // limpiamos la pantalla
      GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
      // establecemos el color blanco para dibujar
      GL11.glColor3f((float)1.0, (float)1.0, (float)1.0);
      // rotamos el cubo en angulo que corresponda en el eje x e y
      GL11.glRotatef(angle_x, 1.0f, 0.0f, 0.0f);
      GL11.glRotatef(angle_y, 0.0f, 1.0f, 0.0f);
      // y dibujamos el cubo
      drawCube();
      
      // se van a dibujar las esferas. Volvemos a activar la iluminación
      GL11.glEnable(GL11.GL_LIGHTING);    
 
      // aplicamos las características del material para la luz ambiental y difusa
      GL11.glColorMaterial(GL11.GL_FRONT, GL11.GL_AMBIENT_AND_DIFFUSE);
      GL11.glEnable(GL11.GL_COLOR_MATERIAL);

      for (int i = 0; i < NUM_BOLAS; i++) {
         GL11.glPushMatrix();

         // aplicamos el color del material a la esfera que se está dibujando
         GL11.glColor4f((sphere[i].getColor())[0], (sphere[i].getColor())[1], (sphere[i].getColor())[2], (sphere[i].getColor())[3]);
                                    
         pos = sphere[i].getPosV();
         // movemos la esfera a la nueva posición que le corresponda
         GL11.glTranslatef((float)pos[0], (float)pos[1], (float)pos[2]);         
         
         Sphere bolita = new Sphere();      
         bolita.setDrawStyle(GLU.GLU_FILL);
         bolita.draw((float)sphere[i].getRadio(), 20, 20);                  
         
         GL11.glPopMatrix();           
                        
         // comprobamos si la bola choca con alguna cara del cubo, y si es así, hacemos que su dirección cambie, y actualizamos su nueva posición
         sphere[i].test();                              
      }
      GL11.glDisable(GL11.GL_COLOR_MATERIAL);
      
      try {
         Thread.sleep(30);
      } catch (InterruptedException e) {
         
      }
      
   }   


   Como observamos, lo primero que hacemos es desactivar la iluminación, ya que el cubo se tiene que renderizar sin el sistema de iluminación. Es decir, se le aplica la función de color standar, para dibujar sus lineas de color blanco. Luego se aplica la rotación que le corresponda, y finalmente se dibuja el cubo en el método "drawCube".
Una vez dibujado el cubo, volvemos a activar el sistema de iluminación, y activamos también el color del material, indicando que le afectará las luces ambiental y difusa. Y ya solo quedaría ir renderizando cada esfera con el color del material que le corresponda y la posición.

   Si ejecutas este ejemplo en tu sistema deberías ver algo como esto:

 

 

      Una buena práctica sería implementar la posibilidad de que las bolas también puedan chocar entre si. Hay varias formas de hacerlo. Una forma que se me ocurre y que es eficiente en cuanto a velocidad, ya que no hay que recorrer todo el array de esferas cada vez que se quiere chequear si una esfera ha chocado con alguna otra, es utilizar una estructura de tabla hash, donde las claves sean la posición de las esferas y los valores los objetos Esfera. Entonces en el test que se comprueba si una esfera ha chocado con alguna cara del cubo, también se chequearía(en caso que no halla chocado con una cara del cubo) si ha chocado con alguna otra esfera. Como sabemos la posición de la esfera y su radio, es fácil saber si está en colisión con otra esfera que esté muy cerca a ella en cuanto a posición(x, y, z), o dicho de una manera más técnica, si está invadiendo algún pixel de otra esfera. Bueno, te reto a que lo intentes. Si lo logras, puedas dejar tu comentario.

    Pues hasta aquí todo lo referente a la iluminación. En proximos artículos nos meteremos de lleno con las técnicas de renderizado de texturas sobre las superficies 3D.

 


Tags: lwjgl, programacion3D, openGL, luz, iluminación

Publicado por antoniopf @ 20:55  | Programaci?n 3D
Comentarios (1)  | Enviar
Comentarios

Gracias a este ejemplo(compiando en gran parte xD). Me he sacado una asignatura facilmente, asi que agradecimientos de este casi-ingeniero(aun no). Y por si te interesa, te pongo un enlace a las modificaciones que he hecho en los controles de camara que permiten manejar la camara como si se tratara de un "first-person". Por si asi te animas a hacer otro tutorial y le simplifico la vida un poco a alguien Muchas risas:

http://www.mediafire.com/?usey222cwhftxfh

Publicado por Invitado
Martes, 22 de febrero de 2011 | 18:41