Iluminación de objetos con OpenGL y Glut [Delphi]

Buenas noches!, después de un tiempo de ausencia y de duro trabajo en el tecnológico he decidido hacer esta entrada simple de iluminar figuras geométricas creadas con glut!. Para comenzar antes que nada debemos de tener 3 librerías que son: OpenGL, Geomerty y Glut. Las descargamos y las agregamos a nuestro proyecto (Ojo este post esta hecho usando Delphi 7). Primero creamos nuestro proyecto en Delphi 7 y lo guardamos, y ahi mismo copiaremos estas 3 librerias y las agregamos con el boton Add file to Project o shift + F11

Ademas glut necesita de 2 archivos mas! que estos solo deben de copiarse a la carpeta donde esta guardado el proyecto, descargalos de aqui y aqui

Una vez que hayamos agregado los Units, en nuestro unit1 o nuestro form en el “Uses” agregamos la libreria OpenGL y Glut para poder usarlas en ese mismo Unit.

Para comenzar declaramos la siguientes variables privadas:

  private
    { Private declarations }
    RC : HGLRC;
    Angle : Integer;

Y a nuestro form le creamos un evento  onCreate y agregamos lo siguiente (al igual que este post)

procedure TForm1.FormCreate(Sender: TObject);
begin
    //Primero creamos un contexto
    RC:=CreateRenderingContext(Canvas.Handle,[opDoubleBuffered],32,0);
end;

Asi de esta manera creamos un lugar donde dibujar, pero al igual como lo creamos debemos eliminarlo, asi en un evento onDestroy de nuestro form agregamos lo siguiente:

procedure TForm1.FormDestroy(Sender: TObject);
begin
    DestroyRenderingContext(RC); //Se libera el contexto
end;

Y al igual como en una entrada pasada, utilizaremos el evento onPaint del form para dibujar lo que nosotros queramos en nuestro form!, asi que en un evento onPaint agregamos lo siguiente

procedure TMainForm.FormPaint(Sender: TObject);
begin
   ActivateRenderingContext(Canvas.Handle,RC); // Hacer el contexto dibujable
  glClearColor(0.1,0.1,0.1,0); // Color de Fondo...
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT); // Borra el Fondo y Depth-Buffer
  glEnable(GL_DEPTH_TEST);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity;
 //Aqui podremos dibujar lo que queramos
  SwapBuffers(Canvas.Handle); //Copiar el Back Buffer en el canvas del formulario...
  DeactivateRenderingContext; //Libera el contexto...
end;

Y para terminar con la inicializacion de los graficos, crearemos un evento onResize, para al momento de cambiar el tamaño de nuestra ventana este se redimencione, este evento onResize se ejecuta en la creacion del form asi que este forma parte importante de la inicializacion de los graficos ( por si llegan pensar en no ponerlo, deben de ponerlo!!).

procedure TMainForm.FormResize(Sender: TObject);
begin
    // Cuando se cambia de tamaño hay que actualizar el puerto de visión...
  wglMakeCurrent(Canvas.handle,RC); // Otra manera de hacer el contexto dibujable
  glViewport(0,0,Width,Height); // Especificar un puerto de visión....
  glMatrixMode(GL_PROJECTION); // Activar matriz de proyección...
  glLoadIdentity; // Poner estado inicial...
  gluPerspective(35,Width/Height,1,100); // Especificar Perspectiva ...
  wglMakeCurrent(0,0); // Otra manera de liberar el contexto...
  Refresh; // Redibujar la escena ...
end;

Ya con este código puesto tendremos una ventana pintada de gris!, pero ya esta todo preparado para comenzar a dibujar =D!. Para eso nos vamos a ir al procedimiento onPaint y debajo del comentario “Aqui podremos dibujar lo que queramos” empezaremos a escribir!.
Primero con glut dibujaremos diferentes figuras compuestas como esferas,cubos o hasta una tetera, entonces comencemos a graficar:

 //Aqui podremos dibujar lo que queramos
  glTranslatef(0,0,-5);
  glutSolidTeapot(1);

Lo que estamos haciendo aqui es transladar toda la escena a -5 en Z con glTranslatef(x,y,z) y con glutSolidTeapot(tamaño) dibujamos un teapot o una tetera solida( tambien podemos dibujar con glutWire en vez de glutSolid, para ver como el esqueleto de la figura). Entonces si ejecutamos nuestro programa tendremos una simple tetera dibujada

Teniendo ya esta figura compuesta dibujada en nuestro contexto, hay que iluminarla!, OpenGL es una maquina de estados, asi que activamos una caracteristica en openGL con   glEnable( ); y todo lo que dibujemos en adelante tendra tal caracteristica y con glDisable(); desactivamos lo que deseemos ( como texturas,iluminacion, generacion de coordenadas etc).

Para iluminar una superficie o plano, necesitamos información sobre su vector normal asociado. Primero veamos que es una normal: la normal de un plano es en realidad un vector perpendicular a este, as í de fácil.¡Volvemos otra vez a la tediosa Álgebra Lineal!… Bueno, no todo es miel sobre hojuelas XD en este ambiente, ni modo… aquí necesitamos entonces saber como calcular este vector y como especificárselo a OpenGL, así que debemos repasar un poco lo que ya habíamos estudiado sobre aritmética de vectores lo recuerdan?.

Tratemos de ver esto con un caso práctico: veamos la “figura1″… Supongamos que tenemos el objeto que se presenta en la figura y deseamos calcular el vector normal a la cara superior de este objeto, la formada por los vértices A, B, C y D. Todos estos vértices son coplanares (adoro estos términos ?) es decir, que pertenecen a un mismo plano, así que si queremos encontrar un vector normal a este plano solo tenemos que encontrar un vector que sea perpendicular acualquiera de estos vértices… Pues bien, para esto solo tenemos que calcular dos vectores pertenecientes a la cara y hacer su producto vectorial. El resultado de esta operación será unvector perpendicular a ambos y por lo tanto una normal del plano.

Figura 1. Calculo de normales para una cara.
Figura 1. Calculo de normales para una cara.

Aquí cabe hacer un pequeño apunte que resulta algo relevante: 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 (es un caso obvio ilustrado en lafigura 1 donde el vértice A por ejemplo, pertenece a tres carasdistintas), ¿qué debemos hacer?. En este caso hay que promediar para obtener cálculos correctos por parte de OpenGL. Tendremos que calcular la normal de cada una delas caras a las que pertenece el vértice, promediarlas y después normalizar el resultado, con lo cual ese vértice presentará un vector normal al cual han contribuido todas las caras a las que pertenece.

Entonces?

Gracias a Dios OpenGL se encarga de todo esto, aveces es interesante un poco de marco teórico!, nosotros utilizaremos Glut openGL se encargara de calcular dependiendo del tipo de iluminación que en este caso sera que cada cara estará en estilo gradiente. Para  crear una luz en openGL necesitamos 3 elementos o características para la luz, que seria la posición de donde viene la emisión de luz, la difusión(que nos configura el color de la luz) es la luz que proviene de una fuente puntual en particular (como el Sol) y golpea la superficie con una intensidad que depende de si la cara da hacia la luz o fuera de ella y la iluminación especular, es lo que produce el brillo que destaca y nos ayuda a distinguir entre superficies planas, sin brillo,  seria como yeso sin chiste.

Entonces declararemos 3 arreglos globales que seran los 3 elementos antes mencionados:

var
  Form1: TForm1;
  glfLightPosition : Array[0..3] of GLfloat = (0, 2, -7, 1.0);
  glfLightDiffuse : Array[0..3] of GLfloat= (0.7, 0.7, 0.7, 1.0);
  glfLightSpecular: Array[0..3] of GLfloat = (0.7, 0.7, 0.7, 1.0);

De esta manera la luz estará colocada en la parte superior de la figura e iluminara la parte de arriba de la tetera, la luz tendrá un color mas omenos blanco/gris
Pero bueno, tenemos que configurar una luz, podemos agregar en total 8 luces pero en este caso solo pondremos como un foco en la parte superior de nuestra escena. En el procedimiento onPaint lo modificaremos de la siguiente manera:

  //Luz
  glLightfv(GL_LIGHT0, GL_POSITION, @glfLightPosition); // Se definen las
  glLightfv(GL_LIGHT0, GL_DIFFUSE, @glfLightDiffuse); //carácterísticas de la
  glLightfv(GL_LIGHT0, GL_SPECULAR,@glfLightSpecular);// luces
  glenable(GL_LIGHT0);
  glEnable(GL_LIGHTING);
  //Fin luz

 //Aqui podremos dibujar lo que queramos
//codigo anterior puesto

Espero se ubiquen un poco para no tener que poner todo el codigo, glLightfv estamos estableciendo las caracteristicas de nuestra luz, agregando en la luz 0 (GL_LIGHT0) nuestro arreglos declarados anteriormente, habilitamos nuestra luz 0 y ademas habilitamos que nuestra escena permita iluminacion con GL_LIGHTING
Entonces ya agregando esta configuracion y habilitando la iluminacion, las figura que dibujemos con glut tendran esta emision de luz en su superficie, quedando de la siguiente manera:

Ahora nuestra tetera tiene un mejor aspecto!. Pero es una tetera fija! sin movimiento, que aburrido no?, ahora vamos a animarla!, pero que podemos hacerle? pues no se, con las transformaciones básicas podemos escalarla, trasladarla o rotarla consecutivamente en un lapso de tiempo para asi simular movimiento!

Entonces agregaremos un timer a nuestro form, y en su evento agregamos lo siguiente:

 procedure TForm1.Timer1Timer(Sender: TObject);
begin
  Inc(Angle,1);
  if Angle >= 360 then Angle:=0;
  Repaint;
end;

Lo que hacemos aqui es incrementar nuestra variable Angle que ya habiamos declarado, la incrementamos de 1 en 1 y como esta sera nuestro angulo pues solo sera desde 0 hasta 360. Ahora nos iremos a nuestro procedimiento onPaint, para hacer rotar nuestra tetera!

 //Aqui podremos dibujar lo que queramos
  glTranslatef(0,0,-10);
  glRotatef(15,1,0,0);
  glRotatef(Angle,0,1,0);
  glutSolidTeapot(2);

Estoy rotando 15 grados respecto al eje “x” para poder mirar la figura no nomas de lado. Si corremos la aplicacion esta ya estara rotando ( la velocidad depende del intervalo del timer), pongan al intervalo del timer 10 milisegundos y asi estara un poco mas rapido.
Pero no notan que parpadea?, si es que les pasa es por que como estamos utilizando el procedimiento onPaint, y lo estamos llamando cada 10 milisegundos, windows redibuja nuestra ventana cada 10milisegundos, pero en este caso nosotros nos encargamos de dibujarla, asi que le decimos a windows byebye no refresques mi ventana!, lo hacemos de la siguiente manera
Nos vamos al apartado de declaraciones privadas ( en el mismo lugar donde declaramos nuestras variables de al principio) y escribimos lo siguiente

procedure WMEraseBkgnd(var Message: TWMEraseBkgnd);message WM_ERASEBKGND;

y nos vamos a definir este procedimiento arriba del ultimo end. ( o donde deseen )

procedure TForm1.WMEraseBkgnd(var Message: TWMEraseBkgnd);
begin
  Message.Result:=1;
end;

Ya con esto le decimos a windows que no refresque nuestra ventana por que de eso nos encargamos nosotros!

Aplicando mas transformaciones

Bueno este es nuestro mundo, así que podemos hacer lo que queramos =D,  que tal si una tetera da vueltas alrededor locamente de la tetera que ya tenemos!?

Para eso tenemos que aplicar dos transformaciones si queremos que otra figura gire respecto a otra, viendo la siguiente imagen:

Aquí estamos trasladando y rotando nuestra figura, la imagen muestra claramente los efectos obtenidos de aplicar las transformaciones de Rotación y Traslación a un mismo cuerpo en diferente orden. Y como podemos observar son muy diferentes.

Pero creo existe un problema, si queremos tener 2 objetos en nuestra escena y aplicarles diferentes transformaciones a cada uno, tenemos que hacer el uso de una pila, que seria  glPushMatrix y glPopMatrix. Lo que hace glPushMatrix es guardar toda nuestra escena en una pila, y todo lo que transformemos no se le aplicara a lo que esta guardado en la pila, y con glPopMatrix liberamos nuestra pila, entonces en pocas palabras todo lo que transformemos dentro de un glPushMatrix y glPopMatrix  sera lo unico que se transformara, entonces nuestro procedimiento onPaint( en la parte del comentario donde dibujamos en delante) quedara de la siguiente manera:

 //Aqui podremos dibujar lo que queramos
  glTranslatef(0,0,-10); //transladamos toda la escena para obtener una mejor vista
  glRotatef(15,1,0,0); //rotamos nuestra escena 15 grados respecto al eje x para poder ver mejor
  //Figura grande
  glPushMatrix;
      glRotatef(Angle,0,1,0);
      glutSolidTeapot(1);
  glPopMatrix;
  //Figura pequeña
  glPushMatrix;
      glRotatef(-Angle*2,0,1,0);
      glTranslatef(3,0,0);
      glRotatef(Angle,1,0,0);
      glutSolidTeapot(0.5);
  glPopMatrix;

Si se fijan los glpush y glpop pueden ser como “corchetes” utilzados en diferentes lenguajes, si quitamos cada uno de esos glpush y glpop, cada transformacion afectara a toda la escena!. Podemos correr nuestra aplicacion y el resultado sera el siguiente:

Con esta practica y con otra que subiré próximamente que trata sobre como aplicar texturas a nuestros objetos, seremos capaces de realizar diferentes animaciones como simular un átomo o hasta un sistema solar

Un saludo! y esto es todo por hoy!

Anuncios

2 comentarios sobre “Iluminación de objetos con OpenGL y Glut [Delphi]

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s