OpenGL + Glut
Capítulo 1 : Inicio
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 <gl/glut.h> // 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 <gl/glut.h> 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->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->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 | 624 |