¡Esta es una revisión vieja del documento!
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.
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.
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
]
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);
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
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.
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);
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);
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.
glEnableClientState(y el array) y para desactivarlos glDisableClientState(y el array).glColorPointer(parámetros), glEdgeFlagPointer(parámetros), glIndexPointer(parámetros), glNormalPointer(parámetros), glTexCoordPointer(parámetros), glVertexPointer(parámetros).glDrawArrays(parámetros), glDrawElements(parámetros), glDrawRangeElements(parámetros), glArrayElement(parámetros).
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:
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…
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):
glSampleCoverage, esta función permite especificar un valor de alpha.
Para utilizar las multitexturas tendrás que echar mano de alguna extensión de OpenGL. (GLext, GLee, glew…).
El funcionamiento de estas se basa en los distintos niveles de texturas. Para definirlas tendrás que seguir los siguientes pasos:
glActiveTexture pasandole GL_TEXTUREn, siendo n el nivel (que por defecto es 0).glBind.glEnable(GL_TEXTURE_2D).
Una vez hecho esto estaremos en el nivel n, para volver a utilizar texturas por defecto iremos al 0 mediante glActiveTexture.
Para aplicarlas utilizaremos glMultiTexCoord2f en vez de glTexCoord2f al cual habrá que pasar el nivel.
glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, text2); glEnable(GL_TEXTURE_2D); glActiveTexture(GL_TEXTURE2); glBindTexture(GL_TEXTURE_2D, text1); glEnable(GL_TEXTURE_2D); glActiveTexture(GL_TEXTURE0); glEnable(GL_TEXTURE_2D); ... glBindTexture (GL_TEXTURE_2D, text2); glPushMatrix (); glTranslatef(3.0f, 3.0f, 0.0f); glBegin(GL_QUADS); glTexCoord2f(0.0f, 1.0f); glVertex2f(-1.0f, 1.0f); glTexCoord2f(0.0f, 0.0f); glVertex2f(-1.0f, -1.0f); glTexCoord2f(1.0f, 0.0f); glVertex2f(1.0f, -1.0f); glTexCoord2f(1.0f, 1.0f); glVertex2f(1.0f, 1.0f); glEnd(); glPopMatrix(); ... glBegin(GL_QUADS); glMultiTexCoord2f(GL_TEXTURE1, 0.0f, 1.0f); glMultiTexCoord2f(GL_TEXTURE2, 0.0f, 1.0f); glVertex2f(-1.0f, 1.0f); glMultiTexCoord2f(GL_TEXTURE1, 0.0f, 0.0f); glMultiTexCoord2f(GL_TEXTURE2, 0.0f, 0.0f); glVertex2f(-1.0f, -1.0f); glMultiTexCoord2f(GL_TEXTURE1, 1.0f, 0.0f); glMultiTexCoord2f(GL_TEXTURE2, 1.0f, 0.0f); glVertex2f(1.0f, -1.0f); glMultiTexCoord2f(GL_TEXTURE1, 1.0f, 1.0f); glMultiTexCoord2f(GL_TEXTURE2, 1.0f, 1.0f); glVertex2f(1.0f, 1.0f); glEnd();
Debemos recordar activar la GL_TEXTURE0, al acabar de definir las anteriores, que es por la que trabajamos por defecto.
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:
Para leer un bitmap hemos de tener en cuenta como están formados interiormente:
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;
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); }
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.
glDrawPixels se le pasan cinco parámetros:
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.
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:Por lo tanto, para que una imagen ocupe toda la ventana:
glPixelZoom usando estas dos variables (anchura y altura).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.
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:
La función wglUseFontOutlines hace la misma función que wglUseFontBitmaps, pero a esta le pasamos:
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.
Consiste en generar pequeños Bitmaps utilizando las fuentes del sistema.
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:
CreateFont, a la que le pasamos los siguientes parámetros: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);
glDeleteLists(font, 96);, así eliminamos los 96 caracteres (contenidos en las listas de visualización) de font.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");
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.
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);
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.
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.
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++;
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);).
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:
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);
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.
ChangeDisplaySettings(NULL,0);
wglMakeCurrent(NULL,NULL); wglDeleteContext(hRC); hRC=NULL;
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.
GetModuleHandle para recoger la hInstance.ChangeDisplaySettings.AdjustWindowRectEx.ChoosePixelFormat, SetPixelFormat, wglCreateContext y wglMakeCurrent.ShowWindow.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.
Esto es algo muy sencillo, existe una función llamada EnumDisplaySettings a la cual le pasas tres parámetros:
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;
Antes de empezar a desarrollar para Linux en OpenGL es necesario saber si nuestro sistema puede renderizar código OpenGL, para ello utilizaremos el comando glxinfo de la siguiente forma:
glxinfo | grep "rendering"
Este comando puede servirnos también para saber qué versión tenemos y la marca de los drivers:
glxinfo | grep "version" glxinfo | grep "vendor"
Para comprobar el funcionamiento de OpenGL en Linux ejecutaremos el comando glxgears.
En caso de no funcionar deberíamos instalar Mesa 3d.
Al desarrollar en X11 será necesario hacer dos cosas:
X11 al preprocesador compilando con: -lX11#include <X11/Xlib.h> Una vez realizados los pasos anteriores podremos usar las funciones para el servidor X. Las que nos importan ahora son:
Display sobre el que se creará la ventana, para ello le preguntamos al sistema utilizando la función getenv.Display.Display *dpy = XOpenDisplay(getenv("DISPLAY")); XCloseDisplay(dpy);
GLX es la librería que proporciona un enlace entre las X y OpenGL activando la posibilidad de dibujo 3d sobre las ventanas. Para desarrollar con esta debemos añadir la librería GL (compilando con -lGL).
La versión actual de GLX es la 1.3 que corresponde a la OpenGL 1.2.
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:
glCallLists.glEnable.
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:
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.
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:
Ejemplo de uso en multitexturas.