Nota del traductor: Esto es una traducción del tutorial para la construcción de un motor gráfico tridimensional escrito por Damiano Vitulli en su sitio www.spacesimulator.net. El documento original en inglés se encuentra en http://www.spacesimulator.net/tutorials/OpenGL_and_Glut_tutorial.html. Esta traducción fue hecha por Erick Martín del Campo <molocheto at hotmail dot com>, el 7 de octubre de 2005, cualquier corrección con respecto a la traducción de este documento puedes enviarme un correo; otros asuntos como los conceptos aquí descritos y el código utilizado deben ser tratados con el autor original de este documento, Damiano Vitulli.

  • 3 CONCLUSIONS
  • 4 LINKS
  • INTRODUCCIÓN

    Original Author: Damiano Vitulli

    Translation by: Click here

    En la lección anterior, construimos la primer sección de nuestra cadena de renderizado: como obtener los datos del objeto guardarlos en una estructura. Hoy, completaremos los puntos 2 y 3:

    • Transformaciones para ubicar los objetos en el mundo (transformaciones de modelando y visualización)
    • Renderizando la escena en la pantalla de 2d (transformación de proyección, transformación del espacio de visualización, eliminación de caras ocultas, buffers de color y profundidad)

    Después vamos a introducir las librerías gráficas de OpenGL y las librerías de utilidades de GLUT. Al final de esta lección seremos capaces de mostrar un objeto sólido rotando en nuestra pantalla!

    TRANSFORMACIONES PARA COLOCAR OBJETOS EN EL MUNDO (transformaciones de modelado y visualización)

    Después de definir la estructura del objeto, debemos aplicar algunas transformaciones a todos los vertices que tenemos antes de mostrarlos en la pantalla. Queremos crear un mundo, o para ser más exactos, (loco yo por los simuladores espaciales) un universo completo.

    La primera transformación es la TRANSFORMACIÓN DE MODELADO. Como nuestra intención es crear naves espaciales que, aunque se pruebe lo contrario, no son objetos estáticos ;-), tenemos que transformar el sistema local de coordenadas del objeto (que es relativo a la posición central de cada objeto) a un sistema absoluto de coordenadas (que es relativo al centro del universo en 3D ;-) ). En otras palabras debemos traducir el objeto añadiendo su posición actual en el universo (el cual puede continuar cambiando si hay movimiento) a las coordenadas de los vertices locales.

    La siguiente es la TRANSFORMACIÓN DE VISUALIZACIÓN. Nuestro más grande objetivo, por supuesto es explorar el universo moviéndonos a través de éste como queramos, convirtiendo el monitor en una videocámara de libre movimiento. ¿Cómo podemos hacer este tipo de transformación? La respuesta es relativamente sencilla. De hecho, simplemente podemos considerar que la cámara siempre esté en la posición 0,0 sin rotaciones. ¿Y luego? Fácil! Tomamos cualquier transformación que tengamos que aplicar a la cámara para obtener cierto movimiento y le aplicamos la transformación opuesta a todos los objetos en lugar de mover la cámara. Por ejemplo, supongamos que quisiéramos mover nuestro punto de visualización hacia el objeto +10 puntos en el eje Z y observar el objeto desde arriba (rotando en el eje X 40 grados). Lo que sucede realmente en la mayoría de los motores gráficos es que el objeto ha hecho la traducción de -10 en el eje Z y luego la rotación de -40 grados en el eje X. Estos simplifica bastante el manejo del motor porque la cámara de video siempre se mantendrá en el origen.

    RENDERIZAR LA ESCENA EN LA PANTALLA 2D (eliminación de caras ocultas, transformación de proyección y de visualización, y buffer de color y de profundidad)

    La siguiente operación a realizar es la ELIMINACIÓN DE CARAS OCULTAS. Esto significa que vamos a excluir los triángulos que no son visibles, esto es, la caras en las partes traseras de los objetos. De esta forma podemos ahorrar mucho tiempo de renderizado debido a que la función para dibujar solamente dibujará la mitad del total de triángulos. OpenGL hará esto por nosotros.

    La siguiente transformación es la TRANSFORMACIÓN DE PROYECCIÓN. En este punto necesitamos "aplastar" una escena 3D en una pantalla 2D. Necesitamos simular el eje Z porque nuestro pobre monitor solamente tiene dos ejes X y Y. La forma más sencilla de llevar a cabo este tipo de traducción es dividir todas las coordinadas de los puntos (x,y) relativas a su componente Z. El efecto de este procedimiento es comprimir los puntos distantes para que parezca que se acercan al punto central x = 0 y y = 0. Esto es exactamente lo que sucede en GL. Muchos de ustedes deben conocer esto como "proyección de perspectiva".

    La última transformación es la TRANSFORMACIÓN DEL ESPACIO DE VISUALIZACIÓN. Todo lo que hace es convertir todos los puntos que serán usados para la actual resolución del espacio de visualización.

    Un buffer es una zona de memoria en la cual podemos almacenar algunos datos. Los buffers del OpenGL son regiones exactamente tan grandes como nuestro espacio de visualización. Por ejemplo, si abrimos una ventana de 640 x 480 de tamaño estaríamos destinando un buffer de: 640 x 480 = 307200 pixeles. Esto significa que, para el modo de color de 16 bits serían: 307200*16 = 4915200 bits. ¡Eso corresponde a como 614 Kbytes de memoria de video!

    OpenGL tiene dos buffers principales: el BUFFER DE COLOR (que puede ser sencillo o doble) y el BUFFER DE PROFUNDIDAD.

    El BUFFER DE COLOR es lo que vemos en la pantalla y a donde llegan los resultados de todas las operaciones de dibujo. Después de haber llevado a cabo todos los cálculos geométricos, OpenGL comienza a llenar este buffer pixel por pixel, rellenando nuestros triángulos. Si la escena es animada el BUFFER DE COLOR es dibujado y borrado cada cuadro (frame). El BUFFER DE COLOR es usado frecuentemente en operación de modo doble, llamado BUFFER DOBLE, que es el que vamos a utilizar. La operación de modo doble consiste en mostrar un buffer mientras el otro buffer es limpiado y llenado con el próximo cuadro. Una vez que esta operación es completada los buffers son intercambiados. Usando esta técnica, la animación resultante queda practicamente libre de parpadeo.

    Ahora, supongamos que hay dos triángulos en nuestra escena uno detrás del otro, ambos visibles. En este caso, el orden en el que los triángulos se dibujarán es muy importante. Si dibujamos el triángulo más cercano a nuestro punto de visualización primero y después el más distante, los pixeles del triángulo más cercano serán cubiertos por el más lejano, creando un efecto desagradable. Una técnica para evitar esto es ordenar todos los triángulos visibles por sus vértices en Z. Después dibujarlos en orden, del triángulo más distante al más cercano. Esta técnica se conoce como "EL ALGORITMO DEL PINTOR". Nosotros no vamos a utilizar este método porque OpenGL nos provee con una herramienta más eficiente: el BUFFER DE PROFUNDIDAD. Este buffer tiene las mismas dimensiones que el BUFFER DE COLOR pero en lugar de contener los colores del pixel contiene información acerca de la profundidad de cada pixel en el eje Z. Guardar esta información es muy importante y por una sencilla razón. Cuando vamos a dibujar nuestros triángulos pixel por pixel en la pantalla, primero hacemos un examen para ver si el pixel a dibujar está más cerca que el pixel que está ya almacenado en el buffer Z. Si está más cerca entonces actualizamos el buffer de profundidad con el nuevo valor y escribimos en el BUFFER DE COLOR. Si no está más cerca no consideramos ese pixel para dibujar. ¡Esto produce excelentes resultados!

    No necesitamos preocuparnos por programar estas operaciones "a mano" porque nuestra gran librería GL hará todos los cálculos para nosotros. OpenGL también llevará a cabo todas las operaciones de dibujo a bajo nivel incluyendo el dibujado de nuestros triángulos y aplicándoles colores asi como efectos de iluminación y mapeado.

    ¿No necesitamos hacer nada? ¿Entonces que vamos a hacer aquí? ¿Estamos perdiendo nuestro tiempo? ¡No! Necesitamos proveer a OpenGL con toda la información que necesita para que pueda hacer todos esos cálculos, interactuar con la tarjeta de video y realizar todas las operaciones de bajo nivel utilizando (si está presente) el hardware de aceleración 3D.

    ¡FINALMENTE, OPENGL!

    OpenGL es una librería que nos permite interactuar con el hardware de gráficos. Contiene una serie de funciones para dibujar puntos, líneas y polígonos, y lleva a cabo todos los cálculos necesarios para la iluminación, sombreado y transformación de los vértices. En cambio Glut es una librería usada para interactuar OpenGL con el systema de ventanas. Nos permite crear una ventana independiente de la plataforma utilizada (Windows o Linux). También maneja las entradas del teclado.

    La estructura de nuestro programa OpenGL está dividida en dos diferentes secciones:

    1. Función Init: usada para arrancar OpenGL y para inicializar las matrices de modelado, visualización y proyección. También podemos poner todas las operaciones de inicialización que queramos en esta función.
    2. Función Resize: es llamada cada vez que el usuario inicia el programa o cambia la resolución de la ventana-salida. Ésta es necesaria para comunicar el nuevo tamaño del expacio de visualización a OpenG.
    3. Función Keyboard: llamada cada vez que el usuario presiona una tecla.
    4. Función Drawing: limpia todos los buffers (color y profundidad). Todas las transformaciones de modelado, visualización y proyección son llevadas a cabo y la escena es dibujada. Finalmente los dos buffers de color son intercambiados.
    5. Main Cycle: un bucle infinito, el cual llama a todas nuestras funciones cada cuadro o "frame".

    Una función típica de OpenGL se ve así: glFunctionName(GL_TYPE arguments). Para la funciones Glut tenemos: glutFunctionName(arguments). OpenGL también tiene tipos predefinidos para ayudar con la portabilidad. Estos tipos comienzan ocn el prefijo "GL" y son seguidos por "u" (para los valores "unsigned" o sin signo) y por el tipo (float, int, etc.). Por ejemplo, podemos utilizar GLfloat o GLuint para definir tipos de variables similares a los tipos "float" y "unsigned int" de C.

    ENCABEZADOS

    Lo primero que hay que hacer es incluir todos los encabezados o "headers" necesarios: windows.h (para los usuarios de Windows) y glut.h

    #include <windows.h>
    #include <GL/glut.h>
    

    Al incluir glut.h también estaremos indirectamente incluyendo gl.h y glu.h (los encabezados de OpenGL). Es también muy importante configurar las opciones de construcción en el compilador para que incluya las librerías opengl32.lib, glu32.lib y glut32.lib

    Ahora, debemos delcarar una función que inicialice el OpenGL.

    FUNCIÓN INIT

    void init(void)
    {
       glClearColor(0.0, 0.0, 0.2, 0.0);
       glShadeModel(GL_SMOOTH);
       glViewport(0,0,screen_width,screen_height);
       glMatrixMode(GL_PROJECTION);
       glLoadIdentity();
       gluPerspective(45.0f,(GLfloat)screen_width/(GLfloat)screen_height,1.0f,1000.0f);
       glEnable(GL_DEPTH_TEST);
       glPolygonMode (GL_FRONT_AND_BACK, GL_FILL);
    }
    

    Empecemos a analizar el código:

    • void glClearColor( GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); especifica los valores del rojo, verde, azul y alfa usados por glClear para limpiar los buffers de color. Utilizamos azul oscuro como color de fondo entonces asignamos 0.2 como el componente azul. Los otros colores son fijados en 0.0 y también el valor alfa (explicaré el significado de este componente en otro tutorial). Se me olvidó mencionar que en OpenGL el rango efectivo de trabajo para los parámetros es de 0-1. Entonces, podemos combinar todos los componentes para crear cualquier color que queramos. Por ejemplo, para crear un fondo amarillo debemos de fijar el componente rojo en 0 y los componentes verde y azul, ambos en 1.0.
    • void glShadeModel( GLenum mode ); especifica un valor representando una técnica de sombreado (la forma en que OpenGL llenará los triángulos). Si utilizamos GL_FLAT para "mode" entonces cada triángulo se va a dibujar en modo de SOMBREADO PLANO y el color va a ser uniforme (el mismo color para cada pixel del triángulo). Si en cambio usamos GL_SMOOTH, OpenGL hará interpolaciones lineales entre los colores de los vértices de los triángulos. Esta técnica también es conocida como SOMBREADO GOURAUD.
    • void glViewport( GLint x, GLint y, GLsizei width, GLsizei height); fija las dimensiones del espacio de visualización actual para la transformación del mismo.
    • void glMatrixMode( GLenum mode); le dice a OpenGL que matriz es la actualmente activa para las operaciones de matriz. ¿Qué es una matriz? No hemos hablado de matrices aún. Este es un tema muy complicado por ahora y vamos a dedicar un tutorial comleto para ello después. Lo único que necesitamos saber por ahora es que una matriz es un objeto utilizado por OpenGL para hacer todas las transformaciones geométricas. En "mode" podemos insertar el valor GL_PROJECTION para hacer transformaciones más proyectivas o GL_MODELVIEW para hacer transformaciones de visualización y modelado. Una vez especificada la matriz activa actual, la podremos modificar a nuestro gusto.
    • void glLoadIdentity( void ); restaura la matriz activa actual (GL_PROJECTION en nuestro caso) cargando la matriz de identidad.
    • void gluPerspective(GLdouble fovy, GLdouble aspect, GLdouble near,GLdouble far); ¿recuerdas la transformación de proyección? Aquí es donde la ponemos en práctica. OpenGL llena la matriz activa actual (GL_PROJECTION) usando los valores especificados en gluPerspective. En "fovy" debemos especificar el ángulo del campo de visualización, en "aspect" la proporción del aspecto, en "near" y"far" la distancia mínima y máxima de los planos recortados.
    • void glEnable( GLenum cap ); habilita varias opciones. En esta situación hemos habilitado el Buffer Z (BUFFER DE PROFUNDIDAD).
    • void glPolygonMode( GLenum face, GLenum mode ); dibuja nuestros polígonos como puntos, lineas o rellenados (usando GL_POINT,GL_LINES o GL_FILL como parámetro).

    FUNCIÓN RESIZE

    Esta función es muy similar a "init". Limpia los buffers, redefine nuestro espacio de visualización, y muestra de nuevo nuestra escena.

    void resize (int width, int height)
    {
       screen_width=width; 
       screen_height=height; 
       glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
       glViewport(0,0,screen_width,screen_height);
       glMatrixMode(GL_PROJECTION);
       glLoadIdentity();
       gluPerspective(45.0f,(GLfloat)screen_width/(GLfloat)screen_height,1.0f,1000.0f);
       glutPostRedisplay ();
    }
    

    Estas son las funciones que no hemos cubierto:

    • void glClear( GLbitfield mask ); limpia los buffers especificados en "mask". Podemos insertar más de un buffer separándolos con el operador lógico OR "|". En nuestro caso limpiamos tanto el buffer de color como el de profundidad.

    The call to glViewport is necessary here to redefine the new viewport with the new values stored in screen_width and screen_height.

    • void glutPostRedisplay(void); Esta es una función Glut. Llama cualquier rutina que insertemos en la función glutDisplayFunc (llamada en nuestra función Main), con el propósito de redibujar la escena.

    FUNCIONES KEYBOARD

    Vamos a definir dos funciones keyboard: una para manejar la entrada de caracteres ASCII ("r" y "R" y un caracter en blancor ' ') y otra para manejar las teclas de dirección:

    void keyboard (unsigned char key, int x, int y)
    {
       switch (key)
       {
          case ' ':
             rotation_x_increment=0;
             rotation_y_increment=0;
             rotation_z_increment=0;
          break;
          case 'r': case 'R':
             if (filling==0)
             {
                glPolygonMode (GL_FRONT_AND_BACK, GL_FILL);
                filling=1;
             } 
             else 
             {
                glPolygonMode (GL_FRONT_AND_BACK, GL_LINE);
                filling=0;
             }
          break;
       }
    }
    

    Usamos tres variables para hacer rotar nuestro objeto alrededor del eje deseado: rotation_x_increment, rotation_y_increment y rotation_z_increment. Podemos restaurar todas estar variables usando la barra espaciadora y pausar el movimiento de nuestro objeto en su posición actual. También podemos cambiar el modo de dibujado para nuestros polígonos a solo contorno o rellenados con la tecla "r" o "R".

    void keyboard_s (int key, int x, int y)
    {
       switch (key)
       {
          case GLUT_KEY_UP:
             rotation_x_increment = rotation_x_increment +0.005;
          break;
          case GLUT_KEY_DOWN:
             rotation_x_increment = rotation_x_increment -0.005;
          break;
          case GLUT_KEY_LEFT:
             rotation_y_increment = rotation_y_increment +0.005;
          break;
          case GLUT_KEY_RIGHT:
             rotation_y_increment = rotation_y_increment -0.005;
          break;
       }
    }
    

    Esta función es muy similar a la última a diferencia de que ésta maneja las teclas de dirección. Podemos notar que las constantes Glut GLUT_KEY_UP, GLUT_KEY_DOWN, GLUT_KEY_LEFT y GLUT_KEY_RIGHT identifican sus direcciones respectivas e incrementan o decrementan nuestros valores de rotación.

    FUNCIÓN DISPLAY

    Damas y caballeros, la función que han estado esperando: ¡la función de dibujo!

    void display(void)
    {
       int l_index;
       glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
       glMatrixMode(GL_MODELVIEW);
       glLoadIdentity();
       glTranslatef(0.0,0.0,-50);
       rotation_x = rotation_x + rotation_x_increment;
       rotation_y = rotation_y + rotation_y_increment;
       rotation_z = rotation_z + rotation_z_increment;
       if (rotation_x > 359) rotation_x = 0;
       if (rotation_y > 359) rotation_y = 0;
       if (rotation_z > 359) rotation_z = 0;
       glRotatef(rotation_x,1.0,0.0,0.0);
       glRotatef(rotation_y,0.0,1.0,0.0);
       glRotatef(rotation_z,0.0,0.0,1.0);
    

    La primera parte de esta función limpia el buffer de color y el de profundidad y aplica las transformaciones de visualización y modelado. Ponemos la matriz de visualización del modelo como la actual matriz activa usando glMatrixMode con GL_MODELVIEW. Después, inicializamos esta matriz cada cuadro con una llamada a glLoadIdentity.

    • void glTranslatef( GLfloat x, GLfloat y, GLfloat z ); mueve nuestro objeto en el espacio 3D. Esta función multiplica la matriz del modelo por una matriz de traslación definida usando los parámetros x, y, z. La letra "f" al final del nombre indica que estamos utilizando valores de tipo float, en lugar de utilizar la letra "d" que indica parámetros de tipo double. Utilizamos glTranslate para mover el objeto 50 puntos hacia adelante en este caso. ¿Recuerdan la cámara de video y la transformación de visualización? Bueno, podemos considerar esta operación como una traslación de -50 para nuestra cámara. Este movimiento es necesario porque debemos mover una pequeña distancia lejos del objeto para que lo podamos ver. Una vez que hayamos compilado este proyecto podemos intentar modificar el valor Z solamente para ver como afecta la distancia.
    • void glRotatef( GLfloat angle, GLfloat x, GLfloat y, GLfloat z ); Esta función multiplica la matriz actual por la matriz de rotación. Es utilizada para aplicar rotaciones de grados de ángulo alrededor del vector (x,y,z). Las variables rotation_x, rotation_y y rotation_z guardan los valores de rotación del objeto. Esta transformación la consideramos como una transformación de modelo porque solamente rotamos el objeto y no el punto de visualización. Como podemos ver, las diferencias entre las transformaciones de modelado y las de visualización no son tan evidentes. Sin embargo, no necesitamos preocuparnos por esto ahora. Nos encargaremos de este tema en el tutorial de cámara de video.
       glBegin(GL_TRIANGLES);
       for (l_index=0;l_index<12;l_index++)
       {
          glColor3f(1.0,0.0,0.0);
          glVertex3f( cube.vertex[ cube.polygon[l_index].a ].x, cube.vertex[ cube.polygon[l_index].a ].y, cube.vertex[ cube.polygon[l_index].a ].z);
          glColor3f(0.0,1.0,0.0);
          glVertex3f( cube.vertex[ cube.polygon[l_index].b ].x, cube.vertex[ cube.polygon[l_index].b ].y, cube.vertex[ cube.polygon[l_index].b ].z);
          glColor3f(0.0,0.0,1.0);
          glVertex3f( cube.vertex[ cube.polygon[l_index].c ].x, cube.vertex[ cube.polygon[l_index].c ].y, cube.vertex[ cube.polygon[l_index].c ].z);
       }
       glEnd();
       glFlush();
       glutSwapBuffers();
    }
    

    La segunda parte de la función display utiliza las funciones glBegin y glEnd. Estos dos comandos marcan los vertices que definen una primitiva gráfica.

    • void glBegin( GLenum mode ); indica el principio de una lista de vértices-datos que definen una primitiva geométrica. En el parámetro "modo" podemos insertar el tipo de primitiva que vamos a dibujar. Existen diez tipos disponibles (GL_TRIANGLES, GL_POYLGON, GL_LINES, etc.). Usamos GL_TRIANGLES porque queremos dibujar nuestro cubo utilizando 12 triángulos. Hacemos esto comenzando un bucle "for" en el cual realizamos 3 llamadas a glVertex3f y glColor3f.
    • void glVertex3f( GLfloat x, GLfloat y, GLfloat z ); especifica las coordinadas x,y,z de un vértice. El "3f" indica que hay tres parámetros de tipo float. Si solo necesitáramos trabajar con valores x y y de tipo double, el comando correcto sería: void glVertex2d( GLdouble x, GLdouble y ). Insertamos los datos geométricos del objeto en cada parámetro.
    • void glColor3f( GLfloat red, GLfloat green, GLfloat blue ); especifica el actual color activo. Definimos otro color (rojo 1,0,0 - verde 0,1,0 - azul 0,0,1) para cada vértice. Notemos como el modo de sombreado de OpenGL GL_SMOOTH afecta los colores finales del triángulo. Vemos que los colores son interpolados de vértice a vértice para que cambien gradualmente. ¡Muy agradable de ver!
    • void glEnd( void ); termina nuestra definición del objeto.
    • void glFlush( void ); usada para forzar a OpenGL a dibujar la escena. Los buffers de color y de profundidad son llenados y la escena ahora queda visible. ¡Finalmente!
    • void glutSwapBuffers(void); Intercambia el buffer del fondo (donde todas las funciones de dibujado trabajan) con el buffer del frente (lo que vemos ennuestra ventana)cuando se está en modo de doble buffer. Si no estamos en modo de doble buffer esta función no tiene ningún efecto.

    LA FUNCIÓN MAIN

    int main(int argc, char **argv)
    {
    
    Las siguientes cuatro funciones de la librería glut nos permiten crear nuestra ventana para la salida gráfica.
    
    <pre>
       glutInit(&argc, argv);
       glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
       glutInitWindowSize(screen_width,screen_height);
       glutInitWindowPosition(0,0);
       glutCreateWindow("www.spacesimulator.net - 3d engine tutorials: Tutorial 2");
    
    • void glutInit(&argc, argv); inicializa la librería glut. Debemos llamarla antes de llamar cualquier otra función glut.
    • void glutInitDisplayMode(unsigned int mode); establece el modo de display. Las constantes glut GLUT_DOUBLE, GLUT RGB, GLUT DEPTH definen el modo de doble buffer, el modo de color RGB y el buffer de profundidad respectivamente.
    • void glutInitWindowSize(int width, int height); y void glutInitWindowPosition(int x, int y); usados para establecer las dimensiones y la posición inicial de la ventana de salida.
    • int glutCreateWindow(char *name); crea nuestra ventana de salida.

    Para definir las llamadas usamos:

       glutDisplayFunc(display);
       glutIdleFunc(display);
       glutReshapeFunc (resize);
       glutKeyboardFunc (keyboard);
       glutSpecialFunc (keyboard_s);
       init();
       glutMainLoop();
    }
    
    • void glutDisplayFunc(void (*func) (void)); especifica la función a llamar cuando la ventana necesite ser redesplegada, esto es cuando hay una llamada glutPostRedisplay o cuando un error es reportado por el sistema de ventanas.
    • void glutIdleFunc(void (*func) (void)); establece la función de llamada de detenido: la función llamada cada vez que no hay eventos del sistema de ventanas. Esto significa que nuestra función de detenido "display" es llamada constantemente, al menos que los eventos de ventana sean recibidos. Esto mantendrá nuestra animación trabajando.
    • void glutReshapeFunc(void (*func) (void)); establece la función de llamada de remoldeado.
    • void glutKeyboardFunc(void (*func) (void)); la función de llamada de teclado para caracteres ASCII.
    • void glutSpecialFunc(void (*func) (void)); la función de llamada de teclado para caracteres no-ASCII.
    • void glutMainLoop(void); comienza el bucle infinito glut, con procesado de eventos.

    CONCLUSIONES

    ¿Y luego? ¡Asi es, eso es realmente en todo lo que consiste! Lo se, fue algo pesado pero ¡dense cuenta! Ahora son capaces de crear un objeto en 3D rotante! ¡Esto significa que han realizado su primer motor de 3D! En la siguiente lección, estudiaremos como realizar el mapeado de texturas. Por ahora, es momento de darme a conocer tu opinión. Por favor, mandame acerca de cualquier bug que hayas encontrado.

    SOURCE CODE

    The Source Code of this lesson can be downloaded from the Tutorials Main Page