Primero de todo lo que vamos a hacer es adaptar el – a mi entender – uno de los mejores tutoriales de openGL que hay en la red a Python. El tutorial en cuestión es el de NeHe.
Empezaremos por crear una ventana donde no se vea nada. Puede parecer ridículo pero creando un contexto gráfico donde no hay nada podemos ver las funciones esenciales que se necesitan de GLU(T) y openGL.
Nuestro código en Python comienza con:
from OpenGL.GL import * from OpenGL.GLUT import * from OpenGL.GLU import * import sys
En el fragmento de código anterior lo que hacemos es importar las funciones de las librerías de pyOpenGL. Concretamente GL,GLU y GLUT. También importamos el módulo sys para trabajar con algunas llamadas al sistema.
Antes de continuar me gustaría definir unos cuantos conceptos que no se ven explícitos en el código Python pero que es bueno comprenderlos ya que en otros lenguajes es necesario definirlos de forma explícita.
El primero de ellos es el Rendering Context (RC) o el Contexto de Renderizado. Todo programa en OpenGL está enlazado a un RC que no es otra cosa que una capa intermedia que enlaza las llamadas a OpenGL con el Device Context (DC) o Contexto Hardware. Para que el tinglado funcione también es necesario definir (explícita o implicitamente) el Contexto Hardware. El Contexto Hardware no es más que otra capa que enlaza la ventana que el llamado GDI (Graphics Device Interface). Por tanto, el RC lo que hace es conectar OpenGL con el DC para tratar lo que en la ventana se haya de visualizar.
Una vez definidos estos conceptos ya podemos continuar con nuestro código.
Una vez importadas las librerías necesarias hemos de definir la clase ventana.
class Ventana:
La primera de las variables que hemos definido declara el código de tecla que se utilizará para salir del programa. En nuestro caso, Esc. La segunda variable define el número de la ventana en GLUT.
A continuación definiremos el método que trata lo que ocurre en la ventana cuando la redimensionamos:
Lo primero que vamos a hacer es hacer la definición de la función que admitirá dos parámetros de entrada: el ancho y el alto.
def ReSizeGLScene(Width, Height):
Una vez definida lo que hemos de hacer es evitar la división por 0 en el caso que la ventana sea demasiado pequeña
def ReSizeGLScene(Width, Height): if Height == 0:
El siguiente paso es hacer una llamada a la función glViewPort(Width, Height). Esta función definida en el API de OpenGl – observemos el gl que antecede al nombre de la función – lo que hace es crear la equivalencia más fina posible entre las coordenadas del hardware gráfico y las coordenadas de la ventana.
def ReSizeGLScene(Width, Height): if Height == 0:
A continuación lo que hay que hacer es redefinir la matriz de projección definida en la función de inicialización a GL_PROJECTION ya que hemos de aplicar las operaciones a la pila de proyección.
def ReSizeGLScene(Width, Height): if Height == 0: Height = 1 glViewPort(0, 0, Width, Height) glMatrixMode(GL_PROJECTION)
Reinicializamos la matriz de proyección para vaciar la pila de proyección
def ReSizeGLScene(Width, Height): if Height == 0: Height = 1 glViewPort(0, 0, Width, Height) glMatrixMode(GL_PROJECTION) glLoadIdentity()
A continuación hemos de recalcular el Aspect Radio de la ventana. A modo de resumen y sin entrar demasiado en detalle el Aspect Radio (o relación de aspecto) vendría a ser la relación del ancho de la ventana con su altura.
def ReSizeGLScene(Width, Height): if Height == 0: Height = 1 glViewPort(0, 0, Width, Height) glMatrixMode(GL_PROJECTION) glLoadIdentity() gluPerspective(45.0, float(Width)/float(Height), 0.1, 100.0)
Y volvemos a restaurar el modo a GL_MODELVIEW una vez ya hechas las operaciones de redimensionado.
def ReSizeGLScene(Width, Height): if Height == 0: Height = 1 glViewPort(0, 0, Width, Height) glMatrixMode(GL_PROJECTION) glLoadIdentity() gluPerspective(45.0, float(Width)/float(Height), 0.1, 100.0) glMatrixMode(GL_MODELVIEW)
En el próximo paso lo que haremos es configurar todo lo relativo a OpenGL. Configuraremos qué color utilizará el programa para limpiar la pantalla, activaremos el buffer de profundidad, … Todas estas rutinas no han de ser llamadas hasta que la ventana OpenGL sea creada. El procedimiento devolverá un valor pero por ahora no nos hemos de preocupar del valor devuelto ya que la inicialización de OpenGL que haremos no es lo suficientemente compleja como para ello.
def InitGL(Width, Height):
Lo primero que hemos hecho es declarar el método que se encargará de la inicialización de OpenGL. Como parámetro le pasaremos la altura y la anchura ya que algunas de las rutinas que utilizará necesitarán estos parámetros.
def InitGL(Width, Height): glClearColor(0.0, 0.0, 0.0, 0.0)
La función glClearColor define con qué color se pintará el fondo de la ventana cuando se haya de limpiar. Limpiar en este sentido corresponde a eliminar objetos que hayan en la misma.
def InitGL(Width, Height): glClearColor(0.0, 0.0, 0.0, 0.0) glClearDepht(1.0)
Para entender lo que hace la función glClearDepth hemos de entender qué carai es el buffer de profundidad (o Depth Buffer o z-buffer). Un z-buffer no es más que la parte de la memoria de un adaptador de vídeo encargada de gestionar las coordenadas de profundidad de imágenes en 3D. El z-buffer es la solución a la problemática de la visibilidad. El problema consiste en decidir qué elementos de una escena renderizada son visibles y cuáles ocultos.
Básicamente el funcionamiento sería el siguiente: cuando un objeto se renderiza, la profundidad del píxel generado se almacena en un buffer de datos. Este buffer está construído en forma de matriz (coordenadas x e y) con un elemento por cada píxel en la pantalla. Si hay algún elemento en la escena que se ha de mostrar en el mismo píxel, se calcula la profundidad y se compara con la almacenada en la matriz y el objeto que tenga profundidad más cercana al observador será la que se muestre. La profundidad elegida se guardará en el z-buffer reemplazando a la que había. Esta forma de emular la profundidad acabará creando una verdadera percepción de profundidad natural.
Una vez definido lo que es el buffer de profundidad podemos entender la lógica de eliminar los valores del mismo en la inicialización de una nueva escena OpenGl.
def InitGL(Width, Height): glClearColor(0.0, 0.0, 0.0, 0.0) glClearDepth(1.0) glDepthFunc(GL_LESS) glEnable(GL_DEPTH_TEST)
Estas dos rutinas habilitan el testeo del buffer de profundidad de nuestra tarjeta de vídeo. La primera de ellas selecciona qué tipo de testeo se va a hacer y la segunda habilita el testeo del buffer.
def InitGL(Width, Height): glClearColor(0.0, 0.0, 0.0, 0.0) glClearDepht(1.0) glDepthFunc(GL_LESS) glEnable(GL_DEPTH_TEST) glShadeModel(GL_SMOOTH)
La función glShadeModel habilita el llamado Smooth Shading. El método del Smooth Shading mezcla los colores a través de un polígono de forma agradable. De esta manera se eliminan las diferencias de color y luz entre polígonos de un mismo objeto.
def InitGL(Width, Height): glClearColor(0.0, 0.0, 0.0, 0.0) glClearDepht(1.0) glDepthFunc(GL_LESS) glEnable(GL_DEPTH_TEST) glShadeModel(GL_SMOOTH) glMatrixMode(GL_PROJECTION) glLoadIdentity()
Como ya hemos comentado anteriormente, estas dos funciones lo que hacen es definir que tipo de matriz utilizar, en este caso utilizaremos la matriz de proyección. Una vez seleccionada lo que hace es inicializarla.
def InitGL(Width, Height): glClearColor(0.0, 0.0, 0.0, 0.0) glClearDepht(1.0) glDepthFunc(GL_LESS) glEnable(GL_DEPTH_TEST) glShadeModel(GL_SMOOTH) glMatrixMode(GL_PROJECTION) glLoadIdentity() gluPerspective(45.0, float(Width)/float(Height), 0.1, 100.0)
Necesitamos calcular la relación de aspecto – recordemos que es la relación del alto con el ancho- de la ventana para dibujar de forma adecuada los objetos en la ventana.
def InitGL(Width, Height): glClearColor(0.0, 0.0, 0.0, 0.0) glClearDepht(1.0) glDepthFunc(GL_LESS) glEnable(GL_DEPTH_TEST) glShadeModel(GL_SMOOTH) glMatrixMode(GL_PROJECTION) glLoadIdentity() gluPerspective(45.0, float(Width)/float(Height), 0.1, 100.0) glMatrixMode(GL_MODELVIEW)
Una vez dimendionada la ventana OpenGL, volvemos a cambiar a la matriz de modelo ya que no necesitamos hacer más operaciones sobre la matriz de proyección.
En el próximo bloque definiremos la sección principal de pintado. Esta sección es donde va todo el código de dibujado. Todo lo que tengamos planeado de dibujar en la pantalla irá en esta sección. Este bloque de código se irá incrementando a lo largo del tutorial cuanto más complejo sea lo que deseamos dibujar. Por ahora todo lo que haremos es dibujar una pantalla negra. Para cosas más complejas habrá que esperar a las próximas publicaciones del tutorial. ![]()
Si todo va bien, la función devuelve un TRUE, en caso contrario devuelve un FALSE y para la ejecución del programa.
def DrawGLScene():
Lo primero que hemos hecho es definir la función. Es una función que no recibe ningún tipo de parámetro.
def DrawGLScene(): glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) glLoadIdentity()
A continuación lo que hemos de hacer es eliminar el contenido del buffer de profundidad y borrar el contenido de la pantalla.
def DrawGLScene(): glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) glLoadIdentity() glutSwapBuffers()
En la aplicación, para evitar problemas en el refresco de la pantalla utilizamos los llamados Double Buffers. Un Double Buffer lo que hace es pintar primero la información gráfica en un buffer y cuando ha acabado de pintar un objeto completo volcarlo a la pantalla. Con ésto lo que evitamos es ver parpadeos y representaciones extrañas debidas al refresco cuando un objeto está a medio pintar (hay objetos que pueden tardar un intervalo de tiempo relativamente largo). Con esta función lo que hacemos es eliminar el contenido y activar el double buffer
def keyPressed(*args):
La sección código que hemos definido se llamará cada vez que una tecla sea pulsada. Cuando una tecla se pulsa, se comprueba si es ESCAPE, si lo es va finalizando el RC, DC y, finalmente el manejador de ventanas.
def keyPressed(*args): if args[0] == ESCAPE: glutDestroyWindow(window) sys.exit()
La primera de las llamadas destruye, como hemos comentado, la ventana OpenGL y, una vez eliminada la ventana, finaliza la ejecución del programa.
A continuación continuamos con la implementación del constructor de la clase.
def __init__(self): self.window=0 self.ESCAPE='\O33' glutInit(())
En este primer ejemplo, le pasamos una lista vacía a glutInit. No obstante, no siempre será así.
def __init__(self): self.window=0 self.ESCAPE='\O33' glutInit(()) glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH)
Esta última función lo que hace es inicializar el modo gráfico de la ventana. Para una inicialización completa del modo gráfico se han de especificar como mínimo el tipo de modo gráfico, si se activa el double buffer, el color RGBA, los componentes de transparencia soportados y la activación del buffer de profundidad.
def __init__(self): self.window=0 self.ESCAPE='\O33' glutInit(()) glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH) glutInitWindowSize(640, 480)
Una vez seleccionado el modo gráfico lo que hemos de hacer es es inicializar el tamaño de la ventana. En nuestro caso la inicializaremos a un tamaño de 640×480
def __init__(self): self.window=0 self.ESCAPE='\O33' glutInit(()) glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH) glutInitWindowSize(640, 480) glutInitWindowPosition(0, 0)
La ventana toma como coordenadas iniciales (las de referencia) el píxel de más arriba a la izquierda.
def __init__(self): self.window=0 self.ESCAPE='\O33' glutInit(()) glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH) glutInitWindowSize(640, 480) glutInitWindowPosition(0, 0) window = glutCreateWindow("Nuestro primer tutorial en OpenGL basándonos en Nehe '99 ")
Lo que hemos hecho con esta última función es crear la ventana asignándole un texto de título.
def __init__(self): self.window=0 self.ESCAPE='\O33' glutInit(()) glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH) glutInitWindowSize(640, 480) glutInitWindowPosition(0, 0) window = glutCreateWindow("Nuestro primer tutorial en OpenGL basándonos en Nehe '99 ") glutDisplayFunc(DrawGLScene)
Hemos de registrar la función de pintado con GLUT, esta función ha de capturar eventos y responder a los mismos
#glutFullScreen()
Si quisieramos crear una ventana a pantalla completa descomentariamos la función glutFullScreen().
def __init__(self): self.window=0 self.ESCAPE='\O33' glutInit(()) glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH) glutInitWindowSize(640, 480) glutInitWindowPosition(0, 0) window = glutCreateWindow("Nuestro primer tutorial en OpenGL basándonos en Nehe '99 ") glutDisplayFunc(DrawGLScene) glutIdleFunc(DrawGLScene)
En los tiempos de silencio (cuando no estamos haciendo nada) el manejador aprobechará para repintar la pantalla.
def __init__(self): self.window=0 self.ESCAPE='\O33' glutInit(()) glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH) glutInitWindowSize(640, 480) glutInitWindowPosition(0, 0) window = glutCreateWindow("Nuestro primer tutorial en OpenGL basándonos en Nehe '99 ") glutDisplayFunc(DrawGLScene) glutIdleFunc(DrawGLScene) glutReshapeFunc(ReSizeGLScene)
Hemos de registrar la función que es llamada cuando la ventana es redimensionada.
def __init__(self): self.window=0 self.ESCAPE='\O33' glutInit(()) glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH) glutInitWindowSize(640, 480) glutInitWindowPosition(0, 0) window = glutCreateWindow("Nuestro primer tutorial en OpenGL basándonos en Nehe '99 ") glutDisplayFunc(DrawGLScene) glutIdleFunc(DrawGLScene) glutReshapeFunc(ReSizeGLScene) glutKeyboardFunc(keyPressed)
Ídem que la anterior pero cuando una tecla es presionada.
def __init__(self): self.window=0 self.ESCAPE='\O33' glutInit(()) glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH) glutInitWindowSize(640, 480) glutInitWindowPosition(0, 0) window = glutCreateWindow("Nuestro primer tutorial en OpenGL basándonos en Nehe '99 ") glutDisplayFunc(DrawGLScene) glutIdleFunc(DrawGLScene) glutReshapeFunc(ReSizeGLScene) glutKeyboardFunc(keyPressed) InitGL(640, 480)
Inicializamos nuestra ventana openGL.
def __init__(self): self.window=0 self.ESCAPE='\O33' glutInit(()) glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH) glutInitWindowSize(640, 480) glutInitWindowPosition(0, 0) window = glutCreateWindow("Nuestro primer tutorial en OpenGL basándonos en Nehe '99 ") glutDisplayFunc(DrawGLScene) glutIdleFunc(DrawGLScene) glutReshapeFunc(ReSizeGLScene) glutKeyboardFunc(keyPressed) InitGL(640, 480) glutMainLoop()
Y iniciamos el escuchador de eventos (Event Processing Engine).
Una vez programado esto, simplemente hemos de crear el objeto Ventana como en cualquier otro programa Python, dar privilegios de ejecución al script y se nos abrirá una ventana donde no habrá nada. No obstante, este tutorial inicial ha servido para establecer todo lo necesario que necesita -como mínimo- una aplicación OpenGL para funcionar.
¡Nos vemos en la próxima entrega!
En el fragmento de código anterior lo que hacemos es importar las funciones de las librerías de pyOpenGL. Concretamente GL,GLU y GLUT. También importamos el módulo sys para trabajar con algunas llamadas al sistema.
Antes de continuar me gustaría definir unos cuantos conceptos que no se ven explícitos en el código Python pero que es bueno comprenderlos ya que en otros lenguajes es necesario definirlos de forma explícita.
El primero de ellos es el Rendering Context (RC) o el Contexto de Renderizado. Todo programa en OpenGL está enlazado a un RC que no es otra cosa que una capa intermedia que enlaza las llamadas a OpenGL con el Device Context (DC) o Contexto Hardware. Para que el tinglado funcione también es necesario definir (explícita o implicitamente) el Contexto Hardware. El Contexto Hardware no es más que otra capa que enlaza la ventana que el llamado GDI (Graphics Device Interface). Por tanto, el RC lo que hace es conectar OpenGL con el DC para tratar lo que en la ventana se haya de visualizar.
Una vez definidos estos conceptos ya podemos continuar con nuestro código.
Una vez importadas las librerías necesarias hemos de definir la clase ventana.
The excellent post assited me very much! Bookmarked your blog, extremely interesting topics everywhere that I see here! I really appreciate the info, thank you.
Hey thanks for the post. I found it doing a search online for work at home jobs. It’s always nice to read a blog with good quality topic. Thanks. looking forward to your next post