Después de una semana de ansiona espera (supongo) aquí llega la cuarta entrega del tutorial de OpenGL basado en Python. En esta entrega iremos un paso más allá ahora que ya comprendemos todo lo que necesitamos para crear un programa OpenGL. Si no lo recordamos, haré algo de memoria:
Para crear un contexto OpenGL con todo lo necesario necesitaremos de tres métodos obligatorios y uno opcional básicos:

  • método InitGL: inicializa OpenGL al estado incicial. Ésto supoce inicializar el color de borrado de la pantalla, inicializar el buffer de profundidad (el z-buffer), el smooth shading, la matriz de proyección para dimensionar la ventana y la matriz de vista.
  • método ResizeGLScene: La idea de este método es que se active cuando redimensionamos la ventana. Ya la hagamos más grande o más pequeña ha de modificar la matriz de proyección para adaptar la proyección al nuevo tamaño de la ventana y la correspondiente nueva perspectiva del dibujo.
  • método DrawGLScene: Este método es el básico de todo el montaje. Es el que define lo que se dibuja en la ventana OpenGL. Depende de lo complejo que sea lo que queramos dibujar, será más elaborado o menos pero aquí es donde se define lo que visualizaremos en la ventana OpenGL. Básicamente borrará el contenido de la ventana, dibujará lo que tenga que dibujar y activará el double buffer para evitar problemas de visualización consecuencia del refresco de la pantalla.
  • método KeyPressed: Controla que se pulse la tecla ESC (en nuestro caso).

Una vez implementados estos métodos y inicializada la ventana con glut (el controlador de la ventana) en el constructor de la clase los registramos con los métodos glutXXXFunc para que observe el estado y dispare el método adecuado cuando ocurre el evento.

Una vez hecho este pequeño resumen de lo básico de OpenGL entramos ya en lo que haremos en este segundo tutorial. Básicamente el esqueleto de la aplicación es el mismo a excepción del método DrawGLScene que se amplia con algo más de contenido. Era frustrante ver esas línias de código que parecía que no hiciesen nada. Eso no era cierto, inicializaban el contexto OpenGL pero no dibujaban nada. Que no se vea algo no significa que no exista. :)

Este cuarto tutorial lo dividiremos en dos partes. La primera, dibujará un triángulo y un cuadrado en dos dimensiones sin relleno de color y, después, modificaremos esa implementación para darle un relleno de color a cada una de esas figuras.

Los métodos principales se conservarán igual que el tutorial anterior ya que han de hacer el mismo trabajo, el único que variará es el DrawGLScene que pasará de no dibujar NADA a dibujar ALGO. Tampoco lo cargaremos de trabajo,la verdad, pero algo hará.

def DrawGLScene(self):
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    glLoadIdentity()

Estas dos llamadas las ha de tener todo método DrawGLScene ya que inicializa los buffers de color y de profundidad dejandolos preparados para comenzar a pintar.

def DrawGLScene(self):
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    glLoadIdentity()
    glTranslate(-1.5,0.0,-6.0)

glTranslate produce una translación en (x,y,z). La matriz actual (modelview en este caso) es multiplicada por la matriz de translación reemplazándola en la matriz actual obteniendo así el resultado de la translación. En definitiva, glTranslate reemplaza la matriz de vista del modelo por una matriz transladada dada por el producto del vector (x,y,z) y la propia matriz. Al modificar la matriz todos los objetos entre glBegin y glEnd dibujados después de la llamada a glTranslate serán transladados según defina esta llamada.

def DrawGLScene(self):
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    glLoadIdentity()
    glTranslate(-1.5,0.0,-6.0)
    glBegin(GL_POLYGON)

glBegin define el inicio de los limitadores de definición de los vértices de las primitiva o grupo de primitivas. Entendiendo como primitivas las figuras geométricas básicas:

n es un tipo entero incremental y N el número total de vértices

  • glBegin(GL_POINTS): Trata cada vértice como un punto simple.El vértice n define el punto n. Se dibujarán N puntos.
  • glBegin(GL_LINES): Trata cada par de vértices como un segmento linear independiente. Los vértices 2n-1 y 2n formarán las línia n. Se dibujarán un total de N/2 línias.
  • glBegin(GL_LINE_STRIP): Dibuja un conjunto de segmentos de línia conectados desde el primer vértice al último. Los vértices n y n+1 definen la línia n. Se dibujarán N-1 línias.
  • glBegin(GL_LINE_LOOP): Dibuja un conjunto de segmentos de línia conectados desde el primer vértice al último, entonces vuelve al primero. No obstante, la última línia es definida por los vértices 1 y N. Se dibujarán N línias.
  • glBegin(GL_TRIANGLES): Trata cada tres vértices como un triángulo independiente. Los vértices 3n-2, 3n-1 y 3n definirán el triángulo n. Se dibujarán N/3 triángulos.
  • glBegin(GL_TRIANGLE_STRIP): Dibuja un grupo conectado de triángulos. Un triángulo es definido por cada vértice presentado después del primero de dos vértices. Para n impar, los vértices n, n+1 y n+2 formarán un triángulo n. Para n par, n+1, n y n+2 formarán un triángulo n. Se dibujarán N-2 triángulos.
  • glBegin(GL_TRIANGLE_FAN): Dibuja un grupo conectado de triángulos. Un triángulo es definido por cada vértice presentado después del primero de dos vértices. Los vértices 1, n+1 y n+2 definirán el triángulo n. Se definirán N-2 triángulos.
  • glBegin(GL_QUADS): Trata cada grupo de cuatro vértices como un cuadrilatero independiente. Los vértices 4n-3, 4n-2, 4n-1 y 4n definen el cuadrilátero n. N/4 cuadriláteros serán dibujados.
  • glBegin(GL_QUAD_STRIP): Dibuja un grupo conectado de cuadriláteros. Un cuadrilátero es definido por cada par de vértices presentados después del primer par. Los vértices 2n-1, 2n, 2n+2 y 2n+1 definen el cuadrilátero n. Se dibujarán (N/2)-1 cuadriláteros. El orden de la información dada para dibujar este tipo de cuadriláteros es diferente al anterior.
  • glBegin(GL_POLYGON): Dibuja un polígono simple y convexo. Los vértices 1 hasta el N definen el polígono.

glBegin acepta un solo parámetro que especifica con cuál de los diez tipos de vertices se ha de interpretar.

def DrawGLScene(self):
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    glLoadIdentity()
    glTranslate(-1.5,0.0,-6.0)
    glBegin(GL_POLYGON)
    glVertex3f(0.0,1.0,0.0)
    glVertex3f(1.0,-1.0,0.0)
    glVertex3f(-1.0,-1.0,0.0)

glVertex es la función que dibuja un vértice (o punto) con tres parámetros x, y y z que son de tipo glFloat.

def DrawGLScene(self):
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    glLoadIdentity()
    glTranslate(-1.5,0.0,-6.0)
    glBegin(GL_POLYGON)
    glVertex3f(0.0,1.0,0.0)
    glVertex3f(1.0,-1.0,0.0)
    glVertex3f(-1.0,-1.0,0.0)
    glEnd()

glEnd define el final de la definición de vértices de la figura o figuras primitivas. Lo que acabamos de hacer nos dibujará un triángulo en las coordenadas definidas ((0.0,1.0,0.0),(1.0,-1.0,0.0),(-1.0,-1.0,0.0)) sin ningún tipo de color de relleno. Ésto sería equivalente a:

def DrawGLScene(self):
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    glLoadIdentity()
    glTranslate(-1.5,0.0,-6.0)
    glBegin(GL_POLYGON)
    glVertex2f(0.0,1.0)
    glVertex2f(1.0,-1.0)
    glVertex2f(-1.0,-1.0)
    glEnd()

Definimos vértices únicamente con las coordenadas x e y.

def DrawGLScene(self):
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    glLoadIdentity()
    glTranslate(-1.5,0.0,-6.0)
    glBegin(GL_POLYGON)
    glVertex3f(0.0,1.0,0.0)
    glVertex3f(1.0,-1.0,0.0)
    glVertex3f(-1.0,-1.0,0.0)
    glEnd()
    glTranslate(3.0,0.0,0.0)

Transladamos la primitiva que dibujaremos a continuación 3.0 unidades en el eje de las x.

def DrawGLScene(self):
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    glLoadIdentity()
    glTranslate(-1.5,0.0,-6.0)
    glBegin(GL_POLYGON)
    glVertex3f(0.0,1.0,0.0)
    glVertex3f(1.0,-1.0,0.0)
    glVertex3f(-1.0,-1.0,0.0)
    glEnd()
    glTranslate(3.0,0.0,0.0)
    glBegin(GL_QUADS)
    glVertex3f(-1.0,1.0,0.0)
    glVertex3f(1.0,1.0,0.0)
    glVertex3f(1.0,-1.0,0.0)
    glVertex3f(-1.0,-1.0,0.0)
    glEnd()

Con el código anterior lo que hemos hecho es definir un cuadrilátero simple representando sus cuatro vértices.

def DrawGLScene(self):
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    glLoadIdentity()
    glTranslate(-1.5,0.0,-6.0)
    glBegin(GL_POLYGON)
    glVertex3f(0.0,1.0,0.0)
    glVertex3f(1.0,-1.0,0.0)
    glVertex3f(-1.0,-1.0,0.0)
    glEnd()
    glTranslate(3.0,0.0,0.0)
    glBegin(GL_QUADS)
    glVertex3f(-1.0,1.0,0.0)
    glVertex3f(1.0,1.0,0.0)
    glVertex3f(1.0,-1.0,0.0)
    glVertex3f(-1.0,-1.0,0.0)
    glEnd()
    glutSwapBuffers()

Y como comentamos en el tutorial anterior hemos de activar el double buffer para evitar problemas de representación gráfica dados por el refresco de la pantalla.

Este código lo que hará será dibujar dos figuras sin relleno de color aliniadas en el eje de las x y separadas tres unidades.

En el caso que queramos añadirle color a la figura tendremos que definir el ámbito del color ANTES de llamar a la función que dibuja el vértice o primitiva. En nuestro caso el color del triángulo será de ámbito de vértice y el color del cuadrilátero será de ámbito de primitiva:

def DrawGLScene(self):
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    glLoadIdentity()
    glTranslate(-1.5,0.0,-6.0)
    glBegin(GL_POLYGON)
    glColor3f(1.0,0.0,0.0)
    glVertex3f(0.0,1.0,0.0)
    glColor3f(0.0,1.0,0.0)
    glVertex3f(1.0,-1.0,0.0)
    glColor3f(0.0,0.0,1.0)
    glVertex3f(-1.0,-1.0,0.0)
    glEnd()
    glTranslate(3.0,0.0,0.0)
    glColor3f(0.3,0.5,1.0)
    glBegin(GL_QUADS)
    glVertex3f(-1.0,1.0,0.0)
    glVertex3f(1.0,1.0,0.0)
    glVertex3f(1.0,-1.0,0.0)
    glVertex3f(-1.0,-1.0,0.0)
    glEnd()
    glutSwapBuffers()

glColor3f define el color de lo que se dibujará a continuación basándose en tres parámetros de tipo glFloat que son la cantidad de rojo, verde y azul en el rango [0.0,1.0]. Donde 0.0 es el mínimo y 1.0 es el máximo. Existe otra función que se puede definir el parámetro alpha o de transparencia glColor4f. Esta función tiene un último parámetro que se especifica también en el rango anteriormente definido.

En nuestro código, en el triángulo el color se define a nivel de vértico con lo que la figura se verá con un color de degradado suma de los diferentes colores de los vértices. En cuadrilátero al definirse a nivel de primitiva todo él será del mismo color.

Y con ésto acabamos este cuarto tutorial de OpenGL. En el próximo tutorial hablaremos de rotaciones y movimientos de primitivas.

¡Hasta entonces!