Para programar desde C con OpenGL necesitarás linkar las librerías: opengl32.lib y glu32.lib. Necesitarás también incluir los encabezados: gl\gl.h (OpenGL en general) y gl\glu.h (utilidades).
hWnd = CreateWindow ( "NUESTRA_CLASE", "Ventana de ejemplo", WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_VISIBLE, 0, 0, 500, 500, NULL, NULL, hInstance, NULL); // AQUÍ DEBERÍAMOS CONFIGURAR LA VENTANA PARA SOPORTAR OPENGL. While (!quit) { if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { if ( msg.message == WM_QUIT ) quit = TRUE; else { TranslateMessage( &msg ); DispatchMessage( &msg ); } } else Render(); // Función que dibuja con OpenGL } return msg.wParam;
void setupogl(HWND hwnd) { int nPixelFormat; hDC=GetDC(hwnd); // hDC es un HDC // Asignamos los valores al formato de pixel PIXELFORMATDESCRIPTOR pfd; pfd.nSize = sizeof( pfd ); pfd.nVersion = 1; pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; // Soporte para OGL y Doble buffer. pfd.iPixelType = PFD_TYPE_RGBA; // Color RGBA. pfd.cColorBits = 24; // De 24bits. pfd.cDepthBits = 16; // Buffer de profundidad. pfd.iLayerType = PFD_MAIN_PLANE; // Se dibujará en el plano principal. // Asignamos el nuevo formato de pixel a la ventana nPixelFormat = ChoosePixelFormat (hDC, &pfd); SetPixelFormat (hDC, nPixelFormat, &pfd); // Creamos el contexto para OpenGL (indicamos que será OpenGL el encargado de dibujar sobre la ventana). HGLRC hRC=wglCreateContext(hDC); // Crea y activa el contexto de generación wglMakeCurrent(hDC, hRC); // Limpiamos con el color escogido glClearColor (0.0f,0.0f,0.0f,1.0f); }
Necesitamos configurar el sistema de coordenadas de nuestra ventana, para ello lo que hacemos es:
glViewport, con esta indicamos la porción donde trabajaremos, es decir, será el lugar donde se vaya renderizando, si este es cuadrado el render será cuadrado, si es rectangular el render será rectangular.void ConfiguraTamano (GLsizei w, GLsizei h) { if (h<=0) h=1; if (w<=0) w=1; glViewport (0,0,w,h); glMatrixMode (GL_PROJECTION); glLoadIdentity(); glOrtho (0,w, 0,h, 100, -100); glMatrixMode (GL_MODELVIEW); glLoadIdentity(); }
Llamaríamos a esta función desde el mensaje WM_SIZE del WndProc de la siguiente forma:
ConfiguraTamano(LOWORD(lParam), HIWORD(lParam));
Cuando se muestra la ventana por primera vez se genera un mensaje WM_SIZE con el tamaño de esta, por lo que no sería necesario llamar a esta función al inicio del programa. Además si la ventana cambia de tamaño necesitaremos reiniciar nuestro sistema de coordenadas, por lo que la llamada desde WM_SIZE no es mala opción.
Para ello necesitamos guardar el tamaño con el que se crea la ventana (ALT y ANC). Necesitamos también el tamaño de la vista en pixels (RangoXYZ) y el alto y ancho actuales (NewALT y NewANC):
const float ALT=500.0, ANC=500.0; GLfloat RangoXYZ=5.0f; GLfloat NewALT, NewANC;
La clave está en la función ConfiguraTamano, esta realiza una regla de 3 con el rango inicial: Si para la altura inicial equivalía a RangoXYZ, cuanto equivale para la altura actual?.
void ConfiguraTamano (GLsizei w, GLsizei h) { float NewRangoX=(RangoXYZ*w)/ANC; float NewRangoY=(RangoXYZ*h)/ALT; NewALT=NewRangoY; NewANC=NewRangoX; if (h<=0) h=1; if (w<=0) w=1; glViewport (0,0,w,h); glMatrixMode (GL_PROJECTION); glLoadIdentity(); glOrtho (-NewRangoX, NewRangoX, -NewRangoY, NewRangoY, -RangoXYZ, RangoXYZ); glMatrixMode (GL_MODELVIEW); glLoadIdentity(); }
glClear(GL_COLOR_BUFFER_BIT);. Llamandolo al inicio del dibujo, borra la vista mostrando un color plano. Podemos introducir también: GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT lo que borrará el buffer de profundidad. Podemos decir con qué color se hará el borrado mediante glClearColor.SwapBuffers( hDC );. Llamandolo al final dibujo, imprime los contenidos del buffer.glColor3f(x.xf, x.xf, x.xf);. Antes del dibujo de una primitiva, elige su color. El 3 de su nombre significa los colores RGB y puede ser intercambiado por 4 para colores RGBA. La f corresponde al tipo de datos (i (integer), b(byte), ub (unsigned byte)…) es decir que si hacemos glColor3ub podemos usar la nomenclatura de 256 bytes para el color R(0…255), G(0…255), B(0…255).glEnable(elemento); y glDisable(elemento);. Activa o desactiva el elemento que se pasa por parámetro, si luego queremos saber si ese parámetro está activo tendremos que pasarselo a glIsEnabled(elemento);. El parámetro puede ser cualquiera de los siguientes tipos ya definidos:glFrontFace(GL_CCW) o no glFrontFace(GL_CW). Por defecto se dibuja en sentido contrario a las agujas del reloj.glShadeModel(GL_FLAT);. Define como se pintarán las primitivas:GL_FLAT con un color plano.GL_SMOOTH pinta a partir de los vertices (dando colores a los vertices) mediante un suavizado.glPolygonMode(r,s);. Activa que polígono mostrará:gluLookAt Corresponde a la camara. Tiene 9 parametros GLdouble:// Ejemplo 1: gluLookAt(0.0, 1.0, 10.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); // Ejemplo 2: gluLookAt(0.0,0.0,-10.0, // como si hiciesemos un glTranslatef(0.0,0.0,-10.1) desde 0.0 0.0,0.0,0.0, // al lugar donde apunta 0.0,1.0,0.0);
glColorMask (BOOL,BOOL,BOOL,BOOL); Cada booleano corresponde a un valor RGBA. Aplica una máscara a la figura. Por ejemplo un cuadrado verde que atraviesa un cuadrado rojo, si todo es true excepto el valor de rojo el cuadrado se volverá amarillo al pasar por lo rojo. Por defecto todos los valores son TRUE para que puedan visualizarse todos los colores en la escena.
Si no está activado las caras traseras pueden llegar a dibujarse sobre las delanteras, es decir, las superficies que no se deberían de dibujar se dibujan.
Para activarlo haremos una llamada a glEnable pasandole GL_DEPTH_TEST:
glEnable (GL_DEPTH_TEST);
Una vez hecho esto necesitaremos, al repintar la escena y borrarla previamente llamando a glClear indicar también que se borre dicho buffer:
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
Para indicar con qué valor (entre 0 y 1) limpiaremos el buffer de profundidad utilizaremos la función glClearDepth. Por defecto su valor es 1.0f.
glClearDepth(1.0f);
En OGL las funciones glVertex3f, glVertex2f… Se utilizan para especificar un punto en el área de dibujo, la agrupación de un número de estos puntos forman las primitivas (polígonos).
glVertex3f(valorxf, valoryf, valorzf); glVertex2f(valorxf, valoryf); // glVertex2f asume que el valor z es 0.0f
glBegin y otra a glEnd, en medio de estas dos indicaremos tantos vertices (puntos) como la primitiva que dibujemos requiera. A glBegin se le pasa por parámetro el identificador del tipo de primitiva.glBegin(GL_POINTS); glVertex2f(0.0f, 0.0f); glEnd();
glPointSize(número); que no es necesario que vaya entre el glBegin ni el glEnd.glLineWidth(número); y un formato mediante glLineStipple(variableint, 0x5555); siendo la variable int el patrón de formato (para activar el formato tendremos que hacer una llamada a glEnable(GL_LINE_STIPPLE);.glFrontFace(GL_CW). Si queremos volver a la normalidad le pasaremos el parámetro: GL_CCW.glBegin(GL_LINE_STRIP); for (i=0.0f; i<=(2.5f*GL_PI); i+=0.1f) { x=0.5f*sin(i); y=0.5f*cos(i); glVertex2f(x,y); } glEnd(); // Siendo GL_PI=3.14
Las transformaciones transformaciones las podemos hacer sobre:
OpenGL utiliza matrices para manejar los datos, existen tres tipos\modos y la elección de este “modo de matriz” se lleva a cabo según el parámetro que le pasemos a glMatrixMode:
gluLookAt) se utiliza en la MODELVIEW): glLoadIdentity, gluPerspective, glFrustum, glOrtho, gluOrtho2.Es la matriz sobre la que se trabaja inicialmente y a partir de la que se cran las demás matrices. Podemos hacer distintos tipos de transformaciones:
glTranslatef(X,Y,Z);glRotatef(Angulo, X, Y, Z);glScalef(X,Y,Z);. Esta función no hace nada si se le llama como glScalef(1,1,1);, ya que deja tal cual los polígonos, pero los puedes escalar a la mitad llamandola glScalef(0.5,0.5,0.5); o invertirla verticalmente glScalef(1,-1,1);
Para reiniciarla, es decir, antes de empezar a dibujar la escena (piena que esta matriz se “deforma” al ir dibujando y colocando los elementos) llamaremos a glLoadIdentity() que carga la matriz identidad (la inicial). Por lo tanto, para mover dos figuras hacia distinto lugar, aunque generalmente se utiliza la pila de matrices también podemos, primero mover una con glTranslatef y antes de mover la otra reiniciaremos la matriz con glLoadIdentity(); y luego moveremos la otra.
El concepto consiste en, teniendo una matriz inicial añadida a la pila, añadimos una segunda matriz que será una copia de la anterior hacemos las transformaciones necesarias sobre esta y dibujamos, luego podemos volver a añadir otra matriz (que sería una copia de la anterior) o sacar esta de la pila. Usar el tipo de matriz GL_MODELVIEW.
glPushMatrix();glPopMatrix();glLoadIdentity(); // Cargamos la matriz identidad glPushMatrix(); // Añadimos una matriz a la pila glTranslatef(0.5, 0.0, 0.0); // Moveremos lo que dibujemos a la derecha glBindTexture(GL_TEXTURE_2D, this->texture); drawCuad(); glPushMatrix(); // Añadimos una segunda matriz a la pila glTranslatef(0.0, 0.5, 0.0); // Moveremos lo que dibujemos hacia arriba, es decir, estará arriba-derecha glBindTexture(GL_TEXTURE_2D, this->texture); drawCuad(); glPopMatrix(); // Sacamos la segunda matriz, lo dibujado queda a la derecha glPopMatrix(); // Sacamos la primera matriz, lo dibujado queda en el centro glPushMatrix (); // Añadimos una matriz glTranslatef(-0.5, 0.0, 0.0); // Lo que dibujemos a partir de ahora quedará a la izquierda glBindTexture(GL_TEXTURE_2D, this->texture2); drawCuad (); glPopMatrix();
Otro ejemplo:
GlClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); GlMatrixMode (GL_MODELVIEW); // Modelaremos la matriz del modelador. glRotatef(1.0f,0.0f,0.1f,0.0f); // Se rotará todo el dibujo durante todo el rato. glPushMatrix(); // Se crea una matriz. glRotatef(150.0,0.0f,0.1f,0.0f); // Se rota lo que hay en la matriz hasta el punto elegido, como no hay una variable que se incremente sólo se efectuará una vez. glPushMatrix(); // Se crea otra matriz glRotatef(-30.0f,1.0f,0.0f,0.0f); // Lo mismo que en la matriz anterior, colocaremos los elementos. glPushMatrix(); // Otra matriz para la cola glRotatef(xse,0.0f,0.1f, 0.0f); // Esta rotación se efectuará mientras “xse” vaya cambiando. DibujaCuadrao (0.0f,0.0f,0.0f,0.5f); glPopMatrix(); // Cerramos la última matriz abierta. DibujaSuelo(); glPopMatrix(); // Cerramos segunda matriz abierta. glPopMatrix(); // Cerramos primera matriz abierta. SwapBuffers( hDC ); Trans=0.0f; xse+=0.9f;
En el siguiente ejemplo se nos muestra dos cubos, uno rotando en su eje de las X y el otro en el suyo de las y. En el que rota en el eje de las y, si no pusiesemos ese glTranslatef el cubo no giraría sobre sí mismo, sino sobre el eje y(0). Al crear una nueva matriz tenemos que mover el punto 0 y rotarla. Así sí que rota sobre sí mismo.
glPushMatrix(); glRotatef(-XRot,1.0f,0.0f,0.0f); CreaCubo(-4.5f,0.0f,0.0f,2.0f); glPopMatrix(); glPushMatrix(); glTranslatef(4.5f,0.0f,0.0f); glRotatef(XRot,0.0f,1.0f,0.0f); CreaCubo(0.0f,0.0f,0.0f,2.0f); glPopMatrix();
glLoadMatrix(matriz); → Siendo matriz un vector de este tipo:GLfloat matriz[] = { 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f };
Carga una matriz creada por nosostros.
glMultMatrix(matriz); → Multiplica la matriz actual por la matriz especificada.
En el modo de coordenadas ortogonal modo no existe profundidad y los objetos no están dispuestos como si hubiese un punto de fuga. Es decir, los más cercanos no se dibujan más grandes que los más lejanos sino que todos se dibujan con el mismo tamaño. El control de coordenadas se hace definiendo la vista con una llamada a glOrtho.
Su sintaxis es: glOrtho(-x,x,-y,y,-z,z); y estos parámetros significan:
glOrtho (-3.0f, 3.0f, -3.0f, 3.0f, -3.0f, 3.0f);
En este ejemplo el centro de la vista estará en el centro de la ventana. Habrán 3.0 “pixels” desde un lado al centro. O desde arriba… O 3.0 de profundidad desde el usuario a la vista y desde la vista al fondo de la ventana.
glOrtho (0, 3.0f, 0, 3.0f, 0, 3.0f);
En este el centro de la vista estará en la esquina derecha de la parte de debajo de la ventana (Para OpenGL ese es el punto (0,0)). Sin haber ningún pixel de profundidad entre el usuario y la vista pero desde la vista al fondo de la ventana habrían 3.
Esta proyección se consigue sustituyendo el glOrtho (proyección ortogonal) por gluPerspective (habiendo hecho el include a <gl\glu.h>).
gluPerspective tiene como parámetros:
GLfloat fAspect = (GLfloat)w/(GLfloat)h; gluPerspective(60.0f,fAspect,1.0,40.0); ... // Otro ejemplo gluPerspective(60.0, fAspect, 1.0, 500.0);
Ver relación con la cámara.
Cuando iniciamos el programa y lo configuramos en perspectiva, deberemos definir la cámara con gluLookAt. Si la ventana donde estamos trabajando se reinicia deberemos volver a configurar la perspectiva. Aquí el código para reiniciar la vista:
glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(this->angle, (float)w / (float)h, 0.1f, lengthView + 0.1f); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); gluLookAt(0.0f, 0.0, -0.2f, 0.0f, 0.0f, lengthView, 0.0f, 1.0f, 0.0f);
Para usarla debemos activarla con un glEnable(GL_LIGHTING);.
Un material puede reflejar tres tipos de luces:
La base de la iluminación es el cómo los materiales reflejan la luz. Existen dos formas de definir este “cómo”:
glMaterial, que se ha de llamar antes de mostrar un polígono o grupo de polígonos y tendrá efecto hasta que otra llamada a glMaterial sea hecha. Recibe por parámetros: Glfloat gray[] = { 0.75f, 0.75f, 0.75f, 1.0f }; ... glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gray); glBegin(GL_TRIANGLES); glVertex3f(-15.0f,0.0f,30.0f); ...
glColor. Podemos definir qué tipos de luz reflejan los materiales con la función: glColorMaterial (donde, tipo de luz>);glEnable(GL_COLOR_MATERIAL); glColorMaterial(GL_FRONT,GL_AMBIENT_AND_DIFFUSE); ... glcolor3f(0.75f, 0.75f, 0.75f); glBegin(GL_TRIANGLES); glVertex3f(-15.0f,0.0f,30.0f); ...
Una vez activado el GL_LIGHTING debemos definir la luz ambiental de la escena y pasarsela a la función glLightModelfv indicandole que realmente es el modelo ambiental el que vamos a usar.
GLfloat LuzAmbient[]={1.0f,1.0f,1.0f,1.0f}; → La luz por defecto, blanca (lo refleja todo).glLightModelfv indicandole que es la luz ambiente (GL_LIGHT_MODEL_AMBIENT): GlLightModelfv(GL_LIGHT_MODEL_AMBIENT, LuzAmbient);
Una normal es un vector en posición de línea perpendicular (90º) sobre una superficie. Aunque podemos indicar la normal del polígono generalmente las indicaremos por cada vértice de este (hacerlo por polígono sería como si este correspondiese a una porción de una superficie lisa).
Por ejemplo imaginemos un plano que está plano en una posición (x,1,0), un vértice podría estar pefectamente en la (1,1,0), pues una normal a este vertice sería un vector desde este al (1,10,0). Para especificar un vector necesitamos dos puntos, el inicio y el final, la normal podemos especificarla restando los dos vértices, es decir, esta normal sería: (0,9,0).
Esta normal también es llamada “la normal transladada”.
Para indicar una normal, antes de indicar el vértice llamaremos a glNormal3f.
Para encontrar la normal de un vértice (normalizar ese vértice), lo que se hace es hacer el producto cruzado con los otros dos vértices contiguos.
Para un triangulo de 3 puntos (p1, p2 y p3). Para el vector U = p2 - p1 y para el vector V = p3 - p1, el vectro normal sería N = U x V calculado como:
void findNormal (floa result[3], float point1[3], float point2[3], float point3[3]) { GLfloat v1[3],v2[3]; v1[0] = point1[0] - point2[0]; v1[1] = point1[1] - point2[1]; v1[2] = point1[2] - point2[2]; v2[0] = point2[0] - point3[0]; v2[1] = point2[1] - point3[1]; v2[2] = point2[2] - point3[2]; result[0] = v1[1]*v2[2] - v2[1]*v1[2]; result[1] = -v1[0]*v2[2] + v2[0]*v1[2]; result[2] = v1[0]*v2[1] - v2[0]*v1[1]; }
Hay que tener especial cuidado en el orden de cálculo, sobretodo si los polígonos han sido dibujados en el orden de las agujas del reloj o no.
Para que OpenGL pueda usa aplicar la normal todas las normales que indiquemos deberán ser vectores unidad (unit normal), esto es, una normal que tenga longitud 1. Para saber qué longitud tiene un vector buscaremos su magnitud, si queremos conseguir un vector unidad únicamente deberemos dividir cada coordenada del vector entre su magnitud, el vector resultante apuntará al mismo sitio pero tendrá longitud 1.
Aún así, podemos hacer que OpenGL convierta todas las normales a vectores unidad automáticamente utilizando glEnable(GL_NORMALIZE);. Pero esto tiene un problema, si las figuras a las que hemos calculado las normales son escaladas las normales cambiarán, debemos decir a OpenGL que también active el reescalado automático de las normales mediante una función alternativa: glEnable(GL_RESCALE_NORMALS);.
Es decir, lo mejor es indicar, al crear un vértice, la unit normal y activar GL_RESCALE_NORMALS.
También podemos crear una fuente de luz. OpenGL tiene ocho fuentes de luz disponibles (desde GL_LIGHT0 a GL_LIGHT7 (el máximo de luces lo indica la constante GL_MAX_LIGHTS)). Antes de activar cada una de ellas tendremos que configurarla con: glLightfv(luz, parámetro de iluminación, vector con los datos necesarios);
glLightfv(GL_LIGHT0, GL_POSITION, lightPos);
Luego tendremos que activarla explícitamente con glEnable (GL_LIGHT0);
GLfloat ambientLight[] = { 0.3f, 0.3f, 0.3f, 1.0f }; GLfloat diffuseLight[] = { 0.7f, 0.7f, 0.7f, 1.0f }; GLfloat specular[] = { 1.0f, 1.0f, 1.0f, 1.0f}; glEnable(GL_LIGHTING); glLightfv(GL_LIGHT0,GL_AMBIENT,ambientLight); glLightfv(GL_LIGHT0,GL_DIFFUSE,diffuseLight); glLightfv(GL_LIGHT0,GL_SPECULAR,specular); glEnable(GL_LIGHT0);
Para darle a una luz las propiedades especulares se hace de la misma forma que con las otras luces. En el ejemplo anterior sería: glLightfv(GL_LIGHT0, GL_SPECULAR, <array de floats>);
Para que se produzca reflectancia en los materiales tenemos que tener activo GL_COLOR_MATERIAL, pasandole los parámetros GL_FRONT y GL_AMBIENT_AND_DIFUSE.
glMaterialfv → Produce la superficie que refleja la luz especular.glMateriali → Desde aquí indicamos el brillo del material. Parámetros: Desde donde, brillo (GL_SHININESS), cantidad (entero).glEnable(GL_COLOR_MATERIAL); glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE); glMaterialfv(GL_FRONT, GL_SPECULAR, spec); glMateriali(GL_FRONT, GL_SHININESS, 5);
El crear un foco de luz se hace de la misma forma que crear una luz. Debemos de codificar un array de floats para la posición, otro para la luz difusa y otro para la dirección. Lo tratamos como a una luz normal y corriente, es decir, que le pasaremos estos valores mediante la función glLightfv. Luego le pasaremos a esta luz los parámetros para GL_SPOT_CUTOFF y para GL_SPOT_EXPONENT, el primero especifica el angulo de luz y el segundo su grado de brillo. GL_SPOT_DIRECTION también es necesario, indica la dirección del foco. Luego habilitaremos la luz que acabamos de crear.
glLightfv(GL_LIGHT0, GL_POSITION,FocPos); glLightfv(GL_LIGHT0, GL_DIFFUSE, FocLuz); glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, FocDir); glLightf(GL_LIGHT0, GL_SPOT_CUTOFF, 90.1f); glLightf(GL_LIGHT0, GL_SPOT_EXPONENT, 100.0f); glEnable (GL_LIGHT0);
Para hacer que una luz tenga movimiento, dentro de algún cambio de una matriz (entre el glPushMatrix y el glPopMatrix) haremos una llamada a la declaración de la posición de la luz: glLightfv(GL_LIGHT0, GL_POSITION,FocPos);
Para hacer que un objeto brille hemos de entender que algo brillante se compone de una figura y una luz (por separado), pero si ponemos una luz en la misma posición que un objeto el objeto la tapará. Lo que tenemos que hacer es refrescar la luz, ponerla en su posición, y luego deshabilitar sus atributos. Luego dibujariamos el objeto y al final acabaríamos rehabilitando la luz. Para ello imaginaremos una función render, recolocamos la luz, y ahora deshabilitamos sus propiedades con glPushAttrib(GL_LIGHTING_BIT); dibujamos la figura y reactivamos las propiedades de la luz con glPopAttrib();
glLightfv(GL_LIGHT0, GL_POSITION,FocPos);Las DisplayList o 'listas de visualización' son figuras, vértices, polígonos… (código ogl) que se guardan en la memória de la tarjeta gráfica con un identificador y de esa forma las mostramos (ejecutamos)de una forma más rápida y óptima.
glNewList (entero, modo); ... código... glEndList();
Con el código que hay entre glNewList y glEndList creamos una lista y le damos un índice, el número entero, y un modo:
El código puede ser un modelado (llamadas a vértices, polígonos…) o también llamadas a otras listas.
Podemos definir un nombre como entero y luego usarlo en la lista de visualización cómo índice:
#define cabeza 1 ... glNewList(cabeza, GL_COMPILE);
Utilizando glGenLists podemos reservar varios identificadores de listas seguidos (luego se cargarán con funciones como wglUseFontOutlines). El formato es:
GLuint glGenLists (GLsizei range)
Reserva tantos nombres como range se han indicado, el número del primero será el que devuelva.
Podemos hacerlo de varias maneras con la función glCallList(indice de la lista). Usando con lo que usando el código anterior sería: glCallList(cabeza) o también glCallList(1). Cuando llamemos a una lista se ejecutará todo el código que ésta contiene y se mostrará la figura que forma.
Otra forma es utilizar glCallLists que llama a una serie de listas en las que sus indices están en una matriz:
... int lists[100]; ... for (i=1; i<=100; i++) { lists[i]=i+1; } ... glNewList(lists[0], GL_COMPILE); ... glEndList(); glNewList(lists[1], GL_COMPILE); ... glEndList(); ... glCallLists(50, GL_INT, lists); ...
Así llamamos a las cincuenta primeras listas de la matriz “lists”. Como la matriz contiene integers el segundo parámetro de la función es GL_INT, podría ser GL_FLOAT, GL_BYTE…..
Cuando hacemos una llamada a glCallLists indicamos las listas que se han de mostrar a partir de su identificador que es un número escogido a partir del 0, pero a veces queremos llamar a las listas a partir de un número, por ejemplo a partir del número que le fue asignado a la primera. Ese es el caso, por ejemplo, de las fuentes bitmap que cuando se generan se hace a partir de un número de lista que no tiene por qué ser el 0 pero la llamada a estas sí que se hace como si lo fuese. glListBase asigna el número escogido que se utilizará como “base” al llamar a glCallLists.
Para que no quede esta base para siempre haremos la llamada a glListBase entre un glPushAttrib y un glPopAttrib de GL_LIST_BIT.
glPushAttrib(GL_LIST_BIT); glListBase(font - 32); glCallLists(strlen(s), GL_UNSIGNED_BYTE, s); glPopAttrib();
glDeleteLists borra un rango de listas. Hay que pasarle dos parámetros: la primera lista que borrará y la última. Todas las listas que se encuentren en este rango serán borradas: glDeleteLists (1,50);
La función glIsList retorna un booleano que indica si esa lista existe. El parámetro que le pasamos es, evidentemente, el índice de la lista.
Aplicar una textura a un polígono es colocar una imágen sobre él. En OpenGL podemos tener texturas de 1d, 2d y 3d, pero básicamente trabajaremos con las de 2d. El tamaño de estas texturas no importa, pero es necesario que el tamaño de sus lados sean potencias de 2. Podemos tener, por ejemplo, texturas de 128×64 (2^7)x(2^6), de 256×256…
Para utilizarlas lo primero que necesitaremos es una variable unsigned int para cada una de ellas, en esta OpenGL guardará su identificador. Necesitaremos, evidentemente, cargar los datos del archivo imagen correspondiente a la textura y habilitar GL_TEXTURE_2D mediante glEnable. Utilizaremos las siguientes funciones:
glGenTextures(nTexturas,&idTextura); Genera un número nTexturas de nombres para la variable idTextura (el unsigned int anterior y que puede ser un array).glBindTexture(GL_TEXTURE_2D, idTextura); Enlaza una textura 2d al idTextura indicado. Hay que llamar a esta función por cada textura que tengamos.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); Indica a OpenGL el filtrado deseado para las texturas.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, imageWidth , imageHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, imageData); Especifica el tamaño, tipo, localización y formato de la textura.
Una vez generada la textura para seleccionarla utilizaremos glBindTexture.
Podemos tener texturas de 1d (un pixel)(GL_TEXTURE_1D), 2d (con alto y ancho)(GL_TEXTURE_2D) y 3d (GL_TEXTURE_3D). Estos parámetros podremos pasárselos a estas funciones.
glTexImage2d, a la que hay que pasarle los siguientes parámetros:
Para texturas 1d usaremos la función glTexImage1d. Los parámetros para esta función son los mismos que para la función anterior, únicamente cambia que no le indicamos la altura, ya que sólo es de un pixel.
Para ver como utilizar el alpha de una textura mira transparencias.
Consiste en decir a OpenGL cómo actuar respecto a las texturas que se apliquen a un polígono que va “deformandose”, para ello utilizamos la función glTexParameteri, con los parámetros:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
Imaginemos que tenemos dos objetos y vamos a texturizarlos con diferentes texturas (texture[0] y texture[1]). Las cuales generaremos así:
glGenTextures (2, texture); // 2 el valor del array y no las referenciaremos glBindTexture (GL_TEXTURE_2D, texture[0]); // Y a partir de aquí cambiaremos los valores para la textura [0]. glBindTexture (GL_TEXTURE_2D, texture[1]); // Haremos los cambios para la textura [1].
Ahora para aplicarle la textura a un objeto eligiremos que textura será la que aplicaremos (con glBindTexture) y le daremos las coordenadas.
Una vez tengamos las texturas definidas, ahora debemos de indicar las coordenadas que tendrá la textura. En un cuadrado estas coordenadas son:
Por lo tanto, si a estos valores damos parte alta izquierda (ai) 0.25 0.75, ad 0.75 0.75, bi 0.25 0.25, bd 0.75 0.25, veremos sólo el centro de la textura.
Para aplicar estas coordenadas usaremos la función glTexCoord2f de la siguiente forma:
glBegin(GL_QUADS); glTexCoord2f (0.0f,0.0f); glVertex3f(-3.0f, 4.0f, 0.1f); glTexCoord2f (1.0f,0.0f); glVertex3f(-2.0f, 2.0f, 0.1f); glTexCoord2f (1.0f,1.0f); glVertex3f(-4.0f, 2.0f, 0.1f); glTexCoord2f (0.0f,1.0f); glVertex3f(-5.0f, 4.0f, 0.1f); glEnd();
glDisable(GL_TEXTURE_2D);) antes de dibujarla y una vez dibujada volverlas a abilitar.
La textura puede ser mezclada con el color del polígono al que se le asigna, esto es llamaado “el ambiente” de la textura. La función glTexEnvi se utiliza para indicar dicho ambiente, sus parámetros son:
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
Los mipmaps son colecciones de imágenes que ayudan a OpenGL a renderizar cuando el modelo donde la textura está cambia de tamaño, agilizando los cálculos para esta. La técnica se basa en que en una misma imágen existan distintos tamaños de la textura, así cuando el modelo se aleja se escoge una textura más pequeña y cuando se acerca una más grande, evitando así que se vayan haciendo siempre cálculos sobre la textura.
Así pues para crearlas deberíamos, primero crear distintos archivos de imágenes de distintos tamaños y luego hacer algo parecido a esto:
glTexImage2D (GL_TEXTURE_2D, 0, GL_RGB, 64, 64, 0, GL_RGB, GL_UNSIGNED_BYTE, texImage0); glTexImage2D (GL_TEXTURE_2D, 1, GL_RGB, 32, 32, 0, GL_RGB, GL_UNSIGNED_BYTE, texImage1); glTexImage2D (GL_TEXTURE_2D, 2, GL_RGB, 16, 16, 0, GL_RGB, GL_UNSIGNED_BYTE, texImage2); glTexImage2D (GL_TEXTURE_2D, 3, GL_RGB, 8, 8, 0, GL_RGB, GL_UNSIGNED_BYTE, texImage3);
Afortunadamente, para hacer uso de los mipmaps OpenGL nos permite usar la función de la librería glut gluBuild2Dmipmaps, con solo una llamada a esta conseguimos lo que antes en varias a glTexImage2d, los parámetros son los mismos que para esta pero sin necesidad de indicar nivel ni borde.
Otra forma de hacer que se generen automáticamente los mipmaps es llamar a la siguiente función:
glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
Esta indica a OpenGL que cuando se defina una textura, se definan también los mipmaps.
glDeleteTextures(GLsizei n, GLuint *textures) se le indican n texturas a eliminar a partir de textures.
Para poder saber si nuestra tarjeta soporta las multitexturas, el parámetro GL_ARB_multitexture, ha de estar en la lista GL_EXTENSIONS.
Para saberlo lo que haremos será meter en una variable char* la lista GL_EXTENSIONS mediante la función glGetString:
Lista = (char*) glGetString (GL_EXTENSIONS);
La siguiente función buscará “searchStr” dentro de “str”:
bool InStr (char *searchStr, char *str) { char *extension; char *endOfStr; int idx=0; endOfStr =str + strlen(str); while (str < endOfStr) { idx=strcspn(str," "); if ((strlen(searchStr) == idx) && (strncmp(searchStr, str, idx) == 0)) return true; str +=(idx + 1); } return false; }
Si devuelve TRUE es que realmente soporta las multitexturas. Para verificar esto haríamos:
if (InStr ("GL_ARB_multitexture", Lista)) { ...
Podemos mostrar cuantas multitexturas soporta nuestra tarjeta en un messagebox de la siguiente forma, si muestra 1 es que no las soporta:
glGetIntegerv(GL_MAX_TEXTURE_UNITS_ARB, &VarInt); sprintf(VarCharDe30, "Multitexturas soportadas: %d",VarInt); MessageBox(hWnd, VarCharDe30, "Información", NULL);
Para usar las multitexturas necesitaremos incluir el archivo “glext.h” que es donde se encuentran todas las extensiones de opengl.
Declararemos las siguientes variables:
PFNGLMULTITEXCOORD2FARBPROC glMultiTexCoord2fARB = NULL; PFNGLACTIVETEXTUREARBPROC glActiveTextureARB = NULL; PFNGLCLIENTACTIVETEXTUREARBPROC glClientActiveTextureARB = NULL;
La primera es para ver las coordenadas de las multitexturas, la segunda representa a la unidad de multitextura, y la tercera es un puntero de comandos de multitextura.
Definiremos las texturas individualmente, como lo hemos hecho hasta ahora con las texturas simples:
glGenTextures(2, texture); glBindTexture(GL_TEXTURE_2D, texture[0]); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, Img[0].imageWidth, Img[0].imageHeight, GL_RGB, GL_UNSIGNED_BYTE, Img[0].imageData); glBindTexture(GL_TEXTURE_2D, texture[1]); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, Img[1].imageWidth, Img[1].imageHeight, GL_RGB, GL_UNSIGNED_BYTE, Img[1].imageData);
glActiveTextureARB(GL_TEXTURE0_ARB); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, texture[0]); glActiveTextureARB(GL_TEXTURE1_ARB); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, texture[1]);
Con glActiveTextureARB activamos una textura de las que tomarán parte en la “multitexturación”, esta tendrá el nombre GL_TEXTUREx_ARB. No nos olvidemos luego de activar GL_TEXTURE_2D y luego, indicaremos qué textura será la que usaremos.
Las coordenadas de una multitextura sigue la misma norma que las demás, sólo cambia en que debemos pasarselas por la función glMultiTexCoord2fARB, con los parámetros de la 1ª textura, la x y la y, luego la siguiente textura de la misma forma:
glMultiTexCoord2fARB (GL_TEXTURE1_ARB, 0.0f, 1.0f); glMultiTexCoord2fARB (GL_TEXTURE0_ARB, 0.0f, 1.0f); glVertex2f(-4.0, 4.0);
Cuando usamos multitexturas y texturas simples, las texturas que compongan la multitextura las tendremos que definir en orden inverso:
glActiveTextureARB(GL_TEXTURE1_ARB); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, texture[1]); glActiveTextureARB(GL_TEXTURE0_ARB); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, texture[0]);
Además tendremos que indicar que vamos a usarlas (también en orden inverso) antes de dar sus coordenadas:
glBindTexture(GL_TEXTURE_2D, texture[1]); glBindTexture(GL_TEXTURE_2D, texture[0]); glBegin(GL_QUADS); glMultiTexCoord2fARB (GL_TEXTURE1_ARB, 0.0f, 1.0f); glMultiTexCoord2fARB (GL_TEXTURE0_ARB, 0.0f, 1.0f); glVertex2f(-4.0, 4.0);
Para indicar que una textura ha de expandirse desde el centro a los lados usaremos la función glTexGenf, que controla la forma en la que se crean las coordenadas de textura, a la cual han de pasarse los siguientes parámetros:
Y luego activar las coordenadas que hayamos usado. Con glEnable(GL_TEXTURE_GEN_x); donde x puede ser S, T, R o Q.
glTexGenf(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR); glTexGenf(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR); glEnable(GL_TEXTURE_GEN_S); glEnable(GL_TEXTURE_GEN_T);
Si usamos texturas de otro tipo no olvidemos desactivar (con glDisable) estas coordenadas.
Para hacer una simulación de la multitextura, lo que debemos hacer es mostrar el objeto con una textura sin suavizado, luego mostar el objeto sobre el anterior con una textura… transparente:
glBindTexture(GL_TEXTURE_2D, texture[1]); DibuCub(); glEnable(GL_BLEND); glDepthMask(GL_FALSE); glDepthFunc(GL_EQUAL); glBlendFunc(GL_ZERO, GL_SRC_COLOR); glBindTexture(GL_TEXTURE_2D, texture[2]); DibuCub(); glDepthMask(GL_TRUE); glDepthFunc(GL_LESS); glDisable(GL_BLEND);
glDepthMask Para activar (GL_TRUE) o desactivar (GL_FALSE) la escritura en el buffer z.glDepthFunc Selecciona la función de verificación para caras ocultas. Sus parámetros: GL_NEVER, GL_LESS, GL_EQUAL, GL_LEQUAL, GL_GREATER, GL_NOTEQUAL, GL_GLEQUAL, GL_ALWAYS.glBlendFunc Selecciona el orígen y destino para la mezcla de colores. Sus parámetros son el como quedaría el color de detrás y el de delante. Para ello tenemos las siguientes indicaciones: GL_ZERO, GL_ONE, GL_DST_COLOR, GL_ONE_MINUS_DST_COLOR, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA, y GL_SRC_ALPHA_SATURATE. Para repetir una textura en un mismo polígono… la clave está en las coordenadas:
glTexCoord2f(0.0f, 0.0f); glVertex3f(poX-tam,poY+tam,poZ-tam); glTexCoord2f(3.0f, 0.0f); glVertex3f(poX+tam,poY+tam,poZ-tam); glTexCoord2f(3.0f, 3.0f); glVertex3f(poX+tam,poY-tam,poZ-tam); glTexCoord2f(0.0f, 3.0f); glVertex3f(poX-tam,poY-tam,poZ-tam);
Podemos hacer que una figura sea transparente manipulando su valor Alpha (ya sea en su color propio o en su textura). Para ello ha de estar activado el blending y cambiados sus valores por defecto.
glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
Ahora podremos dibujar un cuadrado como siempre lo hemos hecho, pero con otros valores de color:
glColor4f(1.0f,0.0f,1.0f,0.6f); glRectf (0.7f, 0.7f, 0.5f, 0.5f);
Para que las transparencias surjan efecto GL_DEPTH_TEST y la iluminación, han de estar desactivadas.
Para configurar el blending existen varias rutinas, entre ellas:
glBlendFunc, a la que se le pasan dos parámetros que representan la funciones de blending. GL_ZERO, GL_ONE, GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_DST_COLOR, GL_ONE_MINUS_DST_COLOR, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA, GL_CONSTANT_COLOR, GL_ONE_MINUS_CONSTANT_COLOR, GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA, GL_SRC_ALPHA_SATURATEglBlendEquation sólo se le pasa un parámetro, correspondiente a la ecuación de blending.glBlendFuncSeparate como glBlendFunc, pero esta última al indicar las funciones estas se aplican a los valores RGBA por igual, con glBlendFuncSeparate se indican por separado.glBlendColor, para indicar el color que OpenGL tomará por valor de alpha (por defecto el 0000).
Utilizado para decir a OpenGL qué alpha será efectiva y cual no según el color, para que su uso sea efectivo deberemos activar GL_ALPHA_TEST (cuidado, a veces dará problemas con el blending). Y utilizaremos la función glAlphaFunc a la cual se le pasa el modo en que actuará y un valor entre 0.0 y 1.0. El modo puede ser:
Para conseguir niebla en OpenGL debemos activarla con glEnable(GL_FOG);. Con la niebla conseguimos que se distorsione el color de fondo. Utilizamos las funciones: glFogf, glFogfv, glFogi y glFogiv. Con los posibles parámetros situientes:
float niebla[4]={0.5,0.5,0.5,0.5};glEnable(GL_FOG); glFogi(GL_FOG_MODE,GL_EXP); glFogf(GL_FOG_DENSITY,0.2); glFogfv(GL_FOG_COLOR, niebla);
Dentro de glu.h encontramos una serie de “primitivas 3d”, estas en vez de ser polígonos son figuras prediseñadas. Para usarlas primero tendremos que crear una variable puntero cuadrica (puntero a variable del tipo GLUquadricObj):
GLUquadricObj *obj;
Luego tendremos que indicarle que será una cuadrica (llamada a la función gluNewQuadric):
obj=gluNewQuadric();
gluQuadricDrawStyle (obj, estilo);. Siendo los estilos:gluQuadricNormals (obj, tipo_normales);. El tipo de las normales viene dado por las siguientes flags:gluQuadricOrientation(obj, orientación);. La orientación será:gluQuadricTexture (obj, booleano);. El booleano indica si se generarán coordenadas o no, con GL_TRUE se generarán automáticamente, GL_FALSE indica que no. Para generar texturas la figura deberá de ser sólida y estar activadas las texturas.gluCylinder con los parámetros:gluDisk y con los parámetros siguientes crearemos un disco o circunferencia:gluPartialDisk, los parámetros son los mismos que para gluDisk más ángulo inicial (donde empieza a dibujarse) y ángulo final (donde acaba de dibujarse).GLUquadricObj *obj = gluNewQuadric(); gluQuadricDrawStyle (obj, GLU_FILL); gluQuadricOrientation(obj, GLU_OUTSIDE); gluQuadricTexture(obj, GL_TRUE); glBindTexture(GL_TEXTURE_2D, *i); gluSphere(obj, 0.1, 25, 25);
Podemos usar los buffers para diferentes asuntos, como por ejemplo enfocar lo contrario a la escena que estamos mirando, pero el buffer más curioso es el buffer de estarcido (o Stencil Buffer), con él podemos indicar que el dibujo de una escena se haga sobre una figura. El código sería este:
glClear( GL_COLOR_BUFFER_BIT |GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); glPushMatrix(); gluLookAt(0.0,-3.0,-4.0, 0.0,0.0,0.0, 0.0,1.0,0.0); ang+=0.1; glRotatef(ang, 0.0, 0.0, 5.0); glDisable(GL_DEPTH_TEST); glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); glEnable(GL_STENCIL_TEST); glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE); glStencilFunc(GL_ALWAYS, 1, 1); Agua(); glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); glEnable(GL_DEPTH_TEST); glStencilFunc(GL_EQUAL, 1, 1); glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); glPushMatrix(); glScalef(1.0, 1.2, 0.3); glTranslatef(0.0,0.0,CalcPos()); glColor4f(1.0,1.0,1.0,0.1); Pelota(); glPopMatrix(); glDisable(GL_STENCIL_TEST); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glColor4f(1.0f, 1.0f, 1.0f, 0.5f); Agua(); glColor4f(1.0,1.0,1.0, 1.0); glTranslatef(0.0,0.0,-CalcPos()); Pelota(); glPopMatrix();
pfd.cStencilBits = 16;glStencilOp es lo que se ejecutará si el buffer de estarcido falla (GL_KEEP (mantiene el valor actual), GL_ZERO (pone el buffer a 0), GL_REPLACE (se ejecutará glStencilFunc), GL_INCR, GL_DECR, GL_INVERT.glStencilFunc es la máscara cn la que se usará el buffer.glColorMask impide que se hagan cambios en los colores reflejados.glDisable(GL_DEPTH_TEST); glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); glEnable(GL_STENCIL_TEST); glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE); glStencilFunc(GL_ALWAYS, 1, 1); Agua();
Define el tamaño del buffer de estarcido.
glShadeModel (GL_SMOOTH); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glDisable(GL_ALPHA_TEST);