OpenGL + Glut

Capítulo 1 : Inicio

Logo OpenGL

Después de mucho navegar por internet y de ir aprendiendo a programar desde sitios en ingles, y viendo la gran cantidad de personas que quieren aprender a programar con gráficos, he decidido empezar a escribir este documento, pero espero que quede claro, yo, como los que leen esto, estoy siempre aprendiendo más y más de opengl, no piensen que después de haber leído esto van a salir a programar un quake 4 o algo parecido.

Para empezar, este tutorial será sobre opengl usando la librería GLUT, lo cual lo hace mucho más portable a otras plataformas.

Las primeras funciones son para inicializar GLUT, son void glutInit(int argc, char **argv) que es para inicializar GLUT.

Después de haber inicializado GLUT, empezamos seteando nuestra ventana de trabajo, con glutWindowsPosition(int x, int y) donde x e y son las coordenadas de nuestra ventana.

Luego definimos el tamaño de nuestra ventana con glutWindowsSize(int width, int height) donde width y height son largo y ancho de nuestra ventana.

Luego definimos el "modo", con la función glutDisplayMode(unsigned int mode) donde mode puede tomar los siguientes valores:

  • GLUT_RGBA o GLUT_RGB selecciona una ventana con rgb ( rojo, verde, azul) o rgba ( rojo, verde, azul y el canal alfa). El canal alfa se ocupa para transparencias.
  • GLUT_INDEX lo lamento, pero nunca he ocupado esto antes

También se puede seleccionar si se quiere una ventana con doble o un buffer (doble buffer es para animaciones, aunque se puede usar single para animaciones, pero no se vería perfecto).

  • GLUT_SINGLE un buffer.
  • GLUT_DOUBLE doble buffer, para animaciones principalmente.

También se pueden especificar que buffer se quiere :

  • GLUT_ACCUM buffer de acumulación (nunca lo he ocupado).
  • GLUT_STENCIL buffer stencil (no se cual seria la traducción), se ocupa para reflejos y sobras.
  • GLUT_DEPTH buffer de fondo.

Si se quiere una ventana tipo RGB, con buffer simple y depth buffer seria :

glutDisplayMode(GLUT_RGB | GLUT_SINGLE | GLUT_DEPTH);

Después de todos esos pasos, se crea la ventana con el comando glutCreateWindows(char *title), donde title es el titulo de esta. GlutCreateWindows retorna el identificador de la ventana, que se ocupa cuando se definen múltiples ventanas. Así, nuestro código quedaría algo así:

#include  // glut.h incluye opengl.h y glu.h

int main( int argc, char ** argv) {
   glutInit(&argc, argv);
   glutWindowsPosition(100, 100); // Este comando puede ser opcional
   glutWindowSize(320, 320);
   glutCreateWindow("Primer programa GLUT");
}

Si compilan y ejecutan este código, se abrirá una ventana en negro, pues no hemos dibujado nada aún.

Ahora vamos a dibujar algo sencillo en la pantalla:

void dibujar() {
   glClear(GL_COLOR_BUFFER_BIT); // Se borra el buffer de la pantalla
   glBegin(GL_TRIANGLES); // Se va a empezar una secuencia de triángulos.
   glVertex3f(-0.5, -0.5, 0.0); // glVertex3f se usa para definir puntos
                                // con 3 coordenadas que sean float,
                                // glvertex3d para 3 coordenadas que sean double.
   glVertex3f(0.0, 0.0, 0.0);
   glVertex3f(0.0, 0.5, 0.0);
   glEnd();
   // Se termina de definir los triángulos.
}

Uno le puede poner el nombre que quiera a esta función, lo que si debe hacer es decirle a GLUT cual es es nombre de esta, esto se hace por medio de la fusión glutDisplayFunc(void *(func)(void)); donde func es el nombre de la función que hace las tareas de dibujar.

Ahora le avisamos a GLUT que estamos listos para entrar en loop de eventos de la aplicación, esto lo hacemos con la función glutMainLoop();. Ahora nuestro código luce así:

#include 
void dibujar() {
	glClear(GL_COLOR_BUFFER_BIT);	// Se borra el buffer de la pantalla
	glBegin(GL_TRIANGLES);	// Se va a empezar una secuencia de triángulos.
		glVertex3f(-0.5, -0.5, 0.0);	// glVertex3f se usa para definir puntos
		glVertex3f(0.0, 0.0, 0.0);      // con 3 coordenadas que sean flota,
		glVertex3f(0.0, 0.5, 0.0);      // glvertex3d para 3 coordenadas que sean double.
	glEnd();				// Se termina de definir los triángulos.
}

int main(int argc, char **argv) {
   glutInit(&argc,argv);
   glutInitWindowPosition(100,100);
   glutInitWindowSize( 320, 320);
   glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE | GLUT_DEPTH);
   glutCreateWindow("Un Triángulo!!!");

   glutDisplayFunc(dibujar);
   glutMainLoop();
}

Otras funciones necesarias para empezar, son glColor3f( float r, float g, float b); con lo cual definimos el color que vamos a usar.

void dibujar() {
   glClear(GL_COLOR_BUFFER_BIT);	// Se borra el buffer de la pantalla
   glBegin(GL_TRIANGLES);	// Se va a empezar una secuencia de triángulos.
   GlColor3f(1.0, 0.0, 0.0);
   glVertex3f(-0.5, -0.5, 0.0);	// glVertex3f se usa para definir puntos con 3
   glColor3f(0.0, 1.0, 0.0);    // coordenadas que sean float, glvertex3d para 3
   glVertex3f(0.0, 0.0, 0.0);   // coordenadas que sean double.
   glColor3f(0.0, 0.0, 1.0);
   glVertex3f(0.0, 0.5, 0.0);
   glEnd();				// Se termina de definir los triángulos.
}

Con ese nuevo código se obtendrá un triángulo con cada vértice de diferente color.

Otra cosa, si se quieren definir figuras que no sean triángulos, en glBegin, se puede ocupar en vez de GL_TRIANGLES, GL_QUADS para definir polígonos de 4 lados, GL_POLYGON para definir polígonos cualquiera, aunque creo que las tarjetas de video transforman todo a triángulos al momento de mostrarlo. También esta la opcion GL_LINE, para dibujar lineas.

Si se dibuja en 2D, se puede ocupar glVertex2f( float x, float y); o glVertex2d(double x, double y);

Cambiando el tamaño de la ventana

Al cambiar el tamaño de la ventana a veces, no se despliega lo esperado, puesto que los cálculos de perspectiva quedan mal hechos por tener un nuevo tamaño, para solucionar esto, GLUT proporciona una función que llama a una función que recalcule estos valores para casos en que se cambie el tamaño de la ventana. La función es glutReshapeFunc(void *(func)(int width, int height); donde func es la función a la que llamaremos y width y height son los datos que le ingresaremos a esta. Entonces en main() agregamos antes de glutMainLoop(); glutReshapeFunc(cambiarTamano);.

Antes de definir la función voy a explicar otras funciones necesarias, primero, gluPerspective(double fov, double aspect, double near, double far), esta función pone la perspectiva, así se ven mas reales los gráficos que hagamos. fov es el campo de visión que queremos, se define en grados y va en relación con aspect, near y far es cuan cerca y lejos podemos ver, con un menor far, veremos hasta más cerca.

gluLookAt(double eyex, double eyey, double eyez,           // Desde donde miramos
          double centerx, double centery, double cenbterz, // Hacia donde miramos
  	      double upx, double upy, double upz)              // el eje hacia arriba

gluLookAt define dónde esta el observador, hacia donde esta mirando y cual es el eje que nos indica la dirección hacia arriba.

Y ahora vamos a definir esta función :

void cambiarTamano(int largo, int ancho) {
   if(ancho=00) ancho=1;		// Previene que dividamos por 0
   glMatrixMode(GL_PROJECTION);	// Escogemos la matriz de proyección
   glLoadIdentity();		// Se resetea la matriz
   glViewPort(0,0,largo, ancho);	// Se va a usar toda la ventana para mostrar gráficos
   gluPerspective( 45 ,		// ángulo de visión
      (float)lago/(float)ancho, // Razón entre el largo y el ancho, para calcular la perspectiva
      1,					    // Cuan cerca se puede ver
      1000);					// Cuan lejos se puede ver
   glMatrixMode(GL_MODELVIEW);	// Escogemos la matriz de vista
   glLoadIdentity();
   gluLookAt( 0.0, 0.0, 0.0,	// Hacia donde miramos
	 		  0.0, 0.0, -1.0,	// Desde donde miramos
			  0.0, 1.0, 0.0);	// Que eje es el que esta hacia arriba
}

Así es como queda el código.

Capítulo 2: Animaciones

Hasta ahora vamos bien, pero es algo fome todavía, ahora vamos a ver animaciones. GLUT provee una función que controla la aplicación mientras esta está en estado de "ocio", mientras, al parecer no hace nada. Esta función es glutIdleFunc(void *(func)(void)), GLUT llama a ésta función todo el tiempo, haciendo que se logre una animación.

La animación se logra usando doble buffer, se dibuja algo en el buffer secundario, el que no se ve en pantalla, y luego se intercambia por el de la pantalla, si se hacen pequeños cambios, se logra ver una animación.

Es necesario definir nuestra ventana con doble buffer, esto se logra reemplazando los parámetros de glutInitDisplayMode por GLUT_DEPTH | GLUT_double | GLUT_RGB , o sea, cambiar el buffer single por un buffer doble.

En la función dibujar, hemos limpiado el buffer de la pantalla, ahora debemos limpiar el buffer depth, el buffer secundario, para que no tenga cosas previamente dibujadas. Esto se logra usando glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) en la función dibujar.

Y para finalizar, al final de nuestra función dibujar hay que intercambiar los buffer, del secundario al primario, esto se logra con glutSwapBuffers();

Para nuestro ejemplo vamos a definir una variable global angulo, que va a ser el ángulo en que rotamos nuestro triángulo. Para rotar usamos la función glRotatef(float angle, float x, float y, float z), donde angle es el ángulo en cuanto queremos rotar y x, y o z deben ser diferentes de 0 para que rotemos ese eje.

Nuestra función de animación será la misma que dibuja.

float angulo=0.0;

int main(int argc, char **argv) {
	.
	.
	.
	glutIdleFunc(dibujar);
	.
	.
	.
}


void dibujar() {
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);	// Se borran el buffer de pantalla y el depth
   glPushMatrix();		// Guarda la matriz actual en un stack
   glRotatef(angulo, 0.0, 1.0, 0.0);	// Rotamos en engulo sobre el eje y
   glBegin(GL_TRIANGLES);	// Se va a empezar una secuencia de triángulos.
      glColor3f(1.0, 0.0, 0.0);
      glVertex3f(-0.5, -0.5, 0.0);	// glVertex3f se usa para definir puntos con 3 coordenadas que sean flota, glvertex3d para 3 coordenadas que sean double.
      glColor3f(0.0, 1.0, 0.0);
      glVertex3f(0.0, 0.0, 0.0);
      glColor3f(0.0, 0.0, 1.0);
      glVertex3f(0.0, 0.5, 0.0);
   glEnd();				// Se termina de definir los triángulos.
   glPopMatrix();		// Recupera la matriz guardada en el stack
   glutSwapBuffers();
   angulo++;
}

La función glRotated(double angle, double x, double y, double z) es similar a glRotatef, pero ocupa valores double en vez de valores float.

La función para mover dentro del espacio es glTranslatef(float x, float y, float z) o glTranslated(double x, double y, double z), estas funciones mueven en x unidades sobre el eje x, y unidades sobre el eje y, y z unidades sobre el eje z.

La función para agrandar o achicar objetos es glScalef(float x, float y, float z) o glScaled(double x, double y, double z) los que agrandan o achican el tamaño de un objeto, multiplicando su medida en x por x, y asi sucesivamente. Si alguno de los factores es menor a 1 y mayor a 0, entonces los objetos se achican.

Otra función es glClearColor(r ,g ,b ,a) la cual podrá el color de borrado al color seleccionado mediante RGBA. Por ejemplo, si r, g y b son 1.0, entonces el fondo será blanco. La variable a es conveniente dejarla en 0.0 .

Capítulo 3: Pequeñas mejoras e iluminación básica

Hay algunas funciones que podemos usar para que los objetos desplegados es pantalla se vean mejor, para esto crearemos una función que llamaremos void inicializar(), y dentro de ésta pondremos todo estos códigos.

Los valores difuso (luz o material) es el color del objeto, el ambiente, el color del lugar más oscuro, especular el color del lugar con mayor luz, y shininess, la concentracion de puntos luminosos.

GLfloat luzdifusa[]= { 1.0, 1.0, 1.0, 1.0 };   // Definimos valores de luz difusa,
                                               // máxima intensidad de luz blanca
GLfloat luzambiente[]= { 0.5, 0.5, 0.5, 1.0 }; // Definimos valores de luz ambiente
GLfloat luzspecular[]= { 0.0, 0.0, 0.0, 0.0};  // Definimos el valor specular de la luz
GLfloat luzemision[]= { 0.0, 0.0, 0.0, 0.0};   // Definimos el valor de emisión de la luz
GLfloat posicion[]= { 1.0, 1.0, 1.0 , 0.0};    // La posición de la luz
GLfloat brillo = 10;                           // Definimos el brillo de la luz
                                               // (exponente de una ecuación)

void inicializar() {
  glShadeModel(GL_SMOOTH);  // Esto hace que al dibujar las sombras en los objetos,
                            // se vean mas parejas haciendo que se vean mejor.
  glClearColor(0.0, 0.0, 0.0, 0.0); // Pondremos acá la función glclearcolor
  // Las 3 líneas siguientes hacen que el depth buffer ordene los objetos que deben
  // ser puestos primero en pantalla y los que deben ser dibujados después dependiendo
  // de cuan cerca están de la cámara.
  glClearDepth(1.0);
  glEnable(GL_DEPTH_TEST);
  glDepthFunc(GL_LEQUAL);

  glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);   // Esto hace que opengl calcule las
                                                       // perspectivas de la mejor forma,
                                                       // quita un poco de rendimiento, pero
                                                       // hace que las perspectivas se vean
                                                       // un poco mejor.
                                                       // Ahora vamos a inicializar las luces.
  glLightfv(GL_LIGHT0, GL_DIFFUSE, luzdifusa);    // Ponemos valores a la luz 0
  glLightfv(GL_LIGHT0, GL_AMBIENT, luzambiente);  // Ponemos valores a la luz 0
  glLightfv(GL_LIGHT0, GL_SPECULAR, luzspecular); // Ponemos valores a la luz 0
  glLightfv(GL_LIGHT0, GL_EMISSION, luzemision);  // Ponemos valores a la luz 0
  glLightfv(GL_LIGHT0, GL_SHININESS, brillo);     // Ponemos valores a la luz 0
  glLightfv(GL_LIGHT0, GL_POSITION, posicion);    // Ponemos la posición para gl_light1
  glEnable(GL_LIGHTING);                          // Activamos la iluminación.
  glEnable(GL_LIGHT0);                            // Ahora ponemos la luz que ya definimos
}

Para probar la luz y las correcciones, vamos a dibujar algunos objetos 3D, usando los predefinidos de la librería GLUT, como glutSolidTeapot. Se puede probar cambiando los valores de cada variable que define a la luz, y hay que decir que no son obligatorios todos los parámetros, si se quiere luz, por lo menos hay que definir la posición y después usar glEnable(GL_LIGHT0) y glEnable(GL_LIGHTING).

Ahora para darle colores a los objetos bajo luz, hay que definirles un material, esta definición es parecida a la definición de luz, las variables son parecidas, y se implementan con glMaterialfv.

GLfloat material_difuso[]={1.0, 1.0, 0.5, 0.0};    // Definición de un material
GLfloat material_ambiente[]={0.0, 0.0, 0.0, 0.0};  // Definición de un material
GLfloat material_specular[]={1.0, 1.0, 0.0, 0.0};  // Definición de un material
GLfloat material_emission[]={0.0, 0.0, 0.0, 0.0};  // Definición de un material
GLfloat brillo=90;                                 // Definición de un material

Y luego los llamamos antes de dibujar el objeto al cual le queremos poner esta materia.

glMaterialfv(GL_FRONT, GL_DIFFUSE, material_difuso);
glMaterialfv(GL_FRONT, GL_AMBIENT, material_ambiente);
glMaterialfv(GL_FRONT, GL_SPECULAR, material_specular);
glMaterialfv(GL_FRONT, GL_EMISSION, material_emission);
glMaterialfv(GL_FRONT, GL_SHININESS, brillo);

Y efectuamos los dibujos.

Capítulo 4: Texturas

Hasta ahora sólo dibujamos y coloreamos, pero ahora le vamos a poner texturas a nuestros programas, algo que es muy útil para crear escenas mas reales y fáciles. No sería muy buena idea dibujar un mundo de pequeños polígonos de diferentes colores, mejor uno grande y le ponemos nuestra textura. Este código es prácticamente una traducción del que existe en la pagina nehe.gamedev.net, capítulo 25, si quieres saber más, este es un sitio genial (en ingles).

Para empezar hay que definir lo que va a guardar a nuestra textura, esto lo hacemos mediante una estructura:

typedef struct {
  GLuyte *dibujo; // Un puntero a los datos de la imagen
  GLuint bpp;     // bpp significa bits per pixel (bits por punto)
                  // es la calidad en palabras sencillas
  GLuint largo;   // Largo de la textura
  GLuint ancho;   // Ancho de la textura
  GLuint ID;      // ID de la textura, es como su nombre para opengl
} textura;

textura  texturas[1]; // Definimos nuestras texturas, por ahora solo 1

Listo, ahora hay que empezar a cargar el código en la memoria. El código que viene a continuación solo carga tga de 24 o 32 bits sin compresión. cabezeraTGA va a guardar 12 bytes que describen un archivo tga, compararTGA, va a ser donde guardamos los primeros 12 bytes leídos del archivo y los comparamos con los de cabezeraTGA. cabezera, guarda los 6 mas importantes bytes (largo, ancho y bpp).

int cargarTGA( char *nombre, textura *imagen) {
  GLubyte  cabezeraTGA[12]={0,0,2,0,0,0,0,0,0,0,0,0};
  // Cabecera de un tga sin compresión
  GLubyte  campararTGA[12];   // Acá vamos a comprar la cabecera
  GLubyte  cabezera[6];       // Los 6 bytes importantes
  GLuint   bytesporpunto;     // Cuantos bytes hay por punto
  GLuint   tamanoimagen;      // Acá guardaremos el tamaño de la imagen
  GLuint   temp,i;            // Variable temporal, y una para usar con el for
  GLuint   tipo=GL_RGBA;      // Nuestro tipo por defecto, lo veremos mas abajo

  FILE *archivo=fopen(nombre, "rb"); // Cargamos nuestro archivo en memoria
  if( archivo == NULL ||                // Existe nuestro archivo??
      fread(compararTGA,1,sizeof(compararTGA),FILE)!=sizeof(compararTGA) ||
      // Hay 12 bytes para leer??
      emcmp(cabezeraTGA,compararTGA,sizeof(compararTGA))!=0 ||
      // Son iguales??
      fread(cabezera,1,sizeof(cabezera),archivo)!=sizeof(cabezera)) {
    if(archivo==NULL)
      return 0;  // No se abrió el archivo
    else {
      fclose(archivo);
      return 0;
    }
  }
  /* Ahora hay que leer la cabecera del archivo, para saber cuanto es el
     largo, ancho, y los bytes por puntos,
     para eso acá hay una ilustración de la cabecera :
     6 bytes -> xxxxxxx xxxxxxx xxxxxxx xxxxxxx xxxxxxx xxxxxxx
                |--- Largo ---| |---Ancho-----| |-bpp-|
     El dato del largo se guarda en el cabezera[0] y cabezera[1], para
     leerlo hay que multiplicar cabezera[1] por 256 y sumarselo a cabezera[0],
     para leer ancho y bpp es el mismo procedimiento */
  imagen->largo=256*cabezera[1]+ cabezera[0];
  imagen->ancho=256*cabezera[3]+ cabezera[2];

  /* Ahora vemos si hay datos no válidos, como largo o ancho iguales
     menores a 0 o iguales a 0 */
  if( imagen->largo <= 0 ||	// Largo mayor que 0??
      imagen->ancho <= 0 ||	// Ancho mayor que 0??
      (cabezera[4]!=24 && cabezera[4]!=32)) {
      // bpp es 24 o 32?? (solo se cargan 24 y 32 bpp)
    fclose(archivo);
    return 0;
  }
  imagen->bpp=cabezera[4];     // Acá se guardan los bits por punto
  bytesporpunto=cabezera[4]/8; // Acá los bytes por punto (1 byte = 8 bits)
  tamanoimage=imagen->largo * imagen-&gt;ancho * bytesporpunto;
  // Esta es la memoria que necesitaremos para guardar los datos de la textura
  /* Ahora reservamos espacio en memoria para nuestra textura, luego leemos la
     textura del archivo */
  imagen->dibujo = (GLubyte *)malloc(tamanoimagen);
  // Reservamos la memoria necesaria para nuestra textura
  if(imagen->dibujo== NULL ||  // Se logró reservar la memoria???
     fread(imagen->dibujo, 1, tamanoimagen, archivo) != tamanoimagen ) {
        // Se lee, y se comprueba que lo leído es de la
        // misma longitud que la asignada a a dibujo.
        if(imagen->dibujo != NULL)
           free(imagen->dibujo);
        fclose(archivo);
        return 0;
  }
  /* El formato tga guarda las imágenes en BGR, y opengl usa RGB, por lo
     cambiamos de lugares */
  for(i=0; i < int(imageSize); i+=bytesporpunto)
  {
    temp=imagen->dibujo[i];                    // Guardamos el primer valor
    imagen->dibujo[i] = imagen->dibujo[i + 2]; // Asignamos el nuevo primer valor
    imagen->dibujo[i + 2] = temp;              // Asignamos el ultimo valor
  }

  fclose (archivo);	// Cerramos el archivo

  /* Listo, terminamos con el código de carga, volvemos a opengl, ahora hay
     que asignarle a la textura un ID, luego decirle a opengl cuales son el
     largo, el ancho y los bpp */

  glGenTexture(1,&imagen->ID);	// Crea un ID para la textura, buscando un id que este vacío
  glBindTexture(GL_TEXTURE_2D, imagen->ID);	// Seleccionamos nuestra textura
  if(imagen->bpp == 24) tipo= GL_RGB;	// Si nuestra textura es de 24 bits, entonces
                                       // se crea una textura rgb, sino una rgba
  /* Ahora creamos nuestra textura, entrando el largo, ancho y tipo */
  glTexImage2D(GL_TEXTURE_2D, 0, tipo, imagen-&gt;ancho, imagen->largo, 0,
     tipo, GL_UNSIGNED_BYTE, imagen->dibujo);
  /* Ahora le decimos a opengl como queremos que se vea nuestra textura, MAG_FILTER
     es cuando la textura es mas grande que el lugar donde la asignamos, y MIG_FILTER,
     es cuando la textura es mas pequeña que el lugar donde la asignamos, GL_LINEAR es
     para que se vea bien tanto cerca como lejos, pero ocupa bastante procesador.
     Otra opción el GL_NEARES, que ocupa menos procesador */
  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);  // La m
  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  return 1;	// Todo salió bien
}

Tenemos lista la función para cargar las texturas a memoria, ahora hay que decirle a opengl que vamos a usar texturas y debemos cargarlas.

void inicializar() {
  .
  .
  .
  glEnable(GL_TEXTURE_2D);
  if(!cargarTGA("textura.tga", texturas[0])) exit();
    // Cargamos la textura y chequeamos por errores
}

Listo, textura en memoria, ahora hay que usarla en algo.

void dibujar () {
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);   // Se borra el buffer de la pantalla
  glRotatef(angulo, 0.0, 1.0, 1.0);	// Rotamos nuestro objeto
  glBingTexture(texturas[0]->ID);
  // Vamos a usar un cubo
  glBegin(GL_QUADS);
  /* La textura es una imagen cuadrada, ahora hay que asignarle los lugares donde va,
     para eso esta glTexCoord2f(float x, float y) donde x e y son las coordenadas de la
     textura que van a coincidir con el punto que vamos a definir */
  glTexCoord2f(0.0, 0.0); glVertex3f(-1.0, -1.0,  1.0);
  glTexCoord2f(1.0, 0.0); glVertex3f( 1.0, -1.0,  1.0);
  glTexCoord2f(1.0, 1.0); glVertex3f( 1.0,  1.0,  1.0);
  glTexCoord2f(0.0, 1.0); glVertex3f(-1.0,  1.0,  1.0);

  glTexCoord2f(1.0, 0.0); glVertex3f(-1.0, -1.0, -1.0);
  glTexCoord2f(1.0, 1.0); glVertex3f(-1.0,  1.0, -1.0);
  glTexCoord2f(0.0, 1.0); glVertex3f( 1.0,  1.0, -1.0);
  glTexCoord2f(0.0, 0.0); glVertex3f( 1.0, -1.0, -1.0);

  glTexCoord2f(0.0, 1.0); glVertex3f(-1.0,  1.0, -1.0);
  glTexCoord2f(0.0, 0.0); glVertex3f(-1.0,  1.0,  1.0);
  glTexCoord2f(1.0, 0.0); glVertex3f( 1.0,  1.0,  1.0);
  glTexCoord2f(1.0, 1.0); glVertex3f( 1.0,  1.0, -1.0);

  glTexCoord2f(1.0, 1.0); glVertex3f(-1.0, -1.0, -1.0);
  glTexCoord2f(0.0, 1.0); glVertex3f( 1.0, -1.0, -1.0);
  glTexCoord2f(0.0, 0.0); glVertex3f( 1.0, -1.0,  1.0);
  glTexCoord2f(1.0, 0.0); glVertex3f(-1.0, -1.0,  1.0);

  glTexCoord2f(1.0, 0.0); glVertex3f( 1.0, -1.0, -1.0);
  glTexCoord2f(1.0, 1.0); glVertex3f( 1.0,  1.0, -1.0);
  glTexCoord2f(0.0, 1.0); glVertex3f( 1.0,  1.0,  1.0);
  glTexCoord2f(0.0, 0.0); glVertex3f( 1.0, -1.0,  1.0);

  glTexCoord2f(0.0, 0.0); glVertex3f(-1.0, -1.0, -1.0);
  glTexCoord2f(1.0, 0.0); glVertex3f(-1.0, -1.0,  1.0);
  glTexCoord2f(1.0, 1.0); glVertex3f(-1.0,  1.0,  1.0);
  glTexCoord2f(0.0, 1.0); glVertex3f(-1.0,  1.0, -1.0);
  glEnd();
  angulo++;
  glutSwapBuffers();
}

Listo!!! tenemos texturas!! ahora casi podemos hacer un Quake IV!!! bueno... intenta.

Programas en C

Nombre Fichero Fecha Tamaño Contador Descarga
Ejemplos y texturas glut.zip 2001-05-01 55169 bytes 471