Documentos de Académico
Documentos de Profesional
Documentos de Cultura
GESTIÓN DE VENTANAS..................................................................................................................3
WINMAIN()............................................................................................................................................3
WNDPROC()............................................................................................................................................9
EJEMPLO...............................................................................................................................................10
RENDERIZAR OPENGL EN UNA VENTANA WIN32......................................................................................12
GESTION DE EVENTOS DEL RATON CON WIN32..........................................................................................15
UTILIZACIÓN DEL TALLER DE RECURSOS DE BORLAND Y EL COMPILADOR DE
RECURSOS..........................................................................................................................................17
RECURSOS ............................................................................................................................................17
Tipos de recursos..........................................................................................................................17
USO DEL TALLER DE RECURSOS DE BORLAND..............................................................................................18
Creación de un proyecto de recursos:..........................................................................................18
CREACIÓN DE UN CUADRO DE DIALOGO......................................................................................................18
Incluir un menú en un cuadro de diálogo.....................................................................................19
Cambiar las propiedades de las ventanas....................................................................................19
Añadir controles a un cuadro de diálogo.....................................................................................20
Programación de un cuadro de diálgo con el API de Windows...................................................20
CREACIÓN DE UN MENÚ...........................................................................................................................21
Añadir elementos de menú y separadores.....................................................................................21
Añadir aceleradores a menús.......................................................................................................23
PROGRAMACIÓN DE MENÚS Y ACELERADORES CON EL API DE WINDOWS........................................................23
IDENTIFICADORES....................................................................................................................................23
Creacion de un fichero de identificadores....................................................................................24
Añadir identificadores ..................................................................................................................24
DIBUJO GEOMÉTRICO DE OBJETOS.........................................................................................25
COLOR DE FONDO...................................................................................................................................25
PRIMITIVAS DE DIBUJO.............................................................................................................................26
Puntos............................................................................................................................................27
Líneas............................................................................................................................................27
Poligonos......................................................................................................................................29
TRANSFORMACIONES DE VISUALIZACIÓN............................................................................31
MATRIZ DE VISUALIZACIÓN......................................................................................................................31
VISTAS.................................................................................................................................................32
gluLookAt()...................................................................................................................................32
glOrtho().......................................................................................................................................33
glFrustum()...................................................................................................................................34
gluPerpespective()........................................................................................................................34
ASPECTOS AVANZADOS................................................................................................................36
LISTAS..................................................................................................................................................36
ILUMINACIÓN.........................................................................................................................................38
TEXTURAS.............................................................................................................................................41
SELECCION DE OBJETOS EN OPENGL.........................................................................................................47
Gestión de Ventanas
La creación de una aplicación fundamentada en el entorno Windows es una tarea de por sí
bastante complicada. En este apartado se indicarán los pasos a seguir para poder crear un
programa de ventanas sobre un entorno Windows.
La creación de una aplicación sobre el entorno Windows obliga a realizar una gestión sobre
las típicas ventanas de Windows, para ello el programador deberá tener en cuenta una serie
de características y propiedades que pueden tener dichas ventanas.
Las etapas necesarias para desarrollar una ventana se dividirán en varias partes:
• Descripción de la ventana WinMain().
• Registro de las clases de Windows.
• Creación de la ventana.
• Gestión y control de la ventana WndProc().
El código para crear y gestionar una ventana se fundamentará en dos funciones.
WinMain()
La función WinMain() es la primera función que se ejecuta en una aplicación Windows. Esta
función es la equivalente a la función Main() de un programa en C.
El prototipo de la función es el siguiente:
Esta declaración implica que Windows espera la misma convención de llamada que utiliza
en otras funciones Windows. Una vez compilado y enlazado, Windows no sabe en que
lenguaje a estado escrito el programa, por consiguiente no puede llamar a esta función según
las convenciones de llamada de C++, por lo tanto Windows define una estructura de llamada
única que deben utilizar todos los lenguajes.
La función WinMain() tiene como argumentos de entrada:
• hInstance: Es el handle del programa en uso, este argumento puede ser
utilizado por una aplicación Windows para acceder a información acerca del
estado del programa.
• hPrevInstance: Siempre tiene un valor 0, en el caso de Windows 95 este valor
se ignora. Este valor es utilizado para tener un control sobre el número de
veces que se activa el mismo programa. En el caso de Windows 95, este
almacena la información de cada ejecución del programa independientemente,
por lo tanto este valor es ignorado.
• lpszCmdLIne: Puntero a una cadena terminada con el valor 0. Señala todos
aquellos parámetros de la línea de comandos que pasan por el programa. Es
Typedef struct_WNDCLASSW{
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HISTANCE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCWSTR lpszMenuName;
LPCWSTR lpszClassName;
} WNDCLASS
Esta estructura almacena las características de una ventana del entorno Windows. A
continuación describiremos cada uno de los campos de la clase WNDCLASS.
Style
Controla las características de redibujo de la ventana. Así por ejemplo si toma el valor
CS_NOCLOSE, estamos indicando que la ventana no tendrá la opción de Cerrar en el marco
de la ventana. Los valores con prefijo CS_ están definidos en la librería Windows.h.
Es posible combinar diferentes valores en el valor style, para ello cada constante CS_ se
define como un único bit. La siguiente expresión combina varios estilos:
Esta asignación indica que la ventana no tendrá la opción de Cerrar en el marco, además que
enviará mensajes de doble clic a la ventana.
En la siguiente tabla se puede ver los diferentes valores que puede coger el valor style. Estas
constantes están definidas en la librería Windows.h.
Constante Mascara
CS_VREDRAW 0x0001
CS_HREDRAW 0x0002
CS_OWNDC 0x0020
CS_CLASSDC 0x0040
CS_PARENTDC 0x0080
CS_SAVEBITS 0x0800
CS_DBLCLKS 0x0008
CS_BYTEALIGNCLIENT 0x1000
CS_BYTEALIGNWINDOW 0x2000
CS_NOCLOSE 0x0200
CS_KEYCVTWINDOW 0x0004
CS_NOKEYCVT 0x0100
CS_GLOBALCLASS 0x4000
lpfnWndProc
Este valor es un puntero a una función que esta relacionada con cualquier ventana creada con
la clase de ventana.
cbClsExtra y cbWndExtra
Estos dos valores definen la clase y las extensiones de la ventana. Estos valores pueden ser
utilizados para añadir miembros de datos al final de la clase de estructura ventana. C++
gestiona mucho mejor estos elementos y por lo tanto se recomienda que se le asigne el valor
0.
hInstance
Este parámetro esta definido para el manejo de la aplicación en uso. Este elemento permite
asociar la clase de ventana al programa asociado.
hIcon
Este valor nos permite manipular el icono asociado a la aplicación. Windows asigna un valor
por defecto, normalmente asigna el icono IDI_APPLICATION, existen otros tipos de iconos
por defecto como son: IDI_ASTERISK, IDI_EXCLAMATION, IDI_HAND,
IDI_QUESTION.
Cabe la posibilidad de que el programador asigne un icono propio para ello deberá utilizar la
siguiente sentencia:
hCursor
Este valor se utilizará para definir el handle que se utilizará para el cursor. De igual forma
que pasa el caso del icono, Windows utiliza la flecha que es el cursor por defecto, ahora
bien se puede modificar y adaptar al gusto del programador. Algunos de los cursores que
tiene Windows por defecto son: IDC_CROSS, IDC_IBEAM, IDC_NO, IDC_WAIT.
De la misma forma que ocurría con el icono el programador puede asignar el tipo de cursor
que desee, para ello deberá utilizar la sentencia:
hbrBackground
Se utiliza para determinar el pincel (paint-brush) de colores que se utilizara para el fondo de
la ventana de aplicación. Al ser los entornos de ventanas totalmente gráficos es necesario
utilizar un pincel de color, así si deseamos pintar el fondo de color amarillo deberemos coger
un pincel amarilla. Los pinceles de Windows no solo permite pintar con un color uniforme
sino que también pueden utilizar patrones.
La asignación siguiente define como color de fondo el color blanco, si deseamos cambiar el
color de fondo, es suficiente con modificar el índice de la asignación.
Los colores posibles que podemos utilizar para el fondo del área de trabajo de la aplicación
serán:
Color Índice
Rojo Oscuro 1
Verde 2
Mostaza 3
Azul Marino 4
Violeta 5
Cían 6
Gris 25% 7
Verde claro 8
Azul Claro 9
Marrón 10
Gris 40% 11
Gris 50% 12
Rojo Brillante 13
Verde Brillante 14
Amarillo 15
Azul Brillante 16
Violeta Brillante 17
Cían Brillante 18
Blanco 19
Negro 20
Debemos tener en cuenta que los colores dependerán de la paleta de colores del monitor. Así
tenemos que si la configuración es a 256 colores no se distinguirá diferencia entre los colores
brillantes y los oscuros, por lo tanto se recomienda que el monitor tenga la configuración a
Color de Alta densidad.
lpszMenuName
Señala el nombre de la clase de menú que se va a utilizar con la ventana, en el caso que no se
tenga menú deberemos asignarle el valor NULL, si tenemos menú deberemos asignarle el
nombre que se ha definido para él.
lpszClassName
Con este valor podemos asignar un nombre a la ventana, normalmente se utiliza el mismo
nombre para la ventana y para la clase, pero no tiene porque ser así.
Una vez que hemos inicializado estos valores es el momento de registrar la ventana para ello
utilizaremos la sentencia RegisterClass(&wc), donde wc es una variable de tipo
hWnd CreateWindow(
LPCTSTR lpszClassName,
LPCTSTR lpzWindowName,
DWORD dwStyle,
int nX,
int nY,
int nWidth,
int nHeight,
HWND hwndParent,
HMENU hmenu,
HANDLE hinst,
LPVOID lpvParam
);
En el caso del programa que se muestra a final de la sección tendremos el siguiente código
para la creación de la ventana, que difiere en gran parte de la estructura CreateWindow():
hWnd = CreateWindow(
szAppName,
szTitle,
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
WIDTH,
HEIGHT,
NULL,
NULL,
hInstance,
NULL
);
szAppName: Será el nombre que se utilizará para registrar la clase de ventana de Windows,
este mismo nombre es el que hemos utilizado anteriormente cuando se registraba la clase de
ventana.
szTitle: Es él titulo de la ventana que creamos.
WS_OVERLAPPEWINDOW: Este valor especifica ciertos detalles de la ventana.
Normalmente se le asigna el valor WS_OVERLAPPEWINDOW, ya que define la ventana
como una ventana estandar del Windows, existen otras opciones como es
WS_SCROLL,WS_MINIMIZE, WS_MAXIMIZE. La primera define que la ventana tendrá
una barra de scroll horizontal, el segundo la posibilidad de minimizar la ventana, y
finalmente la posibilidad de maximizar. Todas estas descripciones las podemos encontrar en
el include Windows.h junto a un número elevado valores para la ventana.
Los dos siguientes argumentos descritos por, CW_USEDEFAULT, definen la posición
inicial de la ventana, es decir las coordenadas x, y de la ventana. Este valor en nuestro caso
es el propio Windows que especifica un valor por defecto.
WndProc()
Esta función es la encargada de gestionar los diferentes eventos que suceden en el programa,
en esta función es donde el programador debe especificar que ocurrirá en su aplicación según
el tipo de evento que suceda.
El prototipo de la función es:
En el ejemplo que detallaremos al final de la sección podremos ver como nuestra aplicación
no responde a muchos eventos.
Ejemplo
En este punto veremos el código necesario para crear una ventana con fondo blanco, la cual
pueda redimensionarse, y que tenga todas las posibilidades de una ventana Windows.
Nombre
ventana
Cerrar
Área de trabajo
Una vez creada la ventana podemos ver que tiene todas las funciones típicas de una ventana
Windows. Además tenemos la posibilidad de redimensionar la ventana como cualquier
aplicación Windows.
El siguiente código fuente crea la ventana anterior.
//==============================================
// Nombre del programa: Proyecto1
//
// Descripción: Crea y gestiona una ventana del
// entorno Windows
//==============================================
#include <windows.h>
#include <windowsx.h>
HDC ghDC;
HGLRC ghRC;
HINSTANCE hInst;
// ==================================================
// Nombre de la funcion: winMain
// ==================================================
if (!hWnd) return(FALSE);
ShowWindow( hWnd, nCmdShow );
UpdateWindow( hWnd );
while (GetMessage(&msg,NULL,0,0)){
TranslateMessage( &msg );
DispatchMessage( &msg );
}
return( msg.wParam );
}
// ==================================================
// Nombre de la funcion: WndProc
// ==================================================
LONG WINAPI WndProc (HWND hWnd, UINT msg, WPARAM wParam, LPARAM
lParam)
{
LONG lRet = 1;
RECT rect;
PAINTSTRUCT ps;
HDC hdc;
static HINSTANCE hInstance;
switch (msg){
case WM_PAINT:
BeginPaint(hWnd, &ps);
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
if (ghRC) wglDeleteContext(ghRC);
if (ghDC) ReleaseDC(hWnd, ghDC);
PostQuitMessage (0);
break;
default:
lRet=DefWindowProc(hWnd,msg,wParam,lParam);
break;
}
return lRet;
}
#include "stdafx.h"
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>
int nMyPixelFormatID;
HDC hDC;
HGLRC hRC;
switch (msg)
{
case WM_CREATE:
// Mensaje de creacion de la ventana
// Seleccionamos el formato de pixel y entonces
// creamos el contexto de rendering para este.
hRC = SetUpOpenGL( hWnd );
return 0;
case WM_SIZE:
// Se ha cambiado el tamaño de la ventana
// Redefinimos la proyeccion y el viewport
hDC = GetDC(hWnd);
wglMakeCurrent(hDC,hRC);
// Redefinimos la proyeccion
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(60.0, (GLfloat)glnWidth / (GLfloat)glnHeight,
1.0, 200.0);
glMatrixMode(GL_MODELVIEW);
case WM_PAINT:
// Se redibuja la ventana
// Obtenemos el DC y le asociamos el RC
hDC = BeginPaint( hWnd, &ps );
wglMakeCurrent (hDC, hRC);
// Dibujamos la escena
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
glColor3f(1.0,1.0,1.0);
glPushMatrix();
glTranslatef(0.0,0.0,-5.0);
auxWireTeapot(1.0);
glPopMatrix();
SwapBuffers(hDC);
// Deseleccionamos el RC
wglMakeCurrent( hDC, NULL );
case WM_DESTROY :
wglDeleteContext(hRC);
PostQuitMessage(0);
return 0;
}
// Los demas mensajes no los procesamos
return DefWindowProc( hWnd, msg, wParam, lParam );
}
// Registramos la clase
RegisterClass( &wc );
// Mostramos la ventana
ShowWindow( hWnd, nCmdShow );
UpdateWindow( hWnd );
Una ventana WIN32 donde se vaya a renderizar una escena con OpenGL debe, como
minimo, gestionar tres eventos:
• Creación de la ventana
En este momento se debe crear un contexto de rendering. Un contexto de rendering guarda
toda la información que se necesita para renderizar una escena : El z-buffer, el buffer de
color, etc. Cada contexto de rendering se identifica con su handler : el tipo HGLRC.
Se debe crear un contexto de rendering para cada ventana, y cuando se vaya realizar
cualquier operacion con OpenGL, previamente hay que indicar con que contexto de
rendering se va a trabajar. Para crear un contexto de rendering, en el ejemplo utilizamos la
función SetUpOpenGL. Esta función obtiene como parametro el handler de la ventana para
la cual se va a crear el contexto, y devuelve el handler del contexto para esa ventana.
Para indicar cual es el contexto que estamos utilizando actualmente se utiliza la función
wglMakeCurrent, a la cual se le pasa el handler device context de la ventana donde vamos a
renderizar, y el handler del contexto de rendering que hemos obtenido cuando creamos la
ventana. En el ejemplo sólo se ha creado una sola ventana, y por tanto sólo necesitamos un
HGLRC. Si se fuesen a crear mas de una ventana, entonces necesitariamos un HGLRC para
cada una.
• Redibujado de la ventana
Este evento se genera cuando hay que redibujar una parte de la ventana. En este momento es
en el que hay que llamar a la rutina que dibuja la escena. Si estamos utilizando doble
buffering, para que la pantalla no parpadee cuando estamos dibujando, es importante no
olvidarse de llamar a la función SwapBuffers, que es la encargada de intercambiar el buffer
de dibujo con el buffer que se esta visualizando. Si no llamamos a esta función, no se a va a
dibujar absolutamente nada en pantalla.
Estos eran los minimos eventos que una ventana OpenGL debe gestionar. Obviamente hay
muchos mas que seria deseable gestionar. Es muy probable que nuestra aplicación vaya a
tener menus, y quizas también debamos gestionar eventos de raton. En caso que, por motivo
de alguno de ellos vayamos a utilizar codigo OpenGL, entonces no debemos olvidar indicar
cual es el contexto de rendering para el que se va a ejecutar el codigo OpenGL.
A continuación tenemos una lista de algunos de los mensajes generados por un evento de
ratón que puede recibir una ventana:
Con todos estos mensajes se mandan unos parametros que son comunes a todos ellos :
El valor de fwKeys indica el estado de botones a tener en cuenta en un evento del ratón. Este
valor viene dado por una OR bit a bit de los siguientes valores :
xPos y yPos dan la posición del cursor sobre la región cliente durante el evento. La región
cliente es la zona de la ventana sobre la que la aplicación dibuja : No incluye ni el menu, ni
las barras de scroll, la barra de estado ni los bordes de la ventana.
Hay que tener en cuenta que tener en cuenta que los ejes de coordenadas en que se dan xPos
e yPos son diferente con los que trabaja OpenGL. El origen de coordenadas de la ventana de
Windows esta en la esquina superior izquierda, el sentido positivo de la X va hacia la
derecha, y el eje positivo de la Y va hacia abajo. OpenGL trabaja con el sentido positivo de
las X hacia la derecha, y el eje positivo de las Y hacia arriba. Así, si se intenta hacer una
correspondencia entre las coordenadas de la ventana Windows y coordenadas sobre el
viewport de OpenGL hay que cambiar el valor de la coordenada Y :
Recursos
Los archivos de recursos permiten añadir a nuestro programa componentes interactivos con
el usuario como los menús, teclas rápidas y cuadros de diálogo.
El código de recursos se almacena aparte del programa principal en C o C++. Esto asegura
que la información de recursos pueda cargarse en el programa cuando sea necesario,
permitiendo que la usen varias aplicaciones y que se pueda cambiar la representación de una
aplicación sin necesidad de modificar el código original de la misma. BRW nos permite
crear, editar y compilar estos recursos.
Tipos de recursos
Los recursos de aplicación se encuadran en diferentes grupos. Entre los más importantes
podemos citar las teclas rápidas, mapas de bits, cursores, iconos, menús y cuadros de
diálogo.
Teclas rápidas: Una tecla rápida es una tecla o combinación de teclas que podemos pulsar
como alternativa a seleccionar con el ratón una opción del menú. Si se añade una tecla
rápida, las selecciones se pueden efectuar sin usar los menús desplegables.
Mapas de bits: Un mapa de bits es un conjunto de datos utilizados por una aplicación para
mostrar información gráfica en la pantalla. Los mapas de bits se pueden usar para mostrar
imágenes gráficas pequeñas o pantallas completas.
Cursores: Un cursor de pantalla se utiliza para seleccionar elementos en la misma o para
establecer el punto de inserción correspondiente a una entrada de datos.
Iconos: Un icono es un pequeño mapa de bits, es una forma gráfica de representar las
aplicaciones que tenemos disponibles. Pulsando con el ratón en un icono se puede activar
una aplicación.
Menús: Los menús de aplicación se visualizan en la parte superior de la ventana. Los menús
listan las opciones del programa que se pueden activar con el ratón o mediante combinación
de teclas clave. Las opciones del menú pueden utilizarse para abrir otros menús o cuadros de
diálogo, así como para realizar acciones específicas. Los menús permiten al usuario la
interacción a bajo nivel con el programa.
Cuadros de diálogo: Los cuadros de diálogo son los métodos principales para obtener
entradas interactivas con el usuario. Generalmente, las opciones del cuadro de diálogo se
eligen desde un menú y contiene diversos elementos llamados controles. Entre los controles
del cuadro de diálogo podemos citar los recuadros de listas, barras de desplazamiento y los
campos de entrada de datos.
4. Presionar el botón Options para especificar el nombre y tipo del cuadro diálogo, y para
establecer otras opciones del nuevo cuadro de diálogo, incluyendo la plantilla del
diálogo.
5. Seleccionar OK en el diálogo Options para aceptar los valores de las opciones.
Seleccionar OK de nuevo en el diálogo New Resource para crear el nuevo diálogo.
Una vez hecho esto entraremos en el editor de diálogo, donde se puede personalizar el
cuadro de diálogo.
HINSTANCE hInst;
HWND hwndParent;
DLGPROC dlgProc;
DlgProc = (DLGPROC) MakeProcInstance (ResModeDlgProc, Hinst);
DialogBox (hInst, MAKEINTROSOURCE(IDD_DIALOGO), hwndParent,
dlgProc);
FreeProcInstance( (FARPROC) dlgProc);
Donde:
ResModeDlgProc : La función de diálogo que maneja los mensajes que llegan de
los controles.
HInst : El manejador HINSTANCE del módulo Windows (.exe o .dll) que controla
el recurso del diálogo.
IDD_DIALOGO : se reemplazará por el identificador del recurso del diálogo.
HwndParent : La ventana padre del diálogo.
Creación de un menú
El editor de menús facilita la creación y edición de menús. El trabajo con menús implica
cuatro pasos básicos:
1. Crear el menú o editar uno existente. El editor de menús se abre
automáticamente.
2. Hacer cambios en el menú.
3. Probar el menú.
4. Guardar el menú.
El editor de menús proporciona diferentes vistas del menú que se está editando:
• La ventana de edición muestra la estructura del menú que se está editando.
También tiene un modo de prueba donde se puede ver el menú tal como aparecerá en
la aplicación.
• El Inspector de Propiedades es el lugar donde se personaliza el elemento
seleccionado actualmente en el menú
Para crear un nuevo menú :
1. Abrir el proyecto de recursos al que se quiera añadir el menú.
2. Elegir Resource|New para crear un nuevo recurso para el proyecto. El Taller
de Recursos muestra el cuadro de diálogo New Resource.
3. En la lista Resource Type, seleccionar MENU (o MENUEX para proyectos
de 32 bits).
4. Hacer click en Options para especificar un tipo de menú distinto al tipo por
defecto.
5. Hacer click en OK para situar el nuevo menú en el proyecto de recursos
actualmente abierto.
Un elemento de menú que se acaba de añadir, tiene el nombre genérico “Item”. Para que el
elemento sea útil, es necesario editarlo. Se puede editar el texto directamente en el menú, o
con el inspector de propiedades. Con el inspector de propiedades, también se puede cambiar
el identificador del menú y poner una marca de verificación junto al menú si va a ser un
interruptor.
HMENU hMenu;
HMenu = LoadMenu(hInst, MAKEINTRESOURCE (IDENTIF_MENU1);
LoadAccelerators (hInst, MAKEINTRESOURCE (IDENTIF_ACELER1);
Identificadores
Windows requiere que cada tipo de recurso y recurso definido por el usuario estén asociados
con un nombre o número entero único (llamado identificador de recurso). Por defecto el
taller de recursos asigna un nombre a cada nuevo recurso.
Un identificador está formado por dos partes: un literal de texto (nombre del identificador) y
un valor (normalmente un entero). Los identificadores deben ser únicos dentro de un tipo de
recurso. Sólo son significativos los primeros 31 caracteres.
Cuando se crea un nuevo proyecto, lo primero que se debería hacer es especificar un fichero
en el que almacenar los identificadores. Se deben almacenar los identificadores en uno o más
ficheros cabecera de recursos (.RH) que usen #defines para asignar valores a nombres de
identificadores.
Añadir identificadores
Se puede añadir un identificador al fichero de identificadores antes de crear el recurso con el
que estará asociado. Para añadir un identificador,
1. Elegir Resource|Identifiers para visualizar el cuadro de diálogo Identifiers.
2. Hacer click con el botón derecho del ratón y elegir Create Identifier. Aparece
el cuadro de diálogo New Change Identifiers.
3. En el cuadro Name, introducir el nombre del identificador.
4. En el cuadro Value, introducir el valor del identificador.
5. En el cuadro File, introducir el nombre del fichero en el que se va a
almacenar el identificador.
6. Presionar OK.
Área de visualización
Color de fondo
Frecuentemente, según el tipo de aplicación que deseemos crear deberemos modificar el
color de fondo de la ventana. La paleta de colores de fondo o background, corresponde a la
misma que se ha descrito en los temas anteriores. Hay que tener en cuenta, que la paleta
dependerá de la configuración del monitor, ya que si está esta configurado a 256 colores, no
existirá diferencia entre los colores brillantes y los oscuros.
Antes de modificar el color de fondo de la ventana, debemos tener en mente como almacena
la información de pantalla los dispositivos gráficos, normalmente la intensidad de un pixel
corresponde a la suma de diferentes buffers, cada uno de ellos almacena información
referente al pixel en cuestión, OpenGL utiliza estos buffers para determinar el color y alguna
información adicional de cada pixel.
Los buffers que utiliza son:
Buffer Referencia
Para modificar el color de fondo de la ventana podemos utilizar los siguientes comandos
OpenGL:
glClearDepth( 1.0 );
Esta función define el color de fondo que se aplicará, utiliza los valores de RGB, por lo tanto
cada argumento corresponde a la intensidad del color, Rojo, Verde y Azul, el rango de estos
valores debe ir comprendido entre el valor 0.0, menor intensidad, 1.0 máxima intensidad. El
cuarto parámetro que utiliza la función glClearColor, corresponde al valor alpha del color
que se utiliza para determinar información referente a la transparencia del color, de igual
forma que los tres argumentos anteriores, esté también toma valores comprendidos entre 0.0
opaco, y 1.0 totalmente transparente. La función glClearDepth, se utiliza par definir
especificar información sobre la profundidad.
Una vez que sé a definido el color de fondo debemos y se ha definido la profundidad
podemos utilizar la función glClear para borrara la pantalla con el color que habíamos
definido, el argumento de esta función podrá ser simple o compuesto, ya que podremos
combinar la información de varios buffers tal como:
glClear( GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
En este caso el color de fondo depende de la suma de los valores de los buffers de color y de
profundidad. Como vemos esta función nos permite borrar el contenido de todos los buffers,
ahora bien es muy frecuente que solo se desee borrar alguno de estos por lo tanto deberemos
utilizar instrucciones tales como:
Existe una posibilidad de definir color mediante la instrucción glClearIndex, esta función
define el color según un entero. Este entero debe estar comprendido entre 1 y el 20. Para más
información ver tema gestión de ventanas donde esta detallada cada color con su índice.
Primitivas de dibujo
En esta sección se describirán los diferentes elementos necesarios para crear una imagen
sintética. Antes de definir los diferentes elementos que podemos generar especificamos el
formato que sigue OpenGL:
glBegin(MODO);
glVertex(x,y,z);
glVertex(x,y,z);
...
glEnd();
Donde MODO es el identificador del tipo que deseamos dibujar en la pantalla y glVertex es
la descripxión de un punto de la pantalla.
Como veremos a continuación los objetos que definimos los podemos describir como un
conjunto de líneas, este mecanimso es el que utiliza para realizar objetos OpenGL, el
problema surge cuando se desea generar objetos curvos, en este caso debemos generar
rutinas para generarlos.
OpenGL, tiene definidos un conjunto de objetos curvos en las librerias AUX, por lo tanto
incorporando estos elementos podemos reducir el coste de creación de los objetos.
Para definir el color de los objetos se empleará la instrucción glIndexi(), descrita en el
apartado anterior.
Puntos
Un punto se define mediante la función glVertex, esta función especifica las coordenadas del
punto dentro de la ventana de visualización. Con esta función podremos definir puntos en
dos y tres dimensiones, dependiendo del número de coordenadas que se detallan. OpenGL
trabaja normalmente en coordenadas homogéneas representadas por cuatro componentes, (x,
y, z, h), por lo tanto cuando estamos definiendo puntos en dos dimensiones el valor z coge el
valor cero y h el valor 1, en tres dimensiones h coge el valor 1.
El tipo especificado de coordenadas viene determinado a partir según los sufijos que siguen a
la función glvertex. Los sufijos que pueden seguir a la función serán, d (double), indica que
las coordenadas deben especificarse en valores double, f (float), i (integer) y finalmente s
(short), por lo tanto las coordenadas deberán indicarse con valores que correspondan al
sufijo. Existe la posibilidad de definir un punto mediante un vector que contenga las
coordenadas, para ello deberemos utilizar el sufijo v indicando que es un vector de
coordenadas.
Para definir un punto o conjuntos de puntos debemos especificar las siguientes cláusulas
glBegin(GL_POINTS) y la cláusula glEnd(), detallando entre ambas las posiciones de cada
punto, así tenemos por ejemplo:
glBegin(GL_POINTS)
glVertex2f(50.4,34.6); //Punto 2D,en punto flotante
glVertex3i(10,20 34); //Punto 3D, en enteros
glEnd();
Por lo tanto para definir un conjunto de punto sobre la ventana de visualización podemos
emplear la estructura anterior.
Una vez definido los puntos podemos modificar su tamaño empleando la sentencia
glPointSize(valor), donde valor representa el tamaño con el que deseamos dibujar el punto.
Líneas
De la misma forma que definimos puntos podemos definir líneas. Para definir una línea se
precisan dos puntos, estos dos puntos se definen de la misma forma que al definir un punto
de la pantalla, es decir con la función glVertex.
Existen diversas formas de definir líneas dependiendo del modo en que se describa en la
cláusula glBegin(modo), modo representará el tipo de línea que deseamos.
Modo Descripción
Genera una serie de líneas que no se conectan entre sí. Las líneas se
GL_LINES definen mediante los pares de puntos sucesivos, por lo tanto el número de
vértices debe ser par, en el caso de que fuera impar se ignoraría
Genera una serie de líneas pero que se conectan entre sí, es decir el punto
GL_LINE_STRIP final de una línea es el punto inicial de la siguiente. Con este modo se
pueden generar figuras cerradas si el punto inicial coincide con el final.
Genera una serie de líneas conectadas entre sí, es parecido al modo
GL_LINE_LOOP anterior pero este modo conecta automáticamente el punto inicial con el
punto final.
glBegin (MODO);
glVertex2f(0.0, 0.0);
glVertex2f(1.0, 0.0);
glVertex2f(1.0, 1.0);
glVertex2f(0.0, 1.0);
glEnd;
El aspecto de las líneas también pueden modificarse, pudiendo crear líneas más gruesas y
con formato punteado, para ello se utilizará los comandos:
glEnable(GL_LINE_STIPPLE);
glLineStipple( factor, mascara);
glDisable(GL_LINE_STIPPLE);
Con estos comandos podemos conseguir líneas punteadas, la primera instrucción activa el
modo de línea punteada, mientras que el segundo define el estilo de la línea, donde factor es
un valor que esta comprendido entre 1 y 255, este valor define la separación entre los trozos
de la línea, mientras que máscara, es un valor de 16 bits que se describe en hexadecimal,
cada máscara define un formato de línea, los valores van comprendidos entre 0x0000 hasta
0xAAAA.
Otra posibilidad que tenemos es modificar el grosor de la línea, para ello utilizaremos la
siguiente instrucción:
glLineWidth(tamaño);
Por defecto OpenGL define las líneas con tamaño 1.0, pero podemos modificarlo mediante la
sentencia anterior. Hay que tener en cuenta que una vez activado el tamaño hay que volver a
establecer si deseamos líneas con el tamaño por defecto.
Poligonos
OpenGL define los polígonos como secuencia de aristas, por lo tanto sigue con el mismo
formato especificado en los puntos anteriores.
Para generar polígonos tenemos los siguientes modos para la sentencia glBegin(modo),
estos son:
Modo Descripción
Genera un simple poligono relleno con los vertices especificados.
Para generar un poligono debemos tener en cuenta que se debe
garantizar tres cosas:
Gráficamente podemos ver las diferencias entre los modos para genera polígonos:
v2
v0 v1 v1
v1
v4 v3
v5 v2 v5 v0
vo
v3 v2
v4
GL_TRIANGLE_STRIP
GL_TRIANGLES
GL_POLYGON
v1
v1 v1
v0 v0
v2 v2 v2
v0 v3 v4 v3
v7
v3 v5
v4
v5
v6 GL_QUAD_STRIP
v4 GL_QUADS
GL_TRIANGLE_FAN
Transformaciones de visualización
Una vez que tenemos definida la ventana donde realizaremos la escena debemos definir
como va a ser la visualización de esta escena, es decir, deberemos definir aspectos tales
como, posición de la cámara, a que punto mirara la cámara, que tipo de proyección
utilizará,... etc.
En este apartado se mostrará como podemos manipular la cámara virtual, es decir el punto
desde donde vemos la imagen, junto con las transformaciones de visualización. Estos dos
puntos nos permitirán definir una visión de los objetos más realista. Otro de los apartados de
esta sección será manipular las transformaciones de proyección, y poder ver los efectos sobre
la imagen.
Matriz de visualización
Para poder manipular los objetos de la escena debemos tener definida una matriz de
visualización, esta matriz será de cuatro por cuatro, es decir coordenadas normalizadas. Esta
matriz se utiliza tanto en dos como en tres dimensiones, la diferencia esta en la coordenada z,
en el caso de dos dimensiones z siempre tomará el valor cero, mientras que en tres
dimensiones podrá tomar diferentes valores.
glMatrixMode(GL_MODELVIEW);
Una vez definido el modo de la matriz podemos asignarle un valor con las siguientes
funciones:
glLoadIdentity()
Esta sentencia nos permite inicializar la matriz con un valor dado, especificado por el valor
de M. M puede ser definido como una matriz de cuatro por cuatro o bien por un vector de
dieciséis elementos, (m1, m2, m3,...). Hay que tener en cuenta que los valores de la matriz se
especifica por columnas, es decir la primera columna corresponde a los valores del vector
m1, m2, m3, m4, la segunda columna corresponderá a los valores m5, m6, m7, m8, y así
sucesivamente.
Como vemos estas dos sentencias nos permiten inicializar la matriz de transformaciones,
para poder realizar operaciones precisamos la siguiente instrucción:
glMultMatrix(M)
Esta sentencia nos permitirá multiplicar matrices, teniendo en cuenta que multiplicará la
matriz definida con una de las sentencias anteriores por la matriz M que definimos en este
punto.
Vistas
OpenGl permite utilizar diferentes expresiones para definir la posición de la cámara y hacía
donde mira dicha cámara.
En OpenGl la representación de los ejes e coordenadas se describe en el siguiente gráfico.
Existe la posibilidad de manipular estos ejes cambiando la posición relativa de cada uno de
ellos, para ello OpenGl utiliza una serie de sentencias que nos definen como vamos a ver los
ejes de coordenadas y en consecuencia como se visualizará el objeto tridimensional.
gluLookAt()
Esta sentencia permite definir de forma especifica donde se situará la cámara, hacía donde
mirara está y cual va a ser el orden de los ejes de coordenadas.
gluLookAt(x0,y0,z0,xc,yc,zc,Vx,Vy,Vz);
Esta sentencia tiene nueve argumentos que describen tres puntos, los valores de x0, y0, z0,
representa el punto hacia donde mira la cámara virtual, este punto normalmente se identifica
en el origen de coordenadas (0,0,0), ahora bien podemos definir nosotros el punto que más
propicio sea para nuestra escena.
Los siguientes tres argumentos representan el punto donde se situará la cámara de
visualización, estas coordenadas no deben coincidir con el punto al cual miramos.
Las últimas tres coordenadas representa el vector de vista hacía arriba, es decir indica cual
será el vector cuya dirección será hacia arriba, “apuntará hacia la parte superior del monitor”.
Para entender este caso podemos ver una serie de ejemplos donde se especifica cual es el
vector y como que da definido los ejes de coordenadas:
y
y
x
z z
Como podemos ver según sea el valor del vector de vista hacia arriba el objeto que
visualizaremos tendrá un aspecto u otro. Normalmente para simplificar la visualización el
vector de vista hacia arriba se hace coincidir con uno de los ejes de coordenadas, como es el
caso del primer gráfico.
glOrtho()
Se utiliza para especificar una proyección ortográfica. Este tipo de proyección define un
volumen de vista rectangular, concretamente define un paralelepípedo de tamaño infinito,
este hecho nos lleva a definir una serie de planos de corte para detallar con exactitud el
volumen de vista.
OpenGl define la sentencia como:
Estos seis argumentos definen la ventana de visualización y los planos de corte tanto cercano
como lejano.
Para definir la ventana de visualización es suficiente definir las coordenadas de dos esquinas
de la ventana, con estos dos valores queda totalmente definida.
Los valores de pcerca y plejos representan el plano cercano y el plano lejano. Hay que tener
en cuenta que el objeto a visualizar debe encontrarse dentro de ambos planos, si sobrepasan
estos dos planos el objeto se recortará automáticamente.
pcerca
Plano de corte cercano (xwmax, ywmax)
(xwmin, ywmin)
El objeto se visualizará entre los dos planos de recorte, en el caso que sobrepase estos planos
se recortará, y si el objeto es tan grande que la ventana de visualización esta dentro de él, no
se visualizará nada quedando la pantalla en negro.
Hay que tener en cuenta que si el plano de corte es negativo esté se encontrará detrás de la
ventana de visualización.
glFrustum()
Este comando se utiliza para definir una proyección perspectiva, se define de forma similar
a la proyección ortográfica pero con la diferencia que la proyección perspectiva define como
volumen de vista una pirámide, en consecuencia el objeto a visualizar tiene un aspecto
mucho más realista.
La sentencia que utiliza OpenGl es:
Como vemos esta sentencia se define de forma similar a la utilizada para definir
proyecciones paralelas, de igual forma que anteriormente definimos planos de corte para
limitar el volumen de vista, que en este caso al ser una proyección perspectiva definirá un
tronco piramidal.
plejos
Plano de corte lejano
(xwmax, ywmax)
Punto de vista
Como vemos ambas sentencias se define de forma similar pero determina vistas muy
diferentes entre sí.
gluPerpespective()
Esta sentencia es una alternativa a la función glFrustum, la diferencia entre ambas está en la
forma de definir la ventana de visualización. Si en la sentencia glFrustum definimos los dos
vértices necesarios de la ventana, en la sentencia glPerpestive solamente definiremos el
ángulo de apertura de la cámara y la relación entre el largo y ancho del plano cercano de
corte.
Apertura corresponde al ángulo de apertura de la cámara virtual, este ángulo puede tomar
valores comprendidos entre 0º y 180º. El valor de aspect, vendrá dado por la relación entre el
alto y ancho del plano de corte, por lo tanto aspect toma el valor de alto plano dividido entre
largo plano.
Los valores de pcerca y plejos corresponden a los plano de corte cercano y lejano, de forma
similar que en los casos anteriores.
plejos
Plano de corte lejano
Punto de vista
Aspectos avanzados
Listas
Una lista de OpenGL es un grupo de comandos de OpenGL que han sido guardados para una
posterior ejecución. Cuando una lista es invocada, los comandos en ella son ejecutados en el
orden el los cuales fueron introducidos en ella.
Utilizar listas tiene dos ventajas. La primera es la eficiencia y la segunda la claridad.
Imaginemos que tenemos que dibujar tres circulos en pantalla, y el codigo que tenemos es el
siguiente :
void DrawCircle(void)
{
GLint i;
GLfloat cosine, sine;
glBegin(GL_POLYGON);
for(i=0;i<100;i++)
{
cosine = cos(i*2*PI/100.0);
sine = sin(i*2*PI/100.0);
glVertex2f(cosine,sine);
}
glEnd();
}
void DrawScene(void)
{
glPushMatrix();
DrawCircle();
glTranslatef(2.0,0.0,0.0);
DrawCircle();
glTranslatef(2.0,0.0,0.0);
DrawCircle();
glPopMatrix();
}
En este caso, para dibujar los tres circulos, hemos tenido que calcular 300 veces los valores
de un seno y un coseno. Hacer este tipo de calculos es muy costoso. Si utilizasemos listas,
entonces solo necesitariamos calcular una vez los puntos del circulo, con lo que sólo
calculariamos 100 senos y cosenos, y despues llamariamos a la lista tres veces, una vez por
cada dibujo.
Por ejemplo, podriamos crear la lista cuando se inicializa la aplicación, con una funcion
como la siguiente :
define LISTA_CIRCULO 0
void CreateLists(void)
{
GLint i;
GLfloat cosine, sine;
glNewList(LISTA_CIRCULO,GL_COMPILE);
glBegin(GL_POLYGON);
for(i=0;i<100;i++)
{
cosine = cos(i*2*PI/100.0);
sine = sin(i*2*PI/100.0);
glVertex2f(cosine,sine);
}
glEnd();
glEndList();
}
void DrawScene(void)
{
glPushMatrix();
glCallList(LISTA_CIRCULO);
glTranslatef(2.0,0.0,0.0);
glCallList(LISTA_CIRCULO);
glTranslatef(2.0,0.0,0.0);
glCallList(LISTA_CIRCULO);
glPopMatrix();
}
Hay que tener en cuenta que se pueden crear listas jerarquicas. Asi por ejemplo, la función
DrawScene anterior se podria ejecutar con un solo glCallList, si antes hubiesemos definido
una lista del tipo siguiente :
define LISTA_ESCENA 99
void DefinirListaEscena(void)
{
glNewList(LISTA_ESCENA,GL_EXECUTE);
glPushMatrix();
glCallList(LISTA_CIRCULO);
glTranslatef(2.0,0.0,0.0);
glCallList(LISTA_CIRCULO);
glTranslatef(2.0,0.0,0.0);
glCallList(LISTA_CIRCULO);
glPopMatrix();
glEndList();
}
Cuando se crea una lista, OpenGL se encarga de guardar cuales son las operaciones que
realizamos y posteriormente se pueden ejecutar con un coste en calculo mucho menor. Esta
mejora en el tiempo de ejecución depende mucho de la implementación de OpenGL que
estemos utilizando.
Tambien tienen sus desventajas. Normalmente requieren mucho espacio en memoria. Por
ejemplo, para la lista del circulo, se necesita espacio al menos para 200 puntos en coma
flotante. Ademas, las listas una vez hechas no se pueden cambiar, solo se pueden ejecutar.
Las listas pueden guardar las siguientes operaciones :
• Operaciones con matrices
• Rasterización de bitmaps y imagenes
• Luces, propiedades de materiales y modelos de luces.
• Texturas
• Primitivas de dibujo : poligonos, mallas de triangulos, lineas,...
Crea o reemplaza una lista. list es el identificador de la lista. mode puede valer
GL_COMPILE. En este caso, todos los comandos que se encuentren en la lista se guardan el
la lista, pero no se ejecutan. En caso de valer GL_COMPILE_AND_EXECUTE se guardan
en la lista y se ejecutan.
Iluminación
Con OpenGL se pueden crear muchos efectos de luz. Se pueden crear luces puntuales,
direccionales y focos.
Una luz puntual es como si fuese una bombilla. La luz surge de un punto, y se dispersa en
todas direcciones. La intensidad de la luz en un punto del espacio depende de la distancia de
este punto al origen de la luz.
Una luz direccional es una luz parecida a la luz del sol. La luz se dirige en una posición, y no
parece que venga de un punto en concreto.
Nosotros veremos como crear luces de estos dos tipos, y dejaremos fuera de aqui el como
crear luces de foco.
Para especificar la iluminación de una escena hay que decidir como seran las luces, y cuales
seran los materiales de los objetos de las escenas. Por defecto la iluminación esta
deshabilitada. Lo primero que hay que hacer si se desea utilizar luces es habilitarlas. Esto se
hace con el siguiente codigo :
glEnable(GL_LIGHTING);
Despues hay que decidir cuantas luces se van a insertar en la escena. Cada implementación
de OpenGL tiene un número maximo de luces que se pueden manejar. Este numero viene
dado por la constante GL_MAX_LIGHTS, definida en el archivo gl.h. Para habilitar cada
una de las luces se ejecuta lo siguiente :
glEnable(GL_LIGHTi);
Una vez habilitadas las luces, hay que indicar los parametros de estas. Para hacer esto
utilizaremos las siguientes funciones : glLightf, glLighti, glLightfv, glLightiv. A
continuación veremos algunos de los parametros mas importantes de las luces, y como
modificarlos :
• Color de la luz
Los colores de las luces en OpenGL tienen tres componentes :
• Ambiente
Esta componente afecta a todos los objetos de la, independientemente de la
posición o orientación de estos. Viene a emular la luz que en el mundo real
viene dada por la reflexión difusa en las paredes, la luz solar indirecta, etc.
• Difusa
Se puede pensar en ella como en el verdadero color de la luz. Su influencia
sobre una superficie de un objeto depende de su orientación y su distancia.
• Especular
Influye en el brillo que va a tener el objeto.
Los materiales de los objetos tienen las mismas componentes que la los colores de la luz.
Vienen a indicar la reflectancia del material a cada una de las componentes de la luz. Para
indicar el material de los objetos que se van a renderizar a continuación se debe utilizar la
siguiente función :
Donde :
• face indica a que caras se van a modificar. Puede valer GL_FRONT,
GL_BACK o GL_FRONT_AND_BACK, pero casi siempre trabajaremos con
GL_FRONT.
• pname indica que componente se va a modificar. Puede valer GL_AMBIENT,
GL_DIFFUSE o GL_SPECULAR.
• params es un vector de cuatro componentes que indica el nuevo valor del color.
(params [0], params [1], params [2]) da el vector RGB del color. params [3]
debe valer 1.0.
A continuación vamos a ver un ejemplo, donde se tiene una bola roja con una luz direccional
:
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>
void Init(void)
// Inicializacion de los parametros de OpenGL
{
GLfloat mat_difuso[] = {1.0,0.0,0.0,1.0};
GLfloat posicion_luz[] = {1.0,1.0,1.0,0.0}; // Luz direccional!
// Provar despues con una luz puntual del tipo siguiente.
// Se nota un poco que la luz se va degradando cuanto mayor
// es la distancia entre la luz y el objeto
// GLfloat posicion_luz[] = {5.0,5.0,5.0,1.0};
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glDepthFunc(GL_LEQUAL);
glEnable(GL_DEPTH_TEST);
glMaterialfv(GL_FRONT,GL_DIFFUSE ,mat_difuso);
glLightfv(GL_LIGHT0,GL_POSITION,posicion_luz);
}
{
auxSolidSphere(1.0);
}
return 0;
}
Texturas
Poner texturas a un objeto es como poner papel pintado en una pared. Cuando se esta
dibujando un polígono, es posible indicar que se dibuje este poligono con una imagen,
indicandole para cada vertice del polígono, que posición de la imagen le corresponde. Es
posible mapear texturas en una, dos y tres dimensiones. Aqui sólo trataremos las texturas
2D.
Para dibujar un objeto con texturas hay que seguir los siguientes pasos :
glEnable(GL_TEXTURE_2D);
void glTexImage2D( GLenum target, GLint level, GLint components, GLsizei width,
GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels );
Donde :
• target debe valer GL_TEXTURE_2D
• level indica el nivel de detalle de la textura. Esto no se explica aqui, y
habitualmente tiene un valor 0.
• components indica el nº de componentes del color. Usualmente se usan
componentes RGB, y especificaremos 3. Pero también se pueden hacer texturas
semitransparentes, con lo que se utiliza un formato RGBA (4 componentes). En
ese caso indicariamos un valor de 4.
• width indica el ancho de la imagen de la textura. Debe ser una potencia de 2.
• height indica el alto de la imagen de la textura. Debe ser una potencia de 2.
• border indica si se utiliza un borde en la textura (1) o no (0). Usualmente es
0.
• format indica el formato del valor de cada pixel. Normalmente se utiliza
GL_RGB.
• type indica el tipo de datos usado para cada componente del valor de un
pixel. Puede ser uno de los siguientes valores: GL_UNSIGNED_BYTE,
GL_BYTE, GL_BITMAP, GL_UNSIGNED_SHORT, GL_SHORT,
GL_UNSIGNED_INT, GL_INT o GL_FLOAT.
• pixels es un puntero al mapa de valores de los pixels. Es la imagen en si.
La imagen se puede obtener de dos formas. Una, generandola con codigo del propio
programa. Esto es facil si la textura es sencilla, como puede ser un tablero de ajedrez. Si la
imagen es mas complicada, hay que cargarla de un archivo. En el ejemplo que damos aqui,
se muestra una función para cargar una textura a partir de un archivo BMP de Windows.
• Mapear la textura
Cuando se esta dibujando el objeto, hay que indicar, para cada vertice de este, que posición
de la textura le corresponde. Esto se hace mediante la siguiente función :
Lo que se hace es indicar la coordenada de la textura antes de indicar el vertice del polígono.
A continuación vamos a ver dos funciones, donde se dibujan un cuadrado y un triangulo,
indicando las posiciones de la textura :
void Cuadrado(void)
{
glBegin(GL_QUADS);
glTexCoord2f(0.0,1.0);glVertex3f(-1.0,1.0,0.0);
glTexCoord2f(1.0,1.0);glVertex3f(1.0,1.0,0.0);
glTexCoord2f(1.0,0.0);glVertex3f(1.0,-1.0,0.0);
glTexCoord2f(0.0,0.0);glVertex3f(-1.0,-1.0,0.0);
glEnd();
}
void Triangulo(void)
{
glBegin(GL_QUADS);
glTexCoord2f(0.0,1.0);glVertex3f(-1.0,1.0,0.0);
glTexCoord2f(1.0,0.0);glVertex3f(1.0,-1.0,0.0);
glTexCoord2f(0.0,0.0);glVertex3f(-1.0,-1.0,0.0);
glEnd();
}
glBegin(GL_QUADS);
glTexCoord2f(0.0,20.0);glVertex3f(-1.0,1.0,0.0);
glTexCoord2f(20.0,20.0);glVertex3f(1.0,1.0,0.0);
glTexCoord2f(20.0,0.0);glVertex3f(1.0,-1.0,0.0);
glTexCoord2f(0.0,0.0);glVertex3f(-1.0,-1.0,0.0);
glEnd();
Para indicar si se quiere repetir el borde de la textura, o se quiere repetir la textura completa
se utiliza la siguiente función :
Donde :
• target debe valer GL_TEXTURE_2D.
• pname puede valer GL_TEXTURE_WRAP_S o GL_TEXTURE_WRAP_T,
donde el primero indica las coordenadas X de la textura, y el segundo las
coordenadas Y.
• param indica si queremos que se repita el borde de la textura (GL_CLAMP) o
si queremos que se repita la textura completa (GL_REPEAT).
Otro de los parametros a tener en cuenta es el filtrado de las texturas. Cuando la camara esta
muy cerca de un objeto con texturas, debido al efecto del mapeado se pueden notar con
mucha claridad la diferencia entre los pixels contiguos de la textura, que se ven como unos
cuadrados mas grandes cuanto mas cerca se esta del objeto.
Un efecto desagradable aparece tambien cuando se esta lejos de las texturas. Si se tiene
objeto lejano con una textura del tipo de un tablero de ajedrez, debido a que solo se dibujan
algunos de los pixels de la textura, pueden aparecer formas muy extrañas en la textura.
Si se quiere evitar de forma parcial este efecto, existe la posibilidad de filtrar las texturas.
Esto se hace con la siguiente llamada :
Donde :
• target debe valer GL_TEXTURE_2D.
• pname puede valer GL_TEXTURE_MIN_FILTER o
GL_TEXTURE_MAG_FILTER, segun se este especificando un filtro para
cuando la textura este lejos o cerca.
• param indica el tipo de filtro a aplicar. Puede valer GL_NEAREST o
GL_LINEAR. El primero indica que no se filtran las texturas. El segundo indica
que se va ha hacer un filtrado lineal de las texturas. Hay que tener en cuenta que
aplicar un filtrado a las texturas es muy costoso en tiempo.
A continuación vamos a ver un ejemplo que dibuja un cuadrado girado 45º horizontalmente.
En el ejemplo se utiliza una clase de C++ que se corresponde con la información de la
textura. Esta clase incluye una función para cargar una textura a partir de un archivo BMP.
Hay que tener en cuenta que solo se pueden cargar archivos de 24 bits de color, y en ancho y
alto de la imagen deben ser potencias de 2.
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>
///////////////////////////////////
// DECLARACION DE LA CLASE CTextura
///////////////////////////////////
#include <stdio.h>
class CTextura
{
public :
// Constructor
CTextura(void);
// Destructor
~CTextura();
// Tamanyo de la textura
int TamX,TamY;
};
///////////////////////////////////
// IMPLEMENTACION DE LA CLASE CTextura
///////////////////////////////////
CTextura :: CTextura(void)
{
Imagen = NULL;
}
// is it a BMP?
if ( !strcmp(suffix, ".BMP") )
{
FILE* file = fopen(Textura, "rb");
if ( file != NULL )
{
BITMAPFILEHEADER fileheader;
BITMAPINFOHEADER infoheader;
if ( fread(&fileheader, sizeof(BITMAPFILEHEADER), 1,
file) == 1
&& (char)fileheader.bfType == 'B'
&& *(((char*)&fileheader.bfType) + 1) == 'M'
&& fread(&infoheader, sizeof(BITMAPINFOHEADER),
1, file) == 1
&& infoheader.biBitCount == 24
&& infoheader.biCompression == BI_RGB
)
{
rewind( file );
long offset = fileheader.bfOffBits;
fseek(file, offset, SEEK_SET);
TamX = infoheader.biWidth;
TamY = infoheader.biHeight;
//image.setValue(size, 3, imagebuf);
b = TRUE;
}
Imagen = imagebuf;
}
fclose(file);
}
}
}
return b;
}
CTextura :: ~CTextura()
{
if(Imagen)
delete Imagen;
}
/////////////////////////////////////////////////
// Variables globales :
CTextura *Textura;
void Init(void)
// Inicializacion de los parametros de OpenGL
{
glClearColor(0.0,0.0,0.0,0.0);
glDepthFunc(GL_LEQUAL);
glEnable(GL_DEPTH_TEST);
glShadeModel(GL_FLAT);
return 0;
}
Para seleccionar con el raton debemos afinar un poco mas este mecanismo. Lo que tenemos
que hacer es indicar un volumen de visualizacion que sea similar a un rayo que parta del
punto en que se ha pinchado el raton sobre el viewport, con un cierto tamaño que se pueden
considerar como la sensibilidad de la selección, y una cierta profundidad que dependerá del
tamaño de la escena. Afortunadamente, OpenGL también incluye mecanismos para indicar
esta volumen.
A continuación haremos una referencia de las funciones de OpenGL que se utilizan en el
mecanismo de selección, y despues veremos un pequeño ejemplo que muestra como
funcionan.
Indica el modo que se va a utilizar para renderizar. Para el dibujo normal se debe indicar
GL_RENDER. Para el modo de seleccion se debe indicar GL_SELECT. En caso de que se
llame a esta función y estuviésemos en modo de selección, devuelve en numero de objetos
que se han seleccionado mientras se estaba en ese modo.
Indica el buffer donde se van a guardar los identificadores de los objetos que se seleccionen.
El parámetro size indica el tamaño máximo del buffer. Esta función se debe llamar antes de
entrar en el modo de selección. El formato del buffer de seleccion, una vez se ha hecho la
busqueda de las selecciones es el siguiente :
Nº de id. Zmin Zmax Id.1 ... Id.n Nº de id Zmin Zmax Id.1 ...
Selección 1 Selección 2
Cuando se selecciona lo que se hace es trazar un rayo que, si intersecta al objeto, esta esta
seleccionado. Normalmente el rayo intersecta en dos puntos al objeto : uno es el punto donde
el rayo entra en el objeto, y otro es por donde sale (la parte de atras del objeto). Pues Zmax y
Zmin dan un nº entero que da una medida del valor de Z en estos dos puntos. Con esto, si
hay dos objetos que esta seleccionados, podremos saber cual es el que esta mas cerca de
nosotros.
Id. 1, Id.2, ... Id. n indican los identificadores que habia en la pila de nombres para el objeto
seleccionado.
Las funciones que vienen a continuación se utilizan para indicar el identificador del objeto
que se esta renderizando. En realidad no se indica sencillamente el nombre del objeto que se
esta renderizando, sino que los nombres se organizan en forma de pila. Esto es útil cuando se
tiene la escena organizada en forma de árbol. Imaginemos que tenemos un editor, con la
posibilidad de agrupar objetos. Si pinchamos sobre un objeto que forma parte de un grupo,
quizás nos interesa que nos indiquen, no solo el identificador del objeto que nos han
pinchado, sino también el identificador del grupo al que pertenecía el objeto. Esto se
consigue utilizando la pila de nombres (identificadores). Mas adelante se muestra un ejemplo
de como funciona la pila de nombres.
Reemplaza el identificador que esta en la cima de la pila por el dado. La pila debe no estar
vacía.
Ahora vamos a mostrar un sencillo ejemplo que dibuja dos esferas, y muestra un mensaje
cuando se pincha sobre una de las dos esferas. Si se compila para la plataforma windows, no
olvidar indicar que es una aplicación de consola.
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>
#include <stdio.h>
void Init(void)
// Inicializacion de los parametros de OpenGL
{
glClearColor(0.0,0.0,0.0,0.0);
glDepthFunc(GL_LEQUAL);
glEnable(GL_DEPTH_TEST);
glShadeModel(GL_FLAT);
}
glPushMatrix();
if(Identifier == 1)
{
glTranslatef(2.0,0.0,-5.0);
glColor3f(1.0,0.0,0.0);
}
else
{
glTranslatef(-1.0,0.0,-5.0);
glColor3f(0.0,0.0,1.0);
}
glPopMatrix();
gluDeleteQuadric(Sphere);
}
// Dibuja la escena
// RenderType indica si vamos a buscar una seleccion (==GL_SELECT)
// o vamos a dibujar el objeto (==GL_RENDER)
{
DrawSphere(0,RenderType);
DrawSphere(1,RenderType);
}
glMatrixMode(GL_PROJECTION);
glPushMatrix();
// Creamos un volumen de seleccion, centrado en el punto donde
// se pincho el raton (x,y) y con una sensibilidad de 5x5
pixels
glLoadIdentity();
gluPickMatrix( (GLdouble)x, (GLdouble) (ViewportCoordinates[3]
- y),
5.0, 5.0, ViewportCoordinates);
glOrtho(-8.0,8.0,-8.0,8.0,0.0,20.0);
// Redibujamos la escena
DrawScene(GL_SELECT);
glPopMatrix();
glFlush();
IndexBuffer = 0;
SelectedPath.Zmax = 0;
for(int i=0;i<NObjectsSelected; i++)
{
// Obtenemos los valores de la seleccion que vamos a estudiar
CurrentPath.NIdentifiers = SelectionBuffer[IndexBuffer++];
CurrentPath.Zmin = SelectionBuffer[IndexBuffer++];
CurrentPath.Zmax = SelectionBuffer[IndexBuffer++];
for(int j=0;j<CurrentPath.NIdentifiers;j++)
CurrentPath.Identifiers[j] =
SelectionBuffer[IndexBuffer++];
}
else
return 0;
}
Seleccionado = GetSelection(x,y,&SelectedPath);
if(Seleccionado)
{
if(SelectedPath.Identifiers[SelectedPath.NIdentifiers-1] == 0)
printf("Se ha seleccionado la bola azul\n");
else if(SelectedPath.Identifiers[SelectedPath.NIdentifiers-1]
== 1)
printf("Se ha seleccionado la bola roja\n");
}
}
return 0;
}
Vamos a ver ahora cual es la utilidad de tener una pila de nombres. Imaginemos que tenemos
que queremos tener en pantalla dos moleculas : una de oxigeno (O2) y otra de agua (H2O),
y queremos que cuando se pinche el raton, nos indique que atomo y que molecula se ha
seleccionado. Hay varias formas de hacer esto, pero una de las mas comodas es utilizando la
pila de nombres.
Tendremos la escena estructurada en forma de un arbol como este :
Cada uno de los nodos se corresponderia con una funcion que dibujaria cada uno de los
elementos. Obviamente, no todos los nodos son diferentes. Es decir, la función que se llama
para dibujar un atomo de oxigeno es igual para cada uno de los cuatro atomos.
Lo que haremos es en cada función meter en la pila de nombres un identificador que indica
que nodo estamos renderizando : si es un atomo de agua o de oxigeno, o si es un atomo de
oxigeno o hidrogeno. Entonces, cuando se seleccione, obtendremos un ‘camino’ dentro del
arbol, que indica sobre que molecula y que atomo hemos pinchado. En este caso, todos los
caminos tendran una longitud de dos identificadores, ya que todos los caminos dentro del
arbol tiene una longitud dos.
A continuación se puede ver el código correspondiente a este ejemplo. Se basa en el codigo
anterior, modificando las funciones DrawScene y OnLeftMouseDown :
#define AT_OXIGENO 0
#define AT_HIDROGENO 1
#define MOL_OXIGENO 0
#define MOL_AGUA 1
DibujarAtomoOxigeno(RenderType);
glPushMatrix();
glTranslatef(1.5,1.5,0.0);
DibujarAtomoHidrogeno(RenderType);
glTranslatef(-3.0,0.0,0.0);
DibujarAtomoHidrogeno(RenderType);
glPopMatrix();
if(RenderType == GL_SELECT)
glPopName();
}
DibujarAtomoOxigeno(RenderType);
glPushMatrix();
glTranslatef(1.5,0.0,0.0);
DibujarAtomoOxigeno(RenderType);
glPopMatrix();
if(RenderType == GL_SELECT)
glPopName();
}
Seleccionado = GetSelection(x,y,&SelectedPath);
if(Seleccionado)
{
if(SelectedPath.Identifiers[0] == MOL_OXIGENO)
printf("Se ha seleccionado la molecula de oxigeno\n");
else if(SelectedPath.Identifiers[0] == MOL_AGUA)
printf("Se ha seleccionado la modelcula de agua\n");
if(SelectedPath.Identifiers[1] == AT_HIDROGENO)
printf("Se ha seleccionado un atomo de hidrogeno\n");
else if(SelectedPath.Identifiers[1] == AT_OXIGENO)
printf("Se ha seleccionado un atomo de oxigeno\n");
}
}