Herramientas de usuario

Herramientas del sitio


fw:ogl:xtra

¡Esta es una revisión vieja del documento!


OpenGL (Xtra)

Más funciones y trucos con OpenGL

Movimiento de cámaras

Perspectiva

La perspectiva define el ojo del usuario, los valores near y far representan desde donde puede ver y hasta qué distancia. Luego es necesario colocar al usuario en un punto mediante el gluLookAt.
camara

gluPerspective(GLdouble fovy, GLdouble aspect, GLdouble near, GLdouble far);
Pero nunca pongas el near como 0.0, ponlo al menos como 0.1 o las texturas en objetos complejos se volverán locas.
Por defecto, si no se define la cámara, esta está en 0,0,0 y mira en dirección -Z. La -x entonces queda a la izquierda y la +x a la derecha.

Cámaras

gluLookAt(0.0,0.0,5.0, 0.0,0.0,0.0, 0.0,1.0,0.0);

La cámara está en el punto 0x, 0y, 5z, mira al 0x, 0y, 0z, y está orientada hacia arriba.
Los tres últimos números, la orientación; básicamente los valores más útiles derivan de cambiar la x y la y. Si sólo definimos una de estas coordenadas no importa si ponemos 1 o 100, lo importante es si es negativo o positivo, esto es, si la y está positiva la cámara mirará hacia arriba si es negativa estará vocabajo. Si la x tiene un valor positivo estará como tumbada a la derecha, y a la izquierda si lo tiene negativo. Digamos que significa cuanto hacia arriba\abajo y cuanto hacia la derecha\izquierda está.
El orden para dibujar las cosas sería:

repeat [
  guardar matriz
  hacer transformaciones a la cámara
  dibujar objetos que no se mueven
  para cada objeto que se mueve [
    guardar matriz
    aplicar transformaciones
    dibujar el objeto
    restaurar matriz
  ]
  restaurar matriz
]

Rotación de la cámara

El valor de la X del 3er vector corresponde al coseno del ángulo que queremos rotar, el de la Y al seno.

float x = angle * 0.01745f;
float y = angle * 0.01745f;
gluLookAt(this->position->x, this->position->y, this->position->z, 
	this->lens->x, this->lens->y, this->lens->z, 
	cosf(x), sinf(y), 0.0f);

Cambiar de repente vista perspectiva a ortogonal

glDisable(GL_DEPTH_TEST); 
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
glOrtho(-3, 3, -3, 3, 0, 3);
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
 
/* Tu código */
 
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
glPopMatrix();
glEnable(GL_DEPTH_TEST);

:!: El código anterior fue encontrado en internet, el siguiente debería hacer lo mismo pero está documentado y pensado, pero falta probar más.
Si estás en vista en perspectiva y quieres cambiar a ortogonal para, no sé, por ejemplo dibujar paneles (se sobreescribirá a lo ya dibujado) has de seguir los siguientes pasos:

// Cambiar la proyección
glDisable(GL_DEPTH_TEST);		//Desactivas el buffer de profundidad
glMatrixMode(GL_PROJECTION);		// Entras en modo de proyección
glPushMatrix();				// Guardas el estado de la proyección
glLoadIdentity();			// Reinicias a la matriz identidad
glOrtho(-3, 3, -3, 3, 0, 3);		// Defines una vista ortogonal
 
// Entrar en modo MODELVIEW y dibujar
glMatrixMode(GL_MODELVIEW);		// Utilizando la MODELVIEW
glPushMatrix();				// Guardas el estado de la MODELVIEW
glLoadIdentity();			// Reinicias a la matriz identidad
 
// ----------------- código  --------------------//
 
glPopMatrix();				// Restauras el estado de la MODELVIEW
 
// Restaurar la proyección
glMatrixMode(GL_PROJECTION);		// Entras en proyección
glPopMatrix();				// Restauras el estado de la proyección
 
// Volver al estado anterior
glMatrixMode(GL_MODELVIEW);		// Vuelves al MODELVIEW
glEnable(GL_DEPTH_TEST);		// Vuelves a activar el buffer de profundidad

Masking

El masking consiste en una imagen en blanco y negro y otra a color, la máscara (la primera imagen (en b/n)) sólo permitirá ver de la otra imagen las partes que ésta tenga en negro.

glPushMatrix();
	glEnable(GL_BLEND);
	glDisable(GL_DEPTH_TEST);
	glBlendFunc(GL_DST_COLOR,GL_ZERO);	
	if (Masking)
		glBindTexture(GL_TEXTURE_2D, texture[1]);
	else 
		glBindTexture(GL_TEXTURE_2D, texture[0]);
	glBegin(GL_QUADS);						
		glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.1f, -1.1f,  0.0f);
		glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.1f, -1.1f,  0.0f);
		glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.1f,  1.1f,  0.0f);
		glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.1f,  1.1f,  0.0f);
	glEnd();
	glBlendFunc(GL_ONE, GL_ONE);
	if (Masking)
		glBindTexture(GL_TEXTURE_2D, texture[0]);
	else 
		glBindTexture(GL_TEXTURE_2D, texture[1]);
	glBegin(GL_QUADS);							
		glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.1f, -1.1f,  0.0f);
		glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.1f, -1.1f,  0.0f);
		glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.1f,  1.1f,  0.0f);
		glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.1f,  1.1f,  0.0f);
	glEnd();
	glEnable(GL_DEPTH_TEST);
	glDisable(GL_BLEND);
glPopMatrix();

Texture[1] son unas letras negras con el fondo blanco, texture[0] son las mismas letras pero blancas con el fondo en negro. Si hay masking texture[1] hará de máscara y se verán letras blancas, sino se verá el fondo blanco dejando ver la parte que corresponde a las letras.

Carga de un .bmp con la librería aux

AUX_RGBImageRec *LoadBMP(char *Filename) {
	FILE *File = NULL;
	File = fopen(Filename, "r");
	fclose(File);
	return auxDIBImageLoad(Filename);
}

Para usar la librería aux debemos incluir la cabecera y la librería glaux. Ahora ya podemos ejecutar esta función, returna un puntero a una estructura AUX_RGBImageRec y se le pasa el nombre de un fichero, tendremos que leerlo y guardar sus datos.

AUX_RGBImageRec *TextureImage;
TextureImage = LoadBMP("Font.bmp");
glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage->sizeX, TextureImage->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage->data);

Seleccionando elementos

Para seleccionar elementos debemos de colocarlos antes en una pila de identificadores. Antes de dar a una primitiva un identificador deberemos de inicializar la pila de identificadores:

glInitNames();
glPushName(0);

La función que da a un elemento un identificador es glLoadName, y se usa así:

glLoadName(nº del identificador);
gluDisk(obj[0], 0.0, 1.0, 60, 24);

Desde WM_LBUTTONDOWN llamaremos a la función que comprueba sobre qué identificador se ha clicado. Esta función tendría que tener la siguiente estructura:

// 1
GLuint selectBuff[64];
GLint hits, viewport[10];
// 2
glSelectBuffer(64, selectBuff);
// 3
glGetIntegerv(GL_VIEWPORT, viewport);
// 4
glMatrixMode(GL_PROJECTION);
glPushMatrix();
// 5
glRenderMode(GL_SELECT);
glLoadIdentity();
// 6
gluPickMatrix(xPos, yPos, 2,2, viewport);
// 7
glOrtho (-NewANC, NewANC, NewALT, -NewALT, -RangoXYZ, RangoXYZ);
Render();
hits = glRenderMode(GL_RENDER);
// 8
if(hits == 1) 
	seleccionado=selectBuff[3];
else 
	seleccionado=0;
// 9
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
  1. Definimos un array que guarde espacio para el buffer de selección, un contador y un array con el número de identificadores existente.
  2. Definimos el buffer de selección.
  3. Cogemos la vista con sus identificadores.
  4. Vamos a la matriz de proyección y la guardamos.
  5. Cambiamos a modo de selección y reiniciamos las coordenadas.
  6. Definimos la función del click, la posición, cuantos pixels se expande y donde se hace.
  7. Recolocamos todo y redibujamos.
  8. Miramos si existe alguna referencia, si existe, la variable entera se queda con su identificador, sino será 0.
  9. Recuperamos la matriz de proyección y volvemos a la del modelador.
glOrtho (-NewANC, NewANC, NewALT-1, -NewALT, -RangoXYZ, RangoXYZ);

Este glOrtho representa al primero por el que ha de pasar, y es que la primera vez, si no se redimensiona la pantalla, glOrtho (tal como nosotros lo tenemos montados) mueve un pixel más abajo la vista. Y lo hacemos al revés porque la coordenada y la cogerá invertida.

Array de vértices

  • Para usarlos tendremos que activarlos con la función glEnableClientState(y el array) y para desactivarlos glDisableClientState(y el array).
  • Tenemos los siguientes tipos de arrays: GL_COLOR_ARRAY, GL_EDGE_FLAG_ARRAY, GL_INDEX_ARRAY, GL_NORMALÇARRAY, GL_TEXTURE_COORD_ARRAY, GL_VERTEX_ARRAY.
  • Para definirlos glColorPointer(parámetros), glEdgeFlagPointer(parámetros), glIndexPointer(parámetros), glNormalPointer(parámetros), glTexCoordPointer(parámetros), glVertexPointer(parámetros).
  • Y las funciones con las que los usamos: glDrawArrays(parámetros), glDrawElements(parámetros), glDrawRangeElements(parámetros), glArrayElement(parámetros).

Modificar una textura

Cuando queremos realizar una animación (o desacernos de una textura que no vamos a usar) es mejor, computacionalmente hablando, modificar una textura que crear una nueva. Para esta acción existe la función glTexSubImage2D, recibe por parámetro:

  • La textura a modificar.
  • En qué nivel será modificada.
  • En qué x se hará el cambio.
  • En qué y se hará el cambio.
  • Con qué anchura será el cambio.
  • Con qué altura será el cambio.
  • El formato.
  • El tipo.
  • Los pixels que reemplazarán a los que ya están.

Antialising

Es la técnica que hace que al dibujar los píxels sobre el render estos tengan los bordes difuminados para que así no queden tan cuadrados. Para conseguir esto OpenGL utileza el blending, por lo que para aplicarlo en tu escena tendrás que…

  1. Activar el blending
  2. Utilizar la función de blend más adecuada. (Al hacer esto ten en cuenta que la función de blending ha de estar en GL_ADD).
  3. Activar la difuminación de puntos, líneas y bordes de polígonos.
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
...
glEnable(GL_POINT_SMOOTH);
glEnable(GL_LINE_SMOOTH);
glEnable(GL_POLYGON_SMOOTH);

En OpenGL 1.3 se agrega el Multisample, que es una técnica que consiste en dibujar varias veces los polígonos y conseguir así un efecto de antialissng mejor que con blending. Podemos activarlo mediante glEnable(GL_MULTISAMPLE);.
GL_MULTISAMPLE es aplicar multisample con RGB, si queremos aplicarlo con alpha o utilizar cualquier otra configuración llamaremos a glEnable con uno de los flags siguientes (en vez de con GL_MULTISAMPLE):

  • GL_SAMPLE_ALPHA_TO_COVERAGE. Con alpha.
  • GL_SAMPLE_ALPHA_TO_ONE. Con alpha a 1.
  • GL_SAMPLE_COVERAGE. Con alpha y el valor pasado a glSampleCoverage, esta función permite especificar un valor de alpha.

Carga de imágenes

Imágenes .raw

void LoadRaw ( char *file_name, int width, int height, int depth, GLenum colour_type, RAWFILE *RAW) {
   FILE *file;
   file = fopen(file_name, "rb");  
   RAW->Data = (GLubyte *) malloc ( width * height * depth * ( sizeof(GLubyte)) );
   fread  ( RAW->Data , width * height * depth, 1 , file);
   fclose ( file);
}

Un fichero RAW sólo contiene los datos de la imagen, por lo tanto su estructura es muy simple:

typedef struct {
	GLubyte *Data;
} RAWFILE;

La función que carga RAW es la más simple de todas, ya que sólo reserva la memoria y luego asigna a esta los datos. Los parámetros que le pasaremos serán:

  • El nombre del fichero.
  • El ancho, ya que no tiene ninguna cabecera que lo indique.
  • El alto.
  • La profundidad (3 por defecto).
  • En qué formato está guardado (GL_RGB).
  • En qué estructura se guardarán los datos.

Bitmaps

Estructura de un fichero bitmap

Para leer un bitmap hemos de tener en cuenta como están formados interiormente:

  • Los primeros 14 bytes corresponden a la cabecera del archivo bitmap (BITMAPFILEHEADER), tamaño del archivo, tipo….
  • Los primeros 40 bytes son los de la cabecera con información del bitmap (BITMAPINFOHEADER) contiene información sobre el tamaño de la imagen, bits por pixel…
  • Luego está la tabla de colores que indica la paleta de colores usada, si el bitmap es de 24 bits esta tabla no aparecerá.
  • Y luego la información en bytes del fichero, la imagen, mostrada de abajo hacia arriba.

Por lo tanto podemos definir una estructura para un fichero bmp (imaginemos que es de 24 bits):

typedef struct {
   BITMAPFILEHEADER bmfHeader;
   BITMAPINFOHEADER bmiHeader;
   GLubyte *image_data;
} BITMAP_IMAGE;

El puntero image_data indica la posición de memoria donde encontramos la información sobre la imagen.
Por lo tanto lo que haremos para leer una imagen bitmap será crear una variable del tipo BITMAP_IMAGE y llenarla:

BITMAP_IMAGE image; 

Función de lectura de un fichero bitmap

A esta función le pasaremos una referencia del tipo BITMAP_IMAGE.

void Load_Bitmap_File(BITMAP_IMAGE *b) {
	FILE *fp;
	int memory;
	fp = fopen("c:\bicho.bmp", "rb");
 
	fread(&b->bmfHeader, 1, 14, fp);
	fread(&b->bmiHeader, 1, 40, fp);  
 
	if (b->bmiHeader.biSizeImage == 0)
		memory = b->bmiHeader.biWidth * b->bmiHeader.biHeight * 4;
	else
		memory = b->bmiHeader.biSizeImage;
	b->image_data = (GLubyte *)malloc(memory);
	fread(b->image_data , 1, memory, fp);
	fclose(fp);
}
  1. Crearemos un puntero del tipo fichero.
  2. Y abriremos el fichero con fopen, a la cual le pasaremos un string que será la ruta del archivo a abrir y el modo en el que lo abriremos (r(lectura), w(escritura), rb(lectura en modo binario)).
  3. Leeremos una porción de datos del fichero abierto con fread(en stdio.h), sus parámetros son: el lugar donde se guardan los datos, donde comienzan los datos a guardar, donde acaban y de donde lo leemos. Leeremos los de las cabeceras.
  4. Guardaremos en una variable entera el número de bytes que ocupa la imagen en memoria.
  5. Reservamos espacio en la memoria para la imagen haciendo un cast para que estos sean en formato GLubyte.
  6. Y cogeremos los datos de la imagen.
  7. Cerraremos el fichero.

Mostrar la imágen en una ventana de OpenGL

Para ello usamos la función glDrawPixels, pero antes necesitamos indicar la posición del raster con glRasterPos3f. A glRasterPos3f se le pasan tres floats que serían la indicación de la x, y, z del bitmap.

  • A glDrawPixels se le pasan cinco parámetros:
    • El ancho de la imagen, en nuestro caso image.bmiHeader.biWidth.
    • El alto image.bmiHeader.biHeight.
    • GL_BGR_EXT, que es el formato de bits del bitmap, y este es el que se adecua a la forma en que lo hemos abierto.
    • El tipo de datos guardados en la memoria (GL_BYTE, GL_SHORT, GL_BITMAP, GL_FLOAT), en nuestro caso GL_UNSIGNED_BYTE.
    • Y los pixels que forman la imagen, que en nuestra estructura están definidos como: image.image_data.

Recuerda, glRasterPos se utiliza para indicar la posición de una imágen que vamos a dibujar en una ventana OpenGL.
glDrawPixels hace lo que indica su nombre: dibujar pixels a saco sobre la ventana.

Jugar con el bmp

Existen funciones que nos permiten modificar el mapa de bits:

  • glPixelTransferf Para variar y escalar el color. Se le pasan dos parámetros, lo que variaremos y en que grado (al menos si hablamos de modificar GL_RED_SCALE, GL_GREEN_SCALE o GL_BLUE_SCALE).
glPixelTransferf(GL_RED_SCALE,15.1);
  • glPixelZoom Pasandole estos parámetros:
    • 1.0,1.0 No escala la imagen
    • -1.0,1.0 Invierte la imagen horizontalmente
    • 1.0,-2.0 Invierte la imagen verticalmente
    • 0.33,0.33 La imagen es dibujada a 1/3 de su tamaño

Por lo tanto, para que una imagen ocupe toda la ventana:

  1. Necesitaremos dos variables globales que den el tamaño actual de la ventana.
  2. Dos variables temporales doubles sobre las que se calculará la relación entre el tamaño de la ventana y el tamaño de la imagen (tamaño de la ventana / tamaño de la imagen), habrá que hacer un cast del resultado a double y multiplicarlo por 100.
  3. Hacer un glPixelZoom usando estas dos variables (anchura y altura).

Targa (tga)

Una imágen .tga puede ser de 16, 24 (RGB) o 32 (RGBA) bits, no es comprimida (aunque puede serlo) y, como la mayoría de los ficheros de imágen no comprimidos contiene una cabecera de la que siguen los datos. En esta cabecera hay toda la información necesaria para cargar el fichero. En el código siguiente se lee un fichero .tga byte a byte estando documentado cual es el significado de cada uno de ellos.

struct IMG {
	int dataSize;
	int width;
	int height;
	int bits;
	unsigned char* data;
};
 
 
IMG loadImg (char* str) {
	IMG img;
	short int tmpInt;
	unsigned char tmpChar;
	FILE* file = fopen(str, "rb");
 
	fread (&tmpChar, sizeof(unsigned char), 1, file);	// El tamñao del campo ID 
	fread (&tmpChar, sizeof(unsigned char), 1, file);	// Tipo de color map: 0=ninguno 1=con paleta
	fread (&tmpChar, sizeof(unsigned char), 1, file);	// Tipo de imágen: 0=ninguna, 1=indexada, 2=rgb, 3=escala de grises
 
	fread (&tmpInt, sizeof(short int), 1, file);		// Primera color map en la paleta
	fread (&tmpInt, sizeof(short int), 1, file);		// Número de colores en la paleta
	fread (&tmpChar, sizeof(unsigned char), 1, file);	// Número de bits por entrada en la paleta (15, 16, 24, 32)
	fread (&tmpInt, sizeof(short int), 1, file);		// Orígen X
	fread (&tmpInt, sizeof(short int), 1, file);		// Orígen Y
	fread (&tmpInt, sizeof(short int), 1, file);		// Width
	img.width = tmpInt;
	fread (&tmpInt, sizeof(short int), 1, file);		// Height
	img.height = tmpInt;
	fread (&tmpChar, sizeof(unsigned char), 1, file);	// Numero de bits por pixel (8, 16, 24, 32)
	img.bits = tmpChar;
	fread (&tmpChar, sizeof(unsigned char), 1, file);	// Descriptor de bits
 
	int colorMode = img.bits / 8;
	img.dataSize = img.width * img.height * colorMode;
 
	img.data = new unsigned char[img.dataSize];
	fread(img.data, sizeof(unsigned char), img.dataSize, file);
 
	for (int i = 0; i < img.dataSize; i += colorMode) {
		tmpChar = img.data[i];
		img.data[i] = img.data[i+2];
		img.data[i+2] = tmpChar;
	}
	return img;
}

La última parte del código coloca correctamente los datos. Y es que los datos en el fichero TGA están como BGR y han de ser convertidos a RGB.

Fuentes de texto

Fuentes outline

void FontPrint(GLuint font, char *s) {
  glPushAttrib(GL_LIST_BIT);
    glListBase(font);
    glCallLists(strlen(s), GL_UNSIGNED_BYTE, s);
  glPopAttrib();
}
 
GLuint FontCreateBitmaps(HDC   hdc, char  *typeface, int   height, int   weight, DWORD italic) {
	GLuint	base;			
	HFONT	font;			  
 
	base = glGenLists(256);
 
	if (stricmp(typeface, "symbol") == 0)
		font = CreateFont(height, 0, 0, 0, weight, italic, FALSE, FALSE,
						SYMBOL_CHARSET, OUT_TT_PRECIS,
						CLIP_DEFAULT_PRECIS, DRAFT_QUALITY,
						DEFAULT_PITCH, typeface);
	else
		font = CreateFont(height, 0, 0, 0, weight, italic, FALSE, FALSE,
						ANSI_CHARSET, OUT_TT_PRECIS,
						CLIP_DEFAULT_PRECIS, DRAFT_QUALITY,
						DEFAULT_PITCH, typeface);
	SelectObject(hdc, font);
	wglUseFontOutlines(hdc, 0, 255, base, 0.0, 0.0, WGL_FONT_POLYGONS, gmf);
	return (base);
}

Estas son las funciones necesarias para la creación de una fuente outline y el mostrarla por pantalla. Estas fuentes tienen algunas ventajas:

  • Pueden ser texturadas, por lo tanto no es necesario deshabilitar la texturización antes de mostrarla.
  • No requieren de la función RasterPos.
  • Pero tardan en cargarse.

La función wglUseFontOutlines hace la misma función que wglUseFontBitmaps, pero a esta le pasamos:

  • El contexto de dispositivo.
  • La primera display list para las fuentes.
  • Número de caracteres glyphs usados.
  • La lista con los caracteres.
  • S
  • La profundidad.
  • WGL_FONT_POLYGONS o WGL_FONT_LINES
  • El array de caracteres glyphs, este es un array del número de caracteres que usamos del tipo GLYPHMETRICSFLOAT. GLYPHMETRICSFLOAT gmf[256];

Para que esté bien texturado, la textura se definirá:

glBindTexture(GL_TEXTURE_2D, texture[3]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, Img[2].imageWidth , Img[2].imageHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, Img[2].imageData );

Pero tendremos que activar GL_TEXTURE_GEN_S y GL_TEXTURE_GEN_T.

Fuentes Bitmap

Consiste en generar pequeños Bitmaps utilizando las fuentes del sistema.

Crear las fuentes

Usaremos una función que nos creará una serie de caracteres a partir de la fuente que le indiquemos, esta es denominada wglUseFontBitmaps.

GLuint FontCreateBitmaps(HDC   hdc, char  *typeface, int   height, int   weight, DWORD italic) {
	GLuint	base;			
	HFONT	font;
 
	base = glGenLists(96);
 
	if (stricmp(typeface, "symbol") == 0)
		font = CreateFont(height, 0, 0, 0, weight, italic, FALSE, TRUE,
						SYMBOL_CHARSET, OUT_TT_PRECIS,
						CLIP_DEFAULT_PRECIS, DRAFT_QUALITY,
						DEFAULT_PITCH, typeface);
	else
		font = CreateFont(height, 0, 0, 0, weight, italic, FALSE, TRUE,
						ANSI_CHARSET, OUT_TT_PRECIS,
						CLIP_DEFAULT_PRECIS, DRAFT_QUALITY,
						DEFAULT_PITCH, typeface);
 
	SelectObject(hdc, font);
	wglUseFontBitmaps(hdc, 32, 96, base);
	return (base);
}

La función que hemos creado, FontCreateBitmaps recibe por parámetro el contexto de dispositivo de la ventana principal, el nombre del tipo de letra, la altura, formato de la fuente, y si la fuente será en cursiva o no. Paso a paso lo que hace es:

  • Declarar un entero, la base de una serie de displaylist que crearemos para cada carácter de nuestra serie de fuentes.
  • Declarar una variable HFONT, el tipo con el que windows identifica una fuente.
  • Reservaremos espacio para 96 listas de visualización (caracteres que tendrá nuestra librería) en la base de las displaylists.
  • En el siguiente if, lo que hacemos es comprovar de qué tipo es el nombre de la fuente que le hemos pasado y si existe o no. Según el caso el programa creará la librería de una forma o de otra.
  • Para crear una fuente usamos la función CreateFont, a la que le pasamos los siguientes parámetros:
    • Tamaño
    • Los tres siguientes no los usamos, por lo tanto los dejamos a 0.
    • Propiedades: FW_THIN, FW_LIGHT, FW_BOLD…
    • Si es en cursiva (1) o no (0).
    • Si está subrayada o no (TRUE o FALSE).
    • Tachada o no (TRUE o FALSE).
    • Tipo de carácter que usaremos: ANSI (el estándar ASCII: ANSI_CHARSET) o UNICODE (el usado por windows: SYMBOL_CHARSET). Aquí diferenciamos según el tipo de fuente.
    • Los siguientes tres parámetros (OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS, DRAFT_QUALITY) son los que se refieren a la precisión y calidad ¿?.
    • En el siguiente parámetro elegimos el pitch (o tono), usaremos el pitch por defecto: DEFAULT_PITCH.
  • Ya tenemos un indicador para la fuente, ahora tendremos que pasarselo al sistema operativo para que él haga lo que tenga que hacer: crear un puntero hacia la fuente indicada con las características específicas. Lo haremos con SelectObject (el contexto de dispositivo y el índice de la fuente).
  • wglUseFontBitmaps generará los mapas de bits correspondientes a cada carácter especificado, le debemos de pasar el contexto de dispositivo, en que posición del codigo ASCII empezará a crearlos, cuantos creará y las listas que llenará con estos.

La función retornará las listas con los bitmaps ya creados. Un ejemplo que usamos para llamarla (siendo FontGrande ha de ser un GLuint):

FontGrande  = FontCreateBitmaps(hDC, "Arial", 36, FW_BOLD, 0);

Eliminar la fuente

  • Para borrar una fuente: glDeleteLists(font, 96);, así eliminamos los 96 caracteres (contenidos en las listas de visualización) de font.

Usar la fuente

void FontPrint(GLuint font, char   *s) {
  glPushAttrib(GL_LIST_BIT);
    glListBase(font - 32);
    glCallLists(strlen(s), GL_UNSIGNED_BYTE, s);
  glPopAttrib();
}

Esta es la función que usaremos para mostrar por pantalla una cadena de caracteres con la fuente indicada (estos son los parámetros que requiere, la fuente y la cadena). El código que realiza es guardar y recuperar el identificador de la lista que usa en ese momento para poder ir al principio de la lista base (font). Luego llamamos a los elementos de la matriz de listas que necesitamos, esto lo hacemos con glCallLists pasandole el número de elementos, el tipo de elementos y los caracteres que tendrá que mostrar por pantalla.
No olvidemos que en la fuente no se define ni el color ni la posición, la posición la definiremos con glRasterPos:

GlColor3f(0.1, 0.1, 1.0);
glRasterPos2f(0.0, 0.0);
FontPrint(FontGrande, "La fuente pequena");
  • Para un correcto uso, no deben de estar activadas las texturas 2d.

Fuentes a partir de un .bmp

El uso de las fuentes en un bmp no tiene gran complicación, debemos de tener en cuenta donde están colocadas las letras y que estas estén bien colocadas, de esta forma podríamos cargarlas dentro de display lists y luego mostrarlas.
Al guardarlas en una lista no debemos olvidar finalizar la lista con un translatef, para que la siguiente que se muestre lo haga al lado. Podemos mostrar las letras de un color más oscuro si las mostramos dos veces (si usamos el blending para mostrarlas).

void CreaFuentes (GLuint textura) {
	float cx;
	float cy;
	iFuents = glGenLists(256);
	glBindTexture(GL_TEXTURE_2D, textura);
	for (int i = 0; i < 256; i++) {
		cx = float(i%16)/16.0;
		cy = float(i/16)/16.0;
		glNewList(iFuents + i, GL_COMPILE);
		glBegin(GL_QUADS);
			glTexCoord2f(cx, 1- cy - 0.0625); glVertex2f(0,0);
			glTexCoord2f(cx + 0.0625, 1-cy-0.0625); glVertex2f(1.6,0);
			glTexCoord2f(cx+0.0625, 1-cy); glVertex2f(1.6,1.6);
			glTexCoord2f(cx, 1-cy); glVertex2f(0,1.6);
		glEnd();
		glTranslatef(1.0,0,0);
		glEndList();
	}
}
void EliminaFuentes() {
	glDeleteLists(iFuents,256);
}
void DibujaFuentes (float x, float y, char *string, int set, GLuint textura) {
	if (set>1)
		set=1;
	glBindTexture(GL_TEXTURE_2D, textura);		
	glPushMatrix();								
		glTranslated(x,y,0);					
		glListBase(iFuents-32+(128*set));		
		glCallLists(strlen(string),GL_BYTE,string);
	glPopMatrix();								
}
// Llamada: 
DibujaFuentes(0,0,"hola",0, texture);

Llamando a glCallLists hacemos que se llamen a un numero de listas igual al número de caracteres de nuestra cadena, en formato byte, así pasaríamos las letras a bytes…. que ya están convertidas en displaylists.

Otros

Que no se vean los píxels no negros

Para conseguir este efecto la imagen usada ha sido una tga de 32 bits y con canal alpha (añadido desde photoshop desde panel ‘canales’), este canal alpha es en blanco y negro, nuestro objetivo es que sólo se muestre la parte del dibujo que en el canal alpha está en blanco.
Primero cargamos la imagen, nos sirve la función ya realizada, y la pasaremos como textura (como es de 32 bits y no de 24 es GL_RGBA).

LoadTGAFile("Tex/Julian2.tga", &Img);
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, Img.imageWidth , Img.imageHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, Img.imageData);

La imagen sigue siendo cuadrada, por lo tanto no necesitaremos ninguna coordenada especial ni ná :P, se aplicará a la figura como cualquier otra, pero eso sí, necesitaremos añadir estas líneas:

glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_ALPHA_TEST);
glAlphaFunc(GL_GREATER, 0);
  • Activamos blend para hacer efectos con los degradados en alpha, sino hubiese blend todo se mostraría lo que no fuese negro en el canal alpha.
  • Activamos el canal alpha e indicamos que el color mayor que 0 (negro) se muestre.

Aún así también podemos hacer algo parecido con sólo las siguientes funciones:

	glDisable(GL_DEPTH_TEST);
	glBlendFunc(GL_ONE,GL_SRC_ALPHA);
	glEnable(GL_BLEND);	
 
glDepthFunc (GL_LEQUAL);
glDisable(GL_DEPTH_TEST);
glShadeModel (GL_SMOOTH);
glBlendFunc(GL_ONE,GL_SRC_ALPHA);
glEnable(GL_BLEND);	

Esto hace que la parte de la imagen no negra se fusione con lo de atrás. Queda mu chulo.

Conversión de un .tga de 24b a 32b

Dentro de la función de carga de tga cambiamos el bitCount a 32.

tgaFile->bitCount = 32;
colorMode = tgaFile->bitCount / 8;

Tenemos que tener una variable unsigned char *nombre (en nuestro caso RGB) y prepararla para volcar la imagen y volcarla:

tgaFile->imageData = (unsigned char*)malloc(sizeof(unsigned char)*imageSize);
RGB = (unsigned char*)malloc(sizeof(unsigned char)*imageSize);
fread(RGB, sizeof(unsigned char), imageSize, file);

Ahora tenemos la imagen del archivo en la variable RGB vamos a convertirla (en los tga RGB es BGR, por lo tanto tendremos que pasar el bit de rojo al azul).

	AlphaIdx = 0;
	for (imageIdx=0; imageIdx < imageSize; imageIdx +=3)
	{
		colorSwap=RGB[imageIdx];
 
		RGB[imageIdx]=RGB[imageIdx+2];
		RGB[imageIdx+2]=colorSwap;

Y ahora volcamos la variable RGB a tgaFile->ImageData.

		tgaFile->imageData[AlphaIdx] = RGB [imageIdx];
		tgaFile->imageData[AlphaIdx+1] = RGB [imageIdx+1];
		tgaFile->imageData[AlphaIdx+2] = RGB [imageIdx+2];

Pero tgaFile->imageData no es rgb sino rgba, por lo tanto tiene otro bit que si los bits rgb son 0 este será transparente (0) y si no, no (255).

		if ((tgaFile->imageData[AlphaIdx+2] == 0) && (tgaFile->imageData[AlphaIdx+1]==0) 
			&& (tgaFile->imageData[AlphaIdx] == 0))
			tgaFile->imageData[AlphaIdx+3] = 0;
		else 
			tgaFile->imageData[AlphaIdx+3] = 255;
		AlphaIdx += 4;
	}

Los bits en tgaFile->imageData se han indexado mediante la variable entera AlphaIdx. Luego no debemos olvidar cargar la textura como si esta tubiese un canal alpha.

Material onduleante

Necesitaremos dos variables, una una matriz de floats para las posiciones y otra un entero contador de velocidad:

float puntos[145][145][3];;
int ContVel = 0;

Primero llenaremos el array:

for(int x=0; x<145; x++) {
	for(int y=0; y<145; y++) {
		puntos[x][y][0]=float((x/5.0f)-4.5f);
		puntos[x][y][1]=float((y/5.0f)-4.5f);
		puntos[x][y][2]=float(sin((((x/5.0f)*20.0f)/360.0f)*3.141592654*2.0f));
	}
}

Esto provocará una ondulación considerable. Para provocar un mayor número de ondulaciones, en (sin((((x/5.0f)*20... la x podría ser x%3.
Luego tendremos que dibujar la figura:

for(int x = 0; x < 144; x++ ) {
	for (int y=0; y<144; y++) {
	glTexCoord2f(0.0,1.0);
	glVertex3f( puntos[x][y+1][0], puntos[x][y+1][1], puntos[x][y+1][2] );
	glTexCoord2f(0.0,0.0);
	glVertex3f( puntos[x][y][0], puntos[x][y][1], puntos[x][y][2] );		
	glTexCoord2f(1.0,0.0);
	glVertex3f( puntos[x+1][y][0], puntos[x+1][y][1], puntos[x+1][y][2] );
	glTexCoord2f(1.0,1.0);
	glVertex3f( puntos[x+1][y+1][0], puntos[x+1][y+1][1], puntos[x+1][y+1][2] );
}

Y luego tendremos que hacer que la ondulación se vaya moviendo. Este if (ContVel==5) podríamos cambiarlo para que se igualase a un número mayor (para una ondulación más lenta) o menor.

if(ContVel == 5 ) {
	for(int y = 0; y < 45; y++ ) {
		hold=puntos[0][y][2];
		for(int x = 0; x < 44; x++)
			puntos[x][y][2] = puntos[x+1][y][2];
		puntos[44][y][2]=hold;
	}
	ContVel= 0;
}
ContVel++;

Fullscreen y cambio de resolución

Fullscreen

Para hacer nuestra ventana opengl fullscreen lo primero que debemos hacer es crear una ventana sin borde, ni título y que esté sobre la barra de inicio de windows, para ello sólo debemos pasar a la función CreateWindowEx los valores WS_POPUP | WS_CLIPCHILDREN | WS_VISIBLE.

hWnd = CreateWindowEx (
  0, "agl", "", WS_POPUP | WS_CLIPCHILDREN | WS_VISIBLE,        
  CW_USEDEFAULT, CW_USEDEFAULT, 
  640, 480, HWND_DESKTOP, 
  NULL, hInstance,  NULL);

Si ejecutas este código y trabajas a una resolución mayor de 640×480 (cosa que probablemente hagas) verás que aparece una ventana con las características descritas, si adaptas esta llamada a tu resolución ya tendrás tu aplicación fullscreen.

Nota 1: Si la imágen que muestra tu aplicación fullscreen muestra un intenso parpadeo puede que sea debido a que no limpias correctamente el COLOR_BUFFER. Para ello antes de iniciar la escena haz:

glClearColor (0.0f, 0.0f, 0.0f, 0.0f);
glClear (GL_COLOR_BUFFER_BIT);

Nota 2: Si al iniciar la aplicación la ventana que aparece es del color de una ventana normal y en poco tiempo esta pasa a mostrar la imágen deseada y esto no es de tu agrado, piensa que es posible que sea debido a que cuando registras la clase indicas que la ventana tenga el color de una corriente (wincl.hbrBackground = (HBRUSH) COLOR_BACKGROUND;), tal vez prefieras definirla como negra (wincl.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);).

Cambio a resolución deseada

Para que esto sea posible habrá que echar mano de la función ChangeDisplaySettings, esta función requiere de una variable DEVMODE la cual es una estructura de la que nos interesa definir únicamente la anchura (dmPelsWidth), la altura (dmPelsHeight) y el modo de color (dmBitsPerPel); una vez definidos debemos indicar que los hemos definido asignando DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT a su propiedad dmFields. Estos dos elementos cambian temporalmente la resolución de pantalla.

A ChangeDisplaySettings se le pasa una referencia al DEVMODE y unas flags, como probablemente cambies la resolución para cambiar a fullscreen la flag ha de ser CDS_FULLSCREEN (que te librará de la barra de inicio), si quieres indicar los valores por defecto en vez de pasar flags pasa un 0. Si todo ha ido bien, ChangeDisplaySettings devolverá DISP_CHANGE_SUCCESSFUL.

DEVMODE dmScreenSettings;
memset(&dmScreenSettings, 0, sizeof(dmScreenSettings));
dmScreenSettings.dmSize = sizeof(dmScreenSettings);
dmScreenSettings.dmPelsWidth = 640;
dmScreenSettings.dmPelsHeight = 480;
dmScreenSettings.dmBitsPerPel = 32;
dmScreenSettings.dmFields=DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT;
 
if (ChangeDisplaySettings(&dmScreenSettings, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL)
    // Indicar que ocurre si no se cambia correctamente la resolución

Si hemos cambiado la resolución para crear una aplicación fullscreen chachi (que es lo más probable) el siguiente paso será calcular cual ha de ser el tamaño de nuestra ventana para que ocupe toda la pantalla. Para ello utilizaremos la función AdjustWindowRectEx, que lo que hace es calcular qué tamaño pasar a CreateWindowEx para que esta nos cree una ventana con el tamaño del area de cliente indicado a esta. Le pasamos los siguientes parámetros:

  1. Una referencia a un RECT, donde se le indica el tamaño deseado del area de cliente y nos devuelve el tamaño que debemos indicar.
  2. El estilo de la ventana.
  3. Un boolean que indique si la ventana tendrá o no menú.
  4. El estilo 'Ex'; si utilizamos CreateWindowEx hay que pasarle un estilo 'Ex', este se indica aquí.
RECT r;
r.top = 0;
r.left = 0;
r.right = 640;
r.bottom = 480;
AdjustWindowRectEx(&r, WS_POPUP | WS_CLIPCHILDREN | WS_VISIBLE, false, 0);

Para volver a la resolución por defecto sólo tendremos que hacer una llamada como la siguiente:

ChangeDisplaySettings(NULL,0);

Alternar ventana\fullscreen

Para alternar de ventana a fullscreen o cambiar de resolución, lo que debemos hacer es cerrar la ventana actual, liberar opengl y volver a hacer los pasos para mostrar una ventana desde 0.

1. Volver a la resolución anterior
ChangeDisplaySettings(NULL,0);
2. Liberar OpenGL
wglMakeCurrent(NULL,NULL);
wglDeleteContext(hRC);
hRC=NULL;
3. Cerrar nuestra ventana
ReleaseDC(hWnd,hDC);                     
DestroyWindow(hWnd);
UnregisterClass("OpenGL",hInstance);    
// Siendo OpenGL el nombre de nuestra clase registrada, hInstance la instancia, hWnd, hDC y hRC ya te imaginarás quienes son.
4. Volver a crear la ventana
  1. Crear y registrar la WNDCLASS, ahora necesitarás utilizar la función GetModuleHandle para recoger la hInstance.
  2. Cambiar a la nueva resolución, usando ChangeDisplaySettings.
  3. Calcular el tamaño de la ventana AdjustWindowRectEx.
  4. Configurar OpenGL utilizando ChoosePixelFormat, SetPixelFormat, wglCreateContext y wglMakeCurrent.
  5. Y mostrar la ventana con ShowWindow.
Notas
  • Ten cuidado, al cerrar la ventana el windowproc lanza un WM_DESTROY y según como tengas el bucle principal puede cerrar tu ventana; en otras palabras, quien debería lanzar el PostQuitMessage(0) es WM_CLOSE.

Recoger resolución actual

Esto es algo muy sencillo, existe una función llamada EnumDisplaySettings a la cual le pasas tres parámetros:

  1. El dispositivo del que quieres mirar sus características, si es NULL es el dispositivo por defecto.
  2. ENUM_CURRENT_SETTINGS o ENUM_REGISTRY_SETTINGS, si lo que quieres es saber la resolución actual o la que marca el registro del operativo (esta última es la que tiene por defecto al iniciar, si no la han cambiado).
  3. Una referencia a un DEVMODE.

Una vez llamada a la función, el DEVMODE se rellenará con los datos pedidos.

DEVMODE devmode;
EnumDisplaySettings (NULL, ENUM_CURRENT_SETTINGS, &devmode);
int ancho = devmode.dmPelsWidth;
int alto = devmode.dmPelsHeight;

Detalle de funciones

glPushAttrib y glPopAttrib

glPushAttrib guarda el estado de atributos (valores del buffer de acomulación, de luces, de viewport, de transformación…), si los quisiesemos guardar todos usariamos GL_ALL_ATTRIB_BITS, y luego vuelve a cargarlos tal como estaban con glPopAttrib.
Las propiedades que podemos guardar son:

  • GL_LIST_BIT: Correspondientes a la base de glCallLists.
  • GL_ENABLE_BIT: Para texturas, luces, blending, niebla… Todo respecto a glEnable.

glHint

Permite cambiar “preferencias” dentro de OpenGL.
A veces existen funciones que se pueden llevar a cabo de dos formas distintas, por ejemplo más rápidas pero menos vistosas o al contrio, más lentas pero más vistosas. Para elegir qué tipo de funciones escogemos utilizaremos glHint. Recibe dos parámetros:

  • A qué le aplicamos las preferencias:
    • GL_FOG_HINT: Para la niebla
    • GL_LINE_SMOOTH_HINT: Indica la calidad del efecto antialising en el dibujo de líneas.
    • GL_PERSPECTIVE_CORRECTION_HINT: Permite mejorar la calidad del color y de texturas en la perspectiva.
    • GL_POINT_SMOOTH_HINT: Indica la calidad de los puntos con antialising.
    • GL_POLYGON_SMOOTH_HINT: Indica la calidad de los polígonos con antialising.
  • Cómo se las aplicamos:
    • GL_FASTEST: que el proceso sea el más rápido.
    • GL_NICEST: que el proceso sea el que produzca un efecto más chulo.
    • GL_DONT_CARE: es indiferente (por defecto).

glFlush

OpenGL realiza los cálculos de dibujo cuando su buffer se llena, mientras no lo esté va guardando los datos en él.
También puede hacer cálculos de dibujo al hacer el intercambio de buffers. Pero si nosotros quisiesemos que esos cálculos se hiciesen en algún momento específico deberíamos llamar a la función glFlush.

glGetString

Esta función nos permite pedir información sobre OpenGL, para ello le pasamos el flag adecuado y devuelve un char* con lo consultado. Podemos pasarle:

  • GL_VERSION, para saber la versión de OpenGL.

Ejemplo de uso en multitexturas.

Notas

fw/ogl/xtra.1216146527.txt.gz · Última modificación: 2020/05/09 09:24 (editor externo)