Herramientas de usuario

Herramientas del sitio


fw:ogl

¡Esta es una revisión vieja del documento!


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

  • Para que una ventana utilice OGL el WNDCLASS.style deberá ser asignado como CS_OWNDC.
  • Al CreateWindow nunca se le llamará con un CS_PARENTDC. Aquí va un ejemplo de parte de un WinMain para OGL:
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;
  • Antes de ponernos a dibujar con OGL debemos configurar la ventana; para ello asignamos los valores adecuados al formato de pixel que utilizará. Esta es una función de ejemplo que realiza esa tarea:
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

  • 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:
    • GL_LINE_STIPPLE: Formato para las GL_LINES.
    • GL_CULL_FACE: Hace que se pinte sólo por la cara de delante. Con esto hacemos uso del culling, la técnica de eliminar los polígonos que no van a verse permitiendo así una mejora significante de rendimiento; pero cuidado al dibujar, has de tener en cuenta si has de dibujar en el sentido de las agujas del reloj glFrontFace(GL_CCW) o no glFrontFace(GL_CW).
    • GL_DEPTH_TEST: Elimina las caras ocultas por otras, si no se pone las últimas caras dibujadas (aunque estén por debajo de otras) saldrán por encima. Necesita del parámetro GL_DEPTH_BUFFER_BIT en el glClear. Por lo visto no hay feeling con el GL_CULL_FACE activado.
    • GL_LIGHTING: Activa los parámetros de iluminación.
    • GL_COLOR_MATERIAL: Activa en los materiales la propiedad de reflejar luz.
  • 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á:
    • r puede ser: GL_BACK, GL_FRONT, por detrás o por delante.
    • s puede ser: GL_LINE, GL_FILL, GL_POINT (líneas (bordes), rellenos o puntos)
  • gluLookAt Corresponde a la camara. Tiene 9 parametros GLdouble:
    • x,y,z de la posición de la camara.
    • x,y,z del punto al que está dirigida.
    • x,y,z de su orientación. (Necesita que la vista sea en perspectiva).
    • Puedes ver más información sobre la cámara aquí.
// 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.

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
  • Para dibujar primitivas tenemos que hacer una llamada a 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();
  • Tipos de primitivas:
    • GL_POINTS Compuestas por un único punto y dibujan eso: puntos. Podemos seleccionar su tamaño con la función: glPointSize(número); que no es necesario que vaya entre el glBegin ni el glEnd.
    • GL_LINES Debemos especificarle dos puntos para dibujar la línea. Cada dos puntos será una línea. Podemos indicar el ancho de una línea mediante: 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);.
    • GL_LINE_STRIP Dibujará una línea que irá pasando por cada vertice que se indique. Dibuja también líneas curvas:
    • GL_LINE_LOOP El último vertice de la línea se enlazará con el primero de la siguiente.
    • GL_TRIANGLES Dibuja triangulos, estos están compuestos por 3 vértices. Tanto los triangulos como los demás polígonos se construyen en sentido de las agujas del reloj. Para cambiarlo a sentido contrario usaremos la función: glFrontFace(GL_CW). Si queremos volver a la normalidad le pasaremos el parámetro: GL_CCW.
    • GL_TRIANGLE_STRIP Dibuja triangulos seguidos. Por ejemplo, para dibujar 3 triangulos no necesita indicar 9 vertices sino 5. Los tres primeros formarán el primer triangulo y luego, colocando el siguiente se le unirá otro triangulo y al colocar el siguiente otro…
    • GL_TRIANGLE_FAN Crearemos una serie de triangulos, estos se conectarán entre sí mediante un punto central que será el primer vértice dibujado. Funciona como GL_TRIANGLE_STRIP, los tres primeros vertices serán un triangulo y luego, al añadirle vertices se formará un “abannico de triangulos”.
    • GL_QUADS Especificando cuatro vertices podemos hacer un cuadrado.
    • GL_QUAD_STRIP Igual que los triangulos, una vez creados los primeros cuatro vertices, añadiendo dos más se nos irán añadiendo más cuadrados.
    • GL_POLYGON Siguiendo las agujas del reloj podremos crear los polígonos de todos los lados que queramos con esta última primitiva.
  • Podemos dar color a toda una primitiva aplicandole la función glColor3f(x,x,x) a todos sus vértices o podemos aplicarsela a cada vértice por separado.
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:

  • Viewing \ Vista: Correspondiente a la cámara.
  • Modeling \ Modelos: Transformaciones a los polígonos y modelos.
  • Projection: A la definición de volumen y a la forma de mostrar la imágen.
  • Viewport: El render 2d sobre la ventana.
  • Modelview: La combinación entre las transformaciones de la vista y los modelos (la matriz del modelador).

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:

  • GL_MODELVIEW: Pila de matrices del modelador (para dibujar en escena).
  • GL_PROJECTION: Pila de matrices de la proyección (para definir el volumen de trabajo).
  • GL_TEXTURE: Pila de matrices de textura (coordenadas de textura).

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:

  • Movimiento con las funciones tipo glTranslatef(X,Y,Z);
  • Rotacion con las funciones tipo glRotatef(Angulo, X, Y, Z);
  • Escalado con las funciones tipo glScalef(X,Y,Z);

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.

  • Para guardar el estado actual de la matriz y crear otra matriz: glPushMatrix();
  • Para cargar el último estado guardado: 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();

Manipulación de matrices

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

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, y ni siquiera se dibujan los más cercanos sobre los lejanos, sino por orden de dibujo :?:. 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:

  • Bits que hay a la izquierda del punto 0,0.
  • Los que hay a la derecha.
  • Los que hay desde abajo.
  • Los que hay desde arriba.
  • Desde el usuario a ese punto (0,0).
  • Desde el punto (0,0) al fondo);
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:

  • angulo visual vertical
  • GLfloat que representa la proporción de altura con anchura (ancho de la vista entre alto).
  • Comienzo de la vista
  • Fin de la vista
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.

Iluminación

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

Tipos de luces

Un material puede reflejar tres tipos de luces:

  • Ambiental: La luz de la escena, no viene de ningun sitio en particular.
  • Difusa: Es reflejada por el objeto en distintas direcciones.
  • Especular: Es reflefada por el objeto en una direccion uniforme, como puede ser la del reflejo en un metal que crea algo así como un destello o brillo.

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”:

  • Con 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:
    • Qué caras del polígono reflejan la luz: GL_FRONT, GL_BACK, GL_FRONT_AND_BACK
    • Qué tipo de luz: GL_AMBIENT_AND_DIFFUSE, GL_AMBIENT, GL_DIFFUSE, GL_SPECULAR
    • Un array RGBA que indica la cantidad.
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);
...
  • Para utilizar la otra forma (preferible) llamaremos a la función glEnable con el parámetro GL_COLOR_MATERIAL. Esto activa el que los materiales reflejen la luz indicada por glColor. Podemos definir qué tipos de luz reflejan los materiales con la función: glColorMaterial (donde, tipo de luz>);
    • donde: GL_FRONT, GL_BACK, GL_FRONT_AND_BACK → Las caras que han de reflejarla.
    • tipo de luz: GL_AMBIENT_AND_DIFFUSE, GL_AMBIENT, GL_DIFFUSE, GL_SPECULAR.
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)

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

  • luz: Desde GL_LIGHT0 a GL_LIGHT7.
  • Parámetro de iluminación: GL_AMBIENT, GL_DIFFUSE, GL_SPECULAR, GL_POSITION (para la posición) ← Cuatro valores (RGBA), excepto para GL_POSITION en la que 1.0f significa que está en el infinito positivo… Y estas de un solo valor → GL_CONSTANT_ATTENUATION, GL_LINEAR_ATTENUATION, GL_QUADRIC_ATTENUATION.
  • Datos: Los guardamos en un array de floats: GLfloat lightPos[] = {-50.f,50.0f,100.0f,1.0f};
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.

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

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

  • Para el calculo automático de las normales: glEnable(GL_AUTO_NORMAL); y glEnable(GL_NORMALIZE);
  • Se produce un efecto al rotar un objeto con una luz al lado, este es que la luz rota también. Para que esto no pase debemos refrescar la posición de la luz antes de rotar el objeto: glLightfv(GL_LIGHT0, GL_POSITION,FocPos);

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:

  • GL_COMPILE para que sólo se compile y para ejecutarla sólo cuando la llamemos.
  • GL_COMPILE_AND_EXECUTE para que se compile y se ejecute.

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:

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

Texturas 2d y 1d

  • Para las texturas 2d usamos la función glTexImage2d, a la que hay que pasarle los siguientes parámetros:
    • GL_TEXTURE_2D.
    • El nivel, especifica las diferentes resoluciones. Como sólo usaremos 1 le daremos el valor 0. Echale un vistazo a los Mipmaps para más información de niveles.
    • El formato interno del color, cómo lo trataremos: GL_RGB, GL_RGBA, GL_LUMANANCE y GL_LUMANANCE_ALPHA.
    • La anchura y la altura de la textura.
    • El borde, 0 para que no tenga y 1 para que sí.
    • El formato de los datos que vamos a introducir(GL_COLOR_INDEX, GL_RGB, GL_RGBA, GL_RED, GL_GREEN…).
    • El formato de los datos con los que tenemos guardada textura (por ejemplo GL_UNSIGNED_BYTE).
    • Los datos de la textura.

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:

  • Objetivo, al tipo de textura: GL_TEXTURE_2D o GL_TEXTURE_1D
  • Nombre, GL_TEXTURE_MIN_FILTER (para el valor mínimo), GL_TEXTURE_MAG_FILTER (para el valor máximo), GL_TEXTURE_WRAP_S, GL_TEXTURE_WRAP_T, o GL_TEXTURE_PRIORITY.
  • Valor,
    • GL_NEAREST
    • GL_LINEAR
    • GL_NEAREST_MIPMAP_NEAREST
    • GL_NEAREST_MIPMAP_LINEAR
    • GL_LINEAR_MIPMAP_NEAREST
    • GL_LINEAR_MIPMAP_LINEAR
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:

  • Parte alta izquierda: 0.0,1.0
  • Parte alta derecha: 1.0, 1.0
  • Parte baja izquierda: 0.0, 0.0
  • Parte baja derecha: 1.0, 0.0

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();
  • Si quisiesemos poner una imagen de fondo y sobre ella las figuras 3d texturizadas deberemos desabilitar el uso de las texturas (glDisable(GL_TEXTURE_2D);) antes de dibujarla y una vez dibujada volverlas a abilitar.

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:

  • GL_TEXTURE_ENV
  • GL_TEXTURE_ENV_MODE
  • Cómo afecta el color:
    • GL_MODULATE, a color más claro en la textura más color que se deja ver del polígono.
    • GL_DECAL, hace que el color de la textura se sobreponga al del polígono (como GL_REPLACE)
    • GL_BLEND, hace que los puntos blancos de la textura hagan que esa parte del polígono se transparente, los negros que se vea. Es decir, la textura actua como alfa sobre el polígono.

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:

  • A qué coordenada (GL_S (corresponde a la altura), GL_T (corresponde a la anchura), GL_R o GL_Q)
  • GL_TEXTURE_GEN_MODE
  • Parámetros: GL_OBJECT_LINEAR, GL_EYE_LINEAR, o GL_SPHERE_MAP (para esféricas).

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

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.

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:

  • GL_FOG_MODE → GL_LINEAR, GL_EXP, y GL_EXP2.
  • GL_FOG_DENSITY → Un float
  • GL_FOG_START y GL_FOG_END → Comienzo y fin de esta.
  • GL_FOG_INDEX
  • GL_FOG_COLOR → 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);

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();
  • Las cuádricas se muestran con un estilo concreto, para cambiar este utilizamos: gluQuadricDrawStyle (obj, estilo);. Siendo los estilos:
    • GLU_FILL → Sólida.
    • GLU_LINE → Líneas.
    • GLU_POINT → Puntos.
    • GLU_SILHOUETTE → Perfil.
  • Las normales se generarán automáticamente si llamamos a la función: gluQuadricNormals (obj, tipo_normales);. El tipo de las normales viene dado por las siguientes flags:
    • GLU_NONE → Sin normales.
    • GLU_FLAT → Normales para cada polígono.
    • GLU_SMOOTH → Normales para cada vértice.
  • Para controlar la dirección de las normales usaremos: gluQuadricOrientation(obj, orientación);. La orientación será:
    • GLU_OUTSIDE → Hacia fuera
    • GLU_INSIDE → Hacia dentro (esto en las esferas si estamos dentro o fuera)
  • Las coordenadas de textura con: 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.

Tipos de cuádricas

  • Cilindros y conos: Al dibujar un cilindro podremos dibujar un cono si el radio superior (o inferior) de este es 0. Los cilindros no están tapados, para ello usaremos los discos. La función para dibujar un cilindro es gluCylinder con los parámetros:
    • Radio de la base.
    • Radio superior.
    • Altura.
    • Numero de caras (20 está bien).
    • Número de pilas (“pisos”, como mínimo que sea 1).
  • Discos: Con gluDisk y con los parámetros siguientes crearemos un disco o circunferencia:
    • Radio Interior → El agujero del centro
    • Radio Esterior → Tamaño del disco
    • Segmentos → Como el número de caras en el cono
    • Vueltas → El número de anillos internos
  • Discos parciales: Es el dibujo de un disco como si fuese un arco… dibujando sólo un trozo, lo haremos con la función 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).
  • Esferas: Una esfera la dibujaremos con gluSphere y con los parámetros:
    • Rádio
    • Segmentos → El número de segmentos en total que tendrá.
    • Pila → “Pisos” de la esfera.
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

  • Si usas perspectiva con objetos complejos texturizados, el parámetro dentro de gluPerspective con respecto al ‘near’ ha de ser mayor de 0.0.
  • A veces, al usar el blending, este no funciona, la razón es porque debemos desabilitare el GL_ALPHA_TEST:
glShadeModel (GL_SMOOTH);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDisable(GL_ALPHA_TEST);
  • Tenemos que tener en cuenta que los objetos a los que vaya a transparentar han de haber sido dibujados antes que el transparente.
  • Sobre qué hacer al cerrar, liberar las propiedades definidas, o cambiar de ventana en OpenGL existe un artículo que habla de ello.
fw/ogl.1215509810.txt.gz · Última modificación: 2020/05/09 09:24 (editor externo)