Tabla de Contenidos

OpenGL

General

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).

Configurar una ventana

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);
}

Configuración del sistema de coordenadas

Necesitamos configurar el sistema de coordenadas de nuestra ventana, para ello lo que hacemos es:

  1. Definir la porción de ventana en la que dibujará ogl, lo que ocupa la vista en la ventana. Para ello se utiliza la función 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.
  2. Reiniciar la matriz de proyección.
  3. Reiniciar el sistema de coordenadas.
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.

Mantener el sistema de coordenadas proporcional

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();
}

Funciones generales

// 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);

Buffer de profundidad

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);

Dibujando

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(GL_POINTS);
glVertex2f(0.0f, 0.0f);
glEnd();
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 matrices y sus transformaciones

Las transformaciones transformaciones las podemos hacer sobre:

Modos de matriz

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:

Matriz del modelador (o matriz general)

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:

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.

Pila de matrices

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.

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();

Manipulación de matrices

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.

Proyecciones

glOrtho

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.

Proyeccion en perspectiva

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);

Iluminación

Para usarla debemos activarla con un glEnable(GL_LIGHTING);.

Tipos de luces

Un material puede reflejar tres tipos de luces:

Configuración de los materiales

La base de la iluminación es el cómo los materiales reflejan la luz. Existen dos formas de definir este “cómo”:

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);
...
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);
...

Luz ambiental

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.

  1. Definimos el color de la luz ambiental: GLfloat LuzAmbient[]={1.0f,1.0f,1.0f,1.0f}; → La luz por defecto, blanca (lo refleja todo).
  2. Se la pasamos a la función glLightModelfv indicandole que es la luz ambiente (GL_LIGHT_MODEL_AMBIENT): GlLightModelfv(GL_LIGHT_MODEL_AMBIENT, LuzAmbient);
  3. Si hemos configurado los materiales correctamente podremos ver la escena como anteriormente la habíamos visto.

Normales

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).
<1,10,0>-<1,1,0> = <1-1, 10-1, 0> = <1,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.

Normalizar

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:
N_x = (U_y · V_z) - (U_z · V_y)
N_y = (U_z · V_x) - (U_x · V_z)
N_z = (U_x · V_y) - (U_y · V_x)

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.

OpenGL y las normales

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.

Crear fuentes de luz

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);

Luz especular

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.

glEnable(GL_COLOR_MATERIAL);
glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
glMaterialfv(GL_FRONT, GL_SPECULAR, spec);
glMateriali(GL_FRONT, GL_SHININESS, 5);

Focos

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);

Objetos brillantes y movimiento de luces

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();

DisplayLists

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.

Creación de una lista

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);

Creación de varias listas

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.

Llamada a listas

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…..

glListBase

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();

Borrado de listas

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.

Texturas

Texturas simples

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:

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.

Texturas 2d y 1d

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.

Filtrado de textura (Texture Filtering)

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);

Diferentes texturas en diferentes objetos

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.

Coordenadas de textura

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();

Adquisición del color del objeto

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);

Uso de mipmaps

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.

Eliminar texturas

glDeleteTextures(GLsizei n, GLuint *textures) se le indican n texturas a eliminar a partir de textures.

Multitexturas

Verificación de soporte de multitexturas

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);

Activar el uso de las multitexturas

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.

Definir las texturas

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);

Indicar la multitextura

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.

Coordenadas de una multitextura

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);

Más sobre las texturas

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);

Texturas esféricas

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.

Simular la multitextura

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);

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);

Efectos

Transparencias

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.

La ecuación de Blending

Para configurar el blending existen varias rutinas, entre ellas:

Alpha Testing

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:

Niebla

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:

glEnable(GL_FOG);
glFogi(GL_FOG_MODE,GL_EXP);
glFogf(GL_FOG_DENSITY,0.2);
glFogfv(GL_FOG_COLOR, niebla);

Cuádricas

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();

Tipos de cuádricas

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);

Buffers

Stencil Buffer

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();
  1. Primero limpiamos todos los buffers, y colocamos la cámara. No nos olvidemos de indicar en la configuración de OpenGL los bits para este buffer: pfd.cStencilBits = 16;
  2. Para usarlo debemos deshabilitar el buffer de profundidad.
  3. 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.
  4. glStencilFunc es la máscara cn la que se usará el buffer.
  5. glColorMask impide que se hagan cambios en los colores reflejados.
  6. Una vez hayamos definido todas estas funciones definiremos el espacio para el reflejo y el reflejo, luego desactivaremos el buffer de estarcido y continuaremos dibujando.
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.

Notas

glShadeModel (GL_SMOOTH);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDisable(GL_ALPHA_TEST);