Está en la página 1de 54

Manual de OpenGL

Juan Manuel Huescar


Juan Carlos Serra
Antonio Fco. Bennasar
Informatica Gráfica II 1999/2000

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

J.M.Huescar J.C.Serra A.F. Bennasar 2


Informatica Gráfica II 1999/2000

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:

int WINAPI WinMain (HINSTANCE hInstance,


HINSTANCE hPrevInstance,
LPSTR lpszCmdLine,
int nCmdShow);

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

J.M.Huescar J.C.Serra A.F. Bennasar 3


Informatica Gráfica II 1999/2000

decir nos permite iniciar nuestra aplicación Windows introduciendo un


comando en la línea de comandos tradicional.
• nCmdShow: Indica a la aplicación como debe mostrar la ventana inicial, es
decir podremos iniciar la aplicación mostrando una ventana normal, o bien
maximizada, minimizada (forma de icono).
En este punto debemos describir que se entiende por un Handle. Los handles se utilizan en
las librerías estándar del lenguaje C para realizar las entradas y salidas de archivos I/O. Así
por ejemplo si abrimos un fichero utilizando la llamada open(), esta retorna un handle, que es
utilizado por el resto de instrucciones, tales como read(). Hay que tener en cuenta que a pesar
de que podamos creer que los handler funcionan como punteros, realmente no lo son, ya que
no podemos manipular directamente el handler para acceder a información del archivo.
Dentro de la función WinMain() se realiza el registro de la clase de ventana de Windows. La
clase de ventana indica a Windows ciertas propiedades importantes acerca del
funcionamiento de la ventana. A partir de una única clase de ventana se puede crear más de
una ventana.
La función WinMain() registra la ventana rellenando la estructura WINDCLASS, que se
define a continuación:

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:

wc.style = CS_NOCLOSE | CS_DBCLIK;

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.

J.M.Huescar J.C.Serra A.F. Bennasar 4


Informatica Gráfica II 1999/2000

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:

J.M.Huescar J.C.Serra A.F. Bennasar 5


Informatica Gráfica II 1999/2000

wc.hIcon = LoadIcon(NULL,“Nombre del Icono”);

El icono deberá encontrarse en el mismo directorio que se encuentra la aplicación, sino


deberemos detallar el camino completo del icono.

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:

wc.hCursor = LoadCursor(NULL,“Nombre cursor”);

El tipo de cursor deberá encontrarse en el mismo directorio que la aplicación, si no es así se


deberá especificar la ruta donde se encuentra el icono.

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.

wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);

Los colores posibles que podemos utilizar para el fondo del área de trabajo de la aplicación
serán:

J.M.Huescar J.C.Serra A.F. Bennasar 6


Informatica Gráfica II 1999/2000

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

J.M.Huescar J.C.Serra A.F. Bennasar 7


Informatica Gráfica II 1999/2000

WNDCLASS. Si esta sentencia da como resultado un cero, indicará que no se pude


continuar, la aplicación se finalizará.
Hasta este momento lo que hemos realizado es el registro de clases de Windows, ahora
debemos crear la ventana de la aplicación.
La creación de la ventana se realiza despues de registrar la clase de ventana, la estructura de
la llamada para crear la ventana es la siguiente:

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.

J.M.Huescar J.C.Serra A.F. Bennasar 8


Informatica Gráfica II 1999/2000

Los siguientes dos argumentos, WIDTH, HEIGHT, especifican las dimensiones de la


ventana. Es posible que asignemos a estos valores CW_USEDEFAULT, de esta forma será
Windows quien especifique las dimensiones.
Los siguientes dos valores son los handler de la ventana padre y del menú, el siguiente el
handler de la aplicación en uso, y finalmente el último se define siempre a un valor NULL.

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:

LONG WINAPI WndProc( HWND hWnd,


UINT msg,
WPARAM wParam,
LPARAM lParam);

En el ejemplo que detallaremos al final de la sección podremos ver como nuestra aplicación
no responde a muchos eventos.

J.M.Huescar J.C.Serra A.F. Bennasar 9


Informatica Gráfica II 1999/2000

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.

El resultado final del código que viene a continuación es:

Barra de menú del Barra de Título Minimizar


sistema Maximizar

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>

#define WIDTH 400


#define HEIGHT 300

HDC ghDC;
HGLRC ghRC;

int WINAPI WinMain (HINSTANCE hInstance,


HINSTANCE hPrevInstance, LPSTR lpszCmdLine,
int nCmdShow);

LONG WINAPI WndProc (HWND hWnd,UINT msg,


WPARAM wParam,LPARAM lParam)

J.M.Huescar J.C.Serra A.F. Bennasar 10


Informatica Gráfica II 1999/2000

HINSTANCE hInst;

// ==================================================
// Nombre de la funcion: winMain
// ==================================================

int WINAPI WinMain (HINSTANCE hInstance,


HINSTANCE hPrevInstance, LPSTR lpszCmdLine,
int nCmdShow)
{
static char szAppName[] = "P1";
static char szTitle[]="Proyecto 1";
WNDCLASS wc;
MSG msg;
HWND hWnd;
wc.style = 0;
wc.lpfnWndProc = (WNDPROC)WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon (hInstance,IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszMenuName = NULL;
wc.lpszClassName = szAppName;
if (!RegisterClass(&wc )) return (FALSE);
hWnd = CreateWindow(
szAppName,
szTitle,
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
WIDTH,
HEIGHT,
NULL,
NULL,
hInstance,
NULL
);

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

J.M.Huescar J.C.Serra A.F. Bennasar 11


Informatica Gráfica II 1999/2000

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

Renderizar OpenGL en una ventana WIN32


A continuación vamos a explicar como crear una ventana en la que renderizar una escena
con OpenGL. Vamos a verlo con un ejemplo :

#include "stdafx.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

HGLRC SetUpOpenGL( HWND hWnd )


// Inicializa el formato de pixel y devuelve el contexto de rendering
{
static PIXELFORMATDESCRIPTOR pfd =
{
sizeof (PIXELFORMATDESCRIPTOR), // tamaño de la estructura
1, // nº version
PFD_DRAW_TO_WINDOW | // Flags de dibujo en la ventana
PFD_SUPPORT_OPENGL |
PFD_DOUBLEBUFFER, // -> Doble buffering! Utilizar SwapBuffers!!
PFD_TYPE_RGBA,
24, // 24 bits de color
0, 0, 0, // RGB bits
0, 0, 0, // No preocuparse de esto
0, 0, // No buffer alpha
0, 0, 0, 0, 0, // No buffer de acumulacion
32, // 32 bits de buffer de profundidad
0, // No buffer de 'stencil'
0, // No buffers auxiliares
PFD_MAIN_PLANE, // Tipo de capas
0, // Reservado
0, // No mascara de capas
0, // No mascara de visibilidad
0 // No 'damage mask'
};

int nMyPixelFormatID;
HDC hDC;
HGLRC hRC;

// Elegimos el formato de pixel


hDC = GetDC( hWnd );
nMyPixelFormatID = ChoosePixelFormat( hDC, &pfd );

// Obtenemos el handler del contexto


SetPixelFormat( hDC, nMyPixelFormatID, &pfd );

// Obtenemos el handler del contexto de rendering


hRC = wglCreateContext( hDC );

J.M.Huescar J.C.Serra A.F. Bennasar 12


Informatica Gráfica II 1999/2000

ReleaseDC( hWnd, hDC );

// Devolvemos el contexto de rendering


return hRC;
}

LONG WINAPI WndProc( HWND hWnd, UINT msg,


WPARAM wParam, LPARAM lParam )
// Funcion que procesa los mensajes de la ventana
{
HDC hDC;
PAINTSTRUCT ps;
GLsizei glnWidth, glnHeight;
static 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);

// Obtenemos el nuevo tamaño de la ventana


// y redefinimos el viewport
glnWidth = (GLsizei) LOWORD (lParam);
glnHeight = (GLsizei) HIWORD (lParam);
glViewport(0,0,glnWidth,glnHeight);

// Redefinimos la proyeccion
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(60.0, (GLfloat)glnWidth / (GLfloat)glnHeight,
1.0, 200.0);
glMatrixMode(GL_MODELVIEW);

wglMakeCurrent( hDC, NULL );


ReleaseDC( hWnd, hDC );
return 0;

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

EndPaint( hWnd, &ps );


return 0;

case WM_DESTROY :
wglDeleteContext(hRC);
PostQuitMessage(0);

J.M.Huescar J.C.Serra A.F. Bennasar 13


Informatica Gráfica II 1999/2000

return 0;
}
// Los demas mensajes no los procesamos
return DefWindowProc( hWnd, msg, wParam, lParam );
}

int APIENTRY WinMain(HINSTANCE hInstance,


HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
// Funcion de entrada a la aplicacion
{
static char szAppName[] = "OpenGL";
static char Titulo[]="Una ventana OpenGL";
WNDCLASS wc;
MSG msg;
HWND hWnd;
int i;

// Definimos la clase de ventana


wc.style = CS_HREDRAW | CS_VREDRAW ; // Estilo de ventana.
wc.lpfnWndProc = (WNDPROC)WndProc; // Procedimiento de proceso de
// mensajes

wc.cbClsExtra = 0; // No hay datos extra por clase


wc.cbWndExtra = 0; // No hay datos extra por ventana
wc.hInstance = hInstance; // Instancia propietaria de la ventana
wc.hIcon = NULL; // Icono de la aplicacion
wc.hCursor = LoadCursor(NULL, IDC_ARROW); // Cursor

wc.hbrBackground = NULL; // Con esto indicamos que el fondo de la


// ventana no debe redibujarse
// Esto es IMPORTANTE, porque si no,
// la ventana parpadea cuando se redibuja

wc.lpszMenuName = NULL; // Menu de la ventana


wc.lpszClassName = szAppName; // Nombre de registro de la ventana

// Registramos la clase
RegisterClass( &wc );

// Creamos las ventanas de vista


hWnd = CreateWindow( szAppName, // Nombre de la aplicacion
Titulo, // Titulo de la ventana
WS_OVERLAPPEDWINDOW // Estilo de la ventana
| WS_CLIPCHILDREN
| WS_CLIPSIBLINGS,
CW_USEDEFAULT, CW_USEDEFAULT, // Posicion
400, 400, // Tamaño
NULL, // Ventana padre
NULL, // Usar el menu de la clase
hInstance, // Instancia propietaria de la ventana
NULL // No datos extra para la ventana
);

// Miramos si se ha podido crear la ventana


if ( !hWnd )
return( 0 );

// Mostramos la ventana
ShowWindow( hWnd, nCmdShow );
UpdateWindow( hWnd );

// BUCLE PRINCIPAL DE LA APLICACION


// Procesamos los mensajes que van llegando a la aplicacion
while(GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}

J.M.Huescar J.C.Serra A.F. Bennasar 14


Informatica Gráfica II 1999/2000

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.

• Cambio de tamaño de la ventana


En caso de que ocurra este evento hay que redefinir el tamaño del viewport y el aspecto de la
proyección.

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

Gestion de eventos del raton con WIN32


Cuando se produce un evento de ratón sobre una ventana, se manda un mensaje a ésta, con
parametros tales como en que posición de la ventana ha ocurrido, si la tecla de Ctrl o Shift
estaban pulsadas, etc. Para que nuestra aplicación gestione uno de estos eventos, unicamente
hay que añadir un nuevo case en el procedimiento de gestión de eventos de la ventana.

J.M.Huescar J.C.Serra A.F. Bennasar 15


Informatica Gráfica II 1999/2000

A continuación tenemos una lista de algunos de los mensajes generados por un evento de
ratón que puede recibir una ventana:

WM_LBUTTONDOWN El botón izquierdo se ha pulsado sobre la ventana.


WM_RBUTTONDOWN El botón derecho se ha pulsado sobre la ventana.
WM_LBUTTONUP El botón izquierdo estaba pulsado y se ha soltado sobre la
ventana.
WM_RBUTTONUP El botón derecho estaba pulsado y se ha soltado sobre la ventana.
WM_LBUTTONDBLCLK Se ha hecho una doble pulsación rapida sobre la ventana con el
botón izquierdo.
WM_RBUTTONDBLCLK Se ha hecho una doble pulsación rapida sobre la ventana con el
botón derecho.

Con todos estos mensajes se mandan unos parametros que son comunes a todos ellos :

int fwKeys = wParam; // flags de teclado


GLint xPos = LOWORD(lParam); // posicion horizontal del cursor
GLint yPos = HIWORD(lParam); // posicion vertical del cursor

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 :

MK_CONTROL Se estaba pulsando la tecla Ctrl. Normalmente se utiliza para indicar


selecciones multiples.
MK_LBUTTON El botón izquierdo del ratón estaba pulsado durante el evento.
MK_MBUTTON El botón de enmedio del raton estaba pulsado durante el evento.
MK_RBUTTON El botón derecho del ratón estaba pulsado durante el evento.
MK_SHIFT La tecla de Shift (Mayusculas) esta pulsada durante el evento.
Normalmente se utiliza para indicar selecciones multiples.

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 :

Coord. X del Viewport = Coord. X de la ventana Windows

J.M.Huescar J.C.Serra A.F. Bennasar 16


Informatica Gráfica II 1999/2000

Coord Y del Viewport = Altura de la ventana - Coord. Y de la ventana Windows

Utilización del taller de recursos de Borland y el


compilador de recursos.
Personalizar una aplicación Windows con nuestros propios menús, iconos, punteros y mapas
de bits resulta una tarea sencilla si utilizamos el Taller de recursos de Borland o BRW
(Borland Resource Workshop). En este capítulo aprenderemos a utilizar este taller para crear
nuestros propios iconos, cursores, menús y cuadros de diálogo. El taller también puede
ayudarnos a manejar mapas de bits, teclas rápidas y cadenas. Los menús, cursores, iconos y
cuadros de diálogo creados por separado en este capítulo, se ensamblarán para dar una
representación de calidad a un programa, en posteriores capítulos.

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

J.M.Huescar J.C.Serra A.F. Bennasar 17


Informatica Gráfica II 1999/2000

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.

Uso del taller de recursos de Borland


Cada editor de recursos es parte del paquete global del Taller de recursos de Borland. Para
entrar en él, lo tendremos que hacer a traves de la opcion :

Creación de un proyecto de recursos:


Para crear un proyecto de recursos,
1. Elegir File|New|Resource Project
2. Seleccionar el tipo de recurso en el que se va a basar el proyecto. Generalmente será un
fichero de guión de recursos (.RC)
3. Cuando se termine, pulsar OK; después volver a pulsar OK para cerrar el cuadro de
diálogo.
El nombre del proyecto se pondrá cuando se guarde.

Creación de un cuadro de dialogo


Para crear un cuadro de diálogo:
1. Elegir File|New|Resource Project para
crear un nuevo proyecto de recursos o File|
Open para cargar un proyecto de recursos
ya existente.
2. Elegir Resource|New. El taller de recursos
muestra el cuadro de diálogo New
Resource.
3. En la lista Resource Type, seleccionar
DIALOG o DIALOGEX. Elegir
DIALOGEX para especificar información
ExStyle en los diálogos cuyo destino sea
Windows’95 o NT.

J.M.Huescar J.C.Serra A.F. Bennasar 18


Informatica Gráfica II 1999/2000

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.

Incluir un menú en un cuadro de diálogo


Como un cuadro de diálogo es en sí una ventana, también puede incluir un menú. Para
incluir un menú en un cuadro de diálogo:
1. Definir el menú como un recurso separado y añadirlo al proyecto
2. Abrir el cuadro de diálogo al que se quiere añadir el menú
3. Hacer click con el botón derecho del ratón en un área vacia del cuadro de diálogo y
elegir Properties para abrir el inspector de propiedades (Property Inspector).
4. Hacer click en la página Window. Introducir el nombre del menú recurso en el campo
Menú.

Cambiar las propiedades de las ventanas


El tipo de ventana, estilo de marco, tamaño, posición y las
fuentes del cuadro de diálogo se eligen en el inspector de
propiedades.
Para abrir el inspector de propiedades, hacer click con el
botón derecho del ratón en un área vacía del cuadro de
diálogo y elegir Properties.
Para cambiar el tipo de ventana para el cuadro de diálogo,
seleccionar la página Window y elegir un tipo de las
opciones de Window Type
El marco se selecciona desde Frame Type en la página
Frame. En esta misma página se eligen también los estilos
del marco del cuadro de diálogo en Frame Attributes. Los
atributos determinan la apariencia del cuadro de diálogo.
Para especificar cómo se visualiza el texto en un cuadro de
diálogo seleccionaremos la página Fonts, y en ella
indicaremos el tipo, tamaño y estilo del texto.
Para fijar el tamaño de un cuadro de diálogo modificaremos
los valores Width y Heigh de la página general. Y para fijar
la posición inicial del cuadro de diálogo modificaremos los valores de Top (superior) y Left
(izquierda) de esta misma página.
Una vez que se ha definido un cuadro de diálogo, se pueden crear y manipular sus controles.
En el editor de diálogos, el menú local Dialog, la paleta Control, y la paleta Tool, facilitan el
trabajo con los controles.

J.M.Huescar J.C.Serra A.F. Bennasar 19


Informatica Gráfica II 1999/2000

Añadir controles a un cuadro de diálogo


Para añadir un nuevo control a un cuadro de diálogo:
1. Hacer click en la página y control deseado en la paleta Control

2. El cursor cambia a forma de cruz cuando se mueve sobre el cuadro diálogo


que se está diseñando.
3. Para situar el control y darle un tamaño al mismo tiempo, hacer click en el
diálogo donde se quiera que esté la esquina superior izquierda del control. Arrastrar
el ratón hacia la esquina superior derecha hasta que el control tenga el tamaño
deseado.
También se puede utilizar el menú Dialog para añadir controles a un cuadro de diálogo.
Una vez que se haya añadido un control a un control de diálogo, se puede modificar
fácilmente haciendo click con el botón derecho del ratón sobre él y eligiendo Properties. Se
visualizará el inspector de propiedades, con opciones que pueden modificar la apariencia y
comportamiento del control.

Programación de un cuadro de diálgo con el API de Windows.


Para programar un cuadro de diálogo con el API de Windows, se debe usar la función
DialogBox.
Por ejemplo, el siguiente código muestra como crear y mostrar un 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.

J.M.Huescar J.C.Serra A.F. Bennasar 20


Informatica Gráfica II 1999/2000

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.

Añadir elementos de menú y separadores


Los elementos siempre se añaden antes del elemento seleccionado. Para añadir elementos al
final, se debe seleccionar el área vacia situada al final e insertar el nuevo elemento.
Para añadir un elemento de menú, menú desplegable, o separador a un menú,
1. Decidir donde se quiere el elemento. Hacer click, en la ventana de edición,
en el elemento encima del que se quiera que se añada la sentencia.
2. Seleccionar el nuevo elemento que se quiere insertar antes del elemento
seleccionado.
• Para insertar un nuevo elemento de menú, hacer click con el botón derecho
del ratón y elegir New Menuitem o elegir Menu|New Menuitem.
• Para insertar un menú desplegable, hacer click con el botón derecho del
ratón y elegir New Popup o elegir Menu|New Popup.
• Para insertar un nuevo separador, hacer click con el botón derecho del ratón
y elegir New Separato o elegir Menu|New Separator.
Ahora se puede editar la cadena de texto, el identificador de menú, mensaje de ayuda, y
aceleradores.

J.M.Huescar J.C.Serra A.F. Bennasar 21


Informatica Gráfica II 1999/2000

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.

J.M.Huescar J.C.Serra A.F. Bennasar 22


Informatica Gráfica II 1999/2000

Añadir aceleradores a menús


Los elementos de la página Accelerator del inspector de propiedades del editor de menús,
crean una asociación entre un recurso MENU y un recurso ACCELERATOR.

Un recurso ACCELERATOR se considera asociado a un recurso MENU si tienene el mismo


nombre de recurso (valor númerico o string). Un acelerador individual está asociado con un
elemento de menú si ambos tienen el mismo identificador.

Programación de menús y aceleradores con el API de


Windows
Para programar menús y aceleradores con el API de Windows, se deben usar las funciones
LoadMenu y LoadAccelerators.
Por ejemplo, el siguiente código muestra cómo asociar un ménu y aceleradores con la
ventana principal:

HMENU hMenu;
HMenu = LoadMenu(hInst, MAKEINTRESOURCE (IDENTIF_MENU1);
LoadAccelerators (hInst, MAKEINTRESOURCE (IDENTIF_ACELER1);

Donde IDENTIF_MENU1 y IDENTIF_ACELER1 son los identificadores del menú y de la


tabla de aceleradores respectivamente.

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.

J.M.Huescar J.C.Serra A.F. Bennasar 23


Informatica Gráfica II 1999/2000

Creacion de un fichero de identificadores


Para añadir un identificador después de crear un nuevo proyecto.
1. Hacer click con el botón derecho del ratón y elegir Add to Project. Se verá el
cuadro de diálogo Add to Project.
2. Hacer click en la flecha de List Files of Type. Elegir: C/C++ header (*.h,
*.rh)
3. Introducir un nombre para el fichero de identificadores en el cuadro File
Name.
4. Presionar OK. El Taller de recursos crea un fichero 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.

J.M.Huescar J.C.Serra A.F. Bennasar 24


Informatica Gráfica II 1999/2000

Dibujo geométrico de objetos


Una vez que tenemos la ventana y el menú de la aplicación, debemos definir la ventana de
visualización donde dibujaremos los objetos geométricos. Antes de definir las diferentes
primitivas para la creación de objetos analizaremos una serie de puntos para conseguir un
entorno agradable al usuario.

Á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

Buffer de color GL_COLOR_BUFFER_BIT

Buffer de profundidad GL_DEPTH_BUFFER_BIT

Buffer de acumulación GL_ACCUM_BUFFER_BIT

Buffer de plantilla GL_STENCIL_BUFFER_BIT

Para modificar el color de fondo de la ventana podemos utilizar los siguientes comandos
OpenGL:

glClearColor (Rojo, Verde, Azul, Alpha);

J.M.Huescar J.C.Serra A.F. Bennasar 25


Informatica Gráfica II 1999/2000

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:

glClearColor() //borra el buffer de color.


glClearDepth() //Buffer de profundidad
glClearAccum() //Buffer acumulativo
glClearStencil() //Buffer de plantilla

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.

J.M.Huescar J.C.Serra A.F. Bennasar 26


Informatica Gráfica II 1999/2000

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.

Gldouble v[3]= {4.5, 6.7, 23.8}


glVertex(v);

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.

J.M.Huescar J.C.Serra A.F. Bennasar 27


Informatica Gráfica II 1999/2000

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.

Podemos ver el efecto de los diferentes modos en el siguiente código:

glBegin (MODO);
glVertex2f(0.0, 0.0);
glVertex2f(1.0, 0.0);
glVertex2f(1.0, 1.0);
glVertex2f(0.0, 1.0);
glEnd;

Gráficamente quedará de la siguiente forma, según el valor de MODO.

GL_LINES GL_LINE_STRIP GL_LINE_LOOP

(0,1) (1,1) (0,1) (1,1) (0,1) (1,1)

(0,0) (1,0) (0,0) (1,0) (0,0) (1,0)

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:

J.M.Huescar J.C.Serra A.F. Bennasar 28


Informatica Gráfica II 1999/2000

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:

GL_POLYGON • Como mínimo se precisan 3 vertices.


• Las líneas no deben cruzarse.
• Los vertices deben formar un poligono convexo, en
caso contrario OpenGL ignorará el vertice.
Genera una serie de triangulos rellenos que no se conectan entre sí.
El número de vertices debe ser multiplo de 3, si el total de vertices es
GL_TRIANGLES
menor de tres, OpenGL ignora los vertices que no forma un
triángulo.
Genera una serie de triángulos rellenos conectados entre sí, es decir
dos de los vertices de un triángulo son los vertices del siguiente
GL_TRIANGLE_STRIP triángulo. Debemos saber que con N vertices se pueden crear N-2
triángulos. De igual forma que anteriormente el número de vertices
debe ser multiplo de tres, si no lo es se ignora aquellos que sobran.
Genera un conjunto de triángulos rellenos conectados entre sí, con la
caracteristica de que todos los triángulos tiene un vertice en común.
GL_TRIANGLE_FAN El primer triángulo defien el vertice comun a todos los triángulos.
De igual forma que los anteriores el número de vertices debe ser
múltiplo de 3, si no lo es se ignora aquellos vertices que sobran.
Genera un conjunto de cuadrilateros rellenos sin conectar entre ellos.
El número de vertices que se requiere es multiplo de cuatro, si no se
GL_QUADS
verifica esto entonces OpenGL ignora los vertices que sobran. Cada
cuatro veretices se describe un cuadrilatero.
Genera un conjunto de cuadrilateros rellenos que se conectan entre
sí, es decir dos vertices de un cuadrado se utilizan para generar el
siguiente cuadrilatero. Hay que tener en cuenta que si tenemos un
GL_QUAD_STRIP
total de N vertices podremos obtener un total de N/2-1 cuadrados. El
número de vertices debe ser multiplo de cautro, si no se verifica
entonces los vertices que sobran son ignorados.

J.M.Huescar J.C.Serra A.F. Bennasar 29


Informatica Gráfica II 1999/2000

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

J.M.Huescar J.C.Serra A.F. Bennasar 30


Informatica Gráfica II 1999/2000

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.

OpenGl, facilita maneras para poder manipular la matriz de visualización. Antes de


manipular la matriz directamente debemos inicializar el modo para la matriz de operaciones,
esto se consigue con la función:

glMatrixMode(GL_MODELVIEW);

Una vez definido el modo de la matriz podemos asignarle un valor con las siguientes
funciones:
glLoadIdentity()

Inicializada la matriz de transformaciones con la identidad.


glLoadMatrix(M)

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.

J.M.Huescar J.C.Serra A.F. Bennasar 31


Informatica Gráfica II 1999/2000

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:

J.M.Huescar J.C.Serra A.F. Bennasar 32


Informatica Gráfica II 1999/2000

y
y
x

z z

(Vx,Vy,Vz) = (0,1,0) (Vx,Vy,Vz) = (1,1,0)

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:

glOrtho(xwmin, xwmax, ywmin, ywmax, pcerca, plejos);

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.

Plano de corte lejano plejos

pcerca
Plano de corte cercano (xwmax, ywmax)

(xwmin, ywmin)

J.M.Huescar J.C.Serra A.F. Bennasar 33


Informatica Gráfica II 1999/2000

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:

glFrustum(xwmin, xwmax, ywmin, ywmax, pcerca,plejos);

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)

Plano de corte cercano


pcerca
(xwmin, ywmin)

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.

J.M.Huescar J.C.Serra A.F. Bennasar 34


Informatica Gráfica II 1999/2000

La sentencia en OpenGl será pues de la forma:

gluPerpective(apertura, aspect, pcerca, plejos);

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

Plano de corte cercano


pcerca
Apertura

Punto de vista

J.M.Huescar J.C.Serra A.F. Bennasar 35


Informatica Gráfica II 1999/2000

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

J.M.Huescar J.C.Serra A.F. Bennasar 36


Informatica Gráfica II 1999/2000

glEnd();
glEndList();
}

Y despues, cuando necesitasemos dibujar los tres circulos, hariamos lo siguiente :

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

Las operaciones basicas con listas son las siguientes :

J.M.Huescar J.C.Serra A.F. Bennasar 37


Informatica Gráfica II 1999/2000

void glNewList( GLuint list, GLenum mode );

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.

void glEndList( void );

Indica el final de los comandos de la lista.

void glCallList( GLuint list );

Ejecuta la lista indicada.

GLboolean glIsList( GLuint list );

Devuelve TRUE si existe alguna lista con el identificador dado.

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

donde 0<=i<=GL_MAX_LIGHTS. Este identificador GL_LIGHTi es el que habitualmente


se utiliza para referenciar a una luz.

J.M.Huescar J.C.Serra A.F. Bennasar 38


Informatica Gráfica II 1999/2000

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 :

• Posición y tipo de luz


Obviamente, si hay una luz, hay que indicar en que posicion de la escena esta. Esto se
consigue llamando a la siguiente función :

void glLightfv( GLenum light, GLenum pname, const GLfloat *params );

donde light es el identificador de la luz (GL_LIGHTi)


pname = GL_POSITION
params es un vector del tipo GLfloat position[4];

Con esta llamada indicamos dos cosas.


• Si params[3] tiene un valor igual a 0.0, entonces indicamos que es una luz
direccional. En este caso el vector (params[0], params[1], params[2]), da el
vector de la dirección de la luz.
• Si params[3] tiene un valor igual a 1.0, entonces indicamos que es una luz
puntual. El vector (params[0], params[1], params[2]) indica la posición de la luz

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

Estas componentes se pueden modificar con la siguiente función :

void glLightfv( GLenum light, GLenum pname, const GLfloat *params );

donde light es el identificador de la luz (GL_LIGHTi)


pname = GL_AMBIENT, GL_DIFFUSE o GL_SPECULAR, dependiendo de que
componente se quiere modificar.
params es un vector del tipo GLfloat color[4];

J.M.Huescar J.C.Serra A.F. Bennasar 39


Informatica Gráfica II 1999/2000

El formato de params debe ser el siguiente :


• color[3] debe valer 1.0
• (color[0], color[1], color[2]) da el vector RGB del color.

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 :

void glMaterialfv( GLenum face, GLenum pname, const GLfloat *params );

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
:

// Si no se esta compilando con WIN32 quitar estas dos lineas


#include "stdafx.h"
#include <windows.h>

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

void DrawScene(GLenum RenderType)

J.M.Huescar J.C.Serra A.F. Bennasar 40


Informatica Gráfica II 1999/2000

{
auxSolidSphere(1.0);
}

void __stdcall OnResizeWindow(GLsizei w, GLsizei h)


{
glViewport(0,0,w,h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(60.0,1.0*(GLfloat)w/(GLfloat)h,1.0,200.0);
gluLookAt(0.0,0.0,5.0,0.0,0.0,0.0,0.0,1.0,0.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}

void __stdcall OnRedrawWindow(void)


{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
DrawScene(GL_RENDER);
glFlush();
auxSwapBuffers();
}

int main(int argc, char* argv[])


{
auxInitDisplayMode(AUX_DOUBLE|AUX_RGBA|AUX_DEPTH);
auxInitPosition(0,0,400,400);
auxInitWindow(argv[0]);
Init();
auxReshapeFunc(OnResizeWindow);
auxMainLoop(OnRedrawWindow);

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 :

• Habilitar el mapeado de texturas


Esto se hace ejecutando la siguiente instrucción :

glEnable(GL_TEXTURE_2D);

• Especificar que imagen va a ser utilizada como textura


Para ello se utiliza la siguiente función :

void glTexImage2D( GLenum target, GLint level, GLint components, GLsizei width,
GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels );

J.M.Huescar J.C.Serra A.F. Bennasar 41


Informatica Gráfica II 1999/2000

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 :

void glTexCoord2f( GLfloat s, GLfloat t);

Donde (s,t) indica una posicíon sobre el mapa de la imagen.

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

J.M.Huescar J.C.Serra A.F. Bennasar 42


Informatica Gráfica II 1999/2000

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

• Indicar como la textura va a ser aplicada a cada pixel


Aqui hay varios puntos que indicar. El primero de ellos es indicar que ocurre con el tamaño
de las texturas. Cuando uno referencia la coordenadas de las texturas, se indican valores
entre 0 y 1, que dan los limites de las texturas. Cuando uno referencia un valor mayor que 1
o menor que 0, se esta fuera del mapa de la imagen. ¿Que hacer en estos casos?. Hay dos
posibilidades. La primera es repetir los pixels de los bordes de la textura cuando se
referencie fuera de ella, lo cual no parece que tenga mucha utilidad. La otra posibilidad es la
de repetir la textura. Esto es, en lugar de tener un mapa con solo una imagen, se tiene un
mapa donde la imagen de la textura esta repetida infinitas veces, unas contiguas a las otras.
Imaginemos que tenemos que la imagen de la textura es la imagen de una baldosa, y
queremos dibujar un suelo que vaya a contener 20x20 baldosas. Entonces, para dibujar este
suelo solo tendriamos que poner un codigo tal que asi :

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 :

void glTexParameterf( GLenum target, GLenum pname, GLfloat param );

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.

J.M.Huescar J.C.Serra A.F. Bennasar 43


Informatica Gráfica II 1999/2000

Si se quiere evitar de forma parcial este efecto, existe la posibilidad de filtrar las texturas.
Esto se hace con la siguiente llamada :

void glTexParameterf( GLenum target, GLenum pname, GLfloat param );

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.

// Si no se esta compilando con WIN32 quitar estas dos lineas


#include "stdafx.h"
#include <windows.h>

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

// Habilita las texturas


static void HabilitarTexturas(void);

// Leer la textura desde un archivo BMP de Windows de 24 bits


// Devuelve 1 si se ha leido la textura
// Devuelve 0 en otro caso
int LeerTextura(const char *Archivo);

// Pone la textura como la activa de OpenGL


void PonerActiva(void);

// Destructor
~CTextura();

// Puntero a la imagen de la textura


unsigned char *Imagen;

// Tamanyo de la textura
int TamX,TamY;

J.M.Huescar J.C.Serra A.F. Bennasar 44


Informatica Gráfica II 1999/2000

};

///////////////////////////////////
// IMPLEMENTACION DE LA CLASE CTextura
///////////////////////////////////

CTextura :: CTextura(void)
{
Imagen = NULL;
}

int CTextura :: LeerTextura(const char *Textura)


{
BOOL b = FALSE;

int namelen = strlen(Textura);


if ( namelen > 4 )
{
char suffix[5];
strncpy(suffix, Textura + namelen - 4, 4);
suffix[4] = 0;
_strupr(suffix);

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

int bufsize = infoheader.biWidth


* abs(( int ) infoheader.biHeight) * 3;
unsigned char* imagebuf = new unsigned
char[bufsize];

if ( (int)fread(imagebuf, 1, bufsize, file) ==


bufsize )
{
// Flip the RGB components
for ( int i = 0; i < bufsize / 3; ++i )
{
unsigned char c = imagebuf[ i * 3 ];
imagebuf[ i * 3 ] = imagebuf[ ( i * 3 ) +
2 ];
imagebuf[ ( i * 3 ) + 2 ] = c;
}

TamX = infoheader.biWidth;
TamY = infoheader.biHeight;
//image.setValue(size, 3, imagebuf);
b = TRUE;
}

Imagen = imagebuf;
}

J.M.Huescar J.C.Serra A.F. Bennasar 45


Informatica Gráfica II 1999/2000

fclose(file);
}
}
}

return b;
}

void CTextura :: PonerActiva(void)


{
if(Imagen)
glTexImage2D(GL_TEXTURE_2D,0,3,TamX,
TamY,0,GL_RGB,GL_UNSIGNED_BYTE, Imagen);
}

void CTextura :: HabilitarTexturas(void)


{
glPixelStorei(GL_UNPACK_ALIGNMENT,1);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glEnable(GL_TEXTURE_2D);
}

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

// Creamos y cargamos la textura


Textura = new CTextura;
if(!Textura->LeerTextura("amacdowell.bmp"))
{
printf("No se pudo leer el archivo bitmap\n");
exit(-1);
}
CTextura :: HabilitarTexturas();
Textura->PonerActiva();
}

void DrawScene(GLenum RenderType)


{
// La imagen va a salir rojiza.
// Notese que el color tambien influye el la textura
glColor3f(1.0,0.5,0.5);
glPushMatrix();
glRotatef(45.0,0.0,1.0,0.0);
glBegin(GL_QUADS);
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);
glTexCoord2f(0.0,1.0); glVertex3f(-1.0,1.0,0.0);
glEnd();
glPopMatrix();
}

J.M.Huescar J.C.Serra A.F. Bennasar 46


Informatica Gráfica II 1999/2000

void __stdcall OnResizeWindow(GLsizei w, GLsizei h)


{
glViewport(0,0,w,h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(60.0,1.0*(GLfloat)w/(GLfloat)h,1.0,200.0);
gluLookAt(0.0,0.0,4.0,0.0,0.0,0.0,0.0,1.0,0.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}

void __stdcall OnRedrawWindow(void)


{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
DrawScene(GL_RENDER);
glFlush();
auxSwapBuffers();
}

int main(int argc, char* argv[])


{
auxInitDisplayMode(AUX_DOUBLE|AUX_RGBA|AUX_DEPTH);
auxInitPosition(0,0,400,400);
auxInitWindow(argv[0]);
Init();
auxReshapeFunc(OnResizeWindow);
auxMainLoop(OnRedrawWindow);

return 0;
}

Seleccion de objetos en OpenGL


Una función tipica de muchos editores es la posibilidad de seleccionar objetos con el ratón.
Esta función en un principio es compleja. Imaginese que se tiene una escena renderizada con
una vista perspectiva, y con una posicion de la camara arbitraria. La idea seria trazar un rayo
que partiese del viewport, y averiguar que objetos atraviesa, y los calculos no son triviales.
Afortunadamente, OpenGL incluye varias funciones que permiten facilitar la selección de
objetos.
El funcionamiento del mecanismo de seleccion de OpenGL es sencillo : volver a redibujar la
escena en un modo especial, dando un identificador (un número entero) al objeto que se está
renderizando en cada momento. OpenGL testea que objetos estan, parcial o totalmente,
dentro del volumen de visualización. Si asi ocurre, entonces se guarda el identificador del
objeto dentro de una lista. Una vez finalizado el ‘dibujo’ de la escena, podemos consultar
esta lista y decidir que objetos han sido seleccionados. Un ejemplo de como se seleccionan
los objetos está a continuación :

J.M.Huescar J.C.Serra A.F. Bennasar 47


Informatica Gráfica II 1999/2000

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.

GLint glRenderMode( GLenum mode );

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.

void glSelectBuffer( GLsizei size, GLuint *buffer );

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 :

0 1 2 3 n n+1 n+2 n+3 N+4

Nº de id. Zmin Zmax Id.1 ... Id.n Nº de id Zmin Zmax Id.1 ...
Selección 1 Selección 2

Donde Nº de id. indica la lista de identificadores que corresponden con la selección. Es el nº


de identificadores que habia el la pila de nombres cuando se vio que el objeto estaba
seleccionado.

J.M.Huescar J.C.Serra A.F. Bennasar 48


Informatica Gráfica II 1999/2000

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.

void gluPickMatrix( GLdouble x, GLdouble y, GLdouble width, GLdouble height, GLint


viewport[4] );

Esta función define un volumen de seleccion perpendicular al viewport y centrado en un


punto de este. El par (x,y) indica el centro del volumen y (width,height) indica el tamaño del
volumen de seleccion. El array viewport indica las coordenadas del viewport actual, y tiene
un formato (left,top,right,down). Esta funcion normalmente se utiliza cuando se pincha el
raton sobre la pantalla y se quieren ver que es lo que se ha seleccionado. El (x,y) seria el
punto donde se ha pinchado en la pantalla, y (width,height) seria la ‘sensibilidad’ del raton.

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.

void glInitNames( void );

Esta función inicializa la pila de nombres. La deja vacia.

void glPushName( GLuint name );

Inserta un identificador en la cima de la pila. Este es el identificador que se insertará en el


buffer de selección si se selecciona el objeto.

void glPopName( void );

Saca el identificador que esta en la cima de la pila.

void glLoadName( GLuint name );

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.

J.M.Huescar J.C.Serra A.F. Bennasar 49


Informatica Gráfica II 1999/2000

// Si no se esta compilando con WIN32 quitar estas dos lineas


#include "stdafx.h"
#include <windows.h>

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>
#include <stdio.h>

#define LENGTH_MAX_PATH 256

// La estructura de una seleccion


typedef struct
{
GLuint Identifiers[LENGTH_MAX_PATH];
int NIdentifiers;
GLuint Zmin,Zmax;
} Path;

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

void DrawSphere(int Identifier, GLenum RenderType)


// Dibuja una esfera
// Identifier indica el identificador que hay que dar al objeto, si
// estamos en modo de seleccion
// RenderType indica si vamos a buscar una seleccion (==GL_SELECT)
// o vamos a dibujar el objeto (==GL_RENDER)
{
GLUquadricObj *Sphere = gluNewQuadric();

// Si estamos en modo seleccion, indicamos el identificador


// de la esfera
if(RenderType == GL_SELECT)
glPushName(Identifier);

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

// Dibujamos una esfera de radio 2.5 con 10 paralelos


//y 10 meridianos
gluSphere(Sphere,2.5,10,10);

// Sacamos el idenficador de la pila


if(RenderType == GL_SELECT)
glPopName();

glPopMatrix();
gluDeleteQuadric(Sphere);
}

void DrawScene(GLenum RenderType)

J.M.Huescar J.C.Serra A.F. Bennasar 50


Informatica Gráfica II 1999/2000

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

int GetSelection(int x, int y, Path *Selected)


// Devuelve 0 si no se ha encontrado ninguna seleccion
// Devuelve 1 si se ha encontrado alguna, y mete su valor en Selected
{
GLuint SelectionBuffer[LENGTH_MAX_PATH];
GLint ViewportCoordinates[4];
int NObjectsSelected;
Path SelectedPath, CurrentPath;
GLuint z1,z2,Identifier;
int IndexBuffer;

// Inicializamos el modo de seleccion


glSelectBuffer(LENGTH_MAX_PATH,SelectionBuffer);
glRenderMode(GL_SELECT);
glInitNames();

// Obtenemos las coordenadas actuales del Viewport


glGetIntegerv(GL_VIEWPORT,ViewportCoordinates);

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

// Ahora procesamos las selecciones detectadas


NObjectsSelected = glRenderMode(GL_RENDER);
printf("\nNumero de objetos selecionados : %i\n",NObjectsSelected);

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++];

// los zi dan una medida de lo cerca o lejos que estan los


puntos
// pinchados del objeto
// z2 es el punto mas cercano a la pantalla.
// z1 el mas lejano.
// Si hay dos objetos superpuestos, con z2 podemos decidir cual
de
// ellos es el realmente seleccionado.
if(CurrentPath.Zmax > SelectedPath.Zmax)
SelectedPath = CurrentPath;
}
if(NObjectsSelected>0)
{
*Selected = SelectedPath;
return 1;

J.M.Huescar J.C.Serra A.F. Bennasar 51


Informatica Gráfica II 1999/2000

}
else
return 0;
}

void __stdcall OnLeftMouseDown(AUX_EVENTREC *event)


{
int x,y;
Path SelectedPath;
x = event->data[AUX_MOUSEX];
y = event->data[AUX_MOUSEY];
int Seleccionado;

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

void __stdcall OnResizeWindow(GLsizei w, GLsizei h)


{
glViewport(0,0,w,h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(-8.0,8.0,-8.0,8.0,0.0,20.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}

void __stdcall OnRedrawWindow(void)


{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
DrawScene(GL_RENDER);
glFlush();
auxSwapBuffers();
}

int main(int argc, char* argv[])


{
auxInitDisplayMode(AUX_DOUBLE|AUX_RGBA|AUX_DEPTH);
auxInitPosition(0,0,400,400);
auxInitWindow(argv[0]);
Init();
auxMouseFunc(AUX_LEFTBUTTON,AUX_MOUSEDOWN,OnLeftMouseDown);
auxReshapeFunc(OnResizeWindow);
auxMainLoop(OnRedrawWindow);

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 :

J.M.Huescar J.C.Serra A.F. Bennasar 52


Informatica Gráfica II 1999/2000

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

void DibujarAtomoOxigeno(GLenum RenderType)


{
GLUquadricObj *Sphere = gluNewQuadric();
if(RenderType == GL_SELECT)
glPushName(AT_OXIGENO);
glColor3f(1.0,0.0,0.0);
gluSphere(Sphere,2.0,20,20);
gluDeleteQuadric(Sphere);
if(RenderType == GL_SELECT)
glPopName();
}

void DibujarAtomoHidrogeno(GLenum RenderType)


{
GLUquadricObj *Sphere = gluNewQuadric();
if(RenderType == GL_SELECT)
glPushName(AT_HIDROGENO);
glColor3f(0.0,0.0,1.0);
gluSphere(Sphere,1.5,20,20);
gluDeleteQuadric(Sphere);
if(RenderType == GL_SELECT)
glPopName();
}

void DibujarMoleculaAgua(GLenum RenderType)


{
if(RenderType == GL_SELECT)
glPushName(MOL_AGUA);

J.M.Huescar J.C.Serra A.F. Bennasar 53


Informatica Gráfica II 1999/2000

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

void DibujarMoleculaOxigeno(GLenum RenderType)


{
if(RenderType == GL_SELECT)
glPushName(MOL_OXIGENO);

DibujarAtomoOxigeno(RenderType);
glPushMatrix();
glTranslatef(1.5,0.0,0.0);
DibujarAtomoOxigeno(RenderType);
glPopMatrix();

if(RenderType == GL_SELECT)
glPopName();
}

void DrawScene(GLenum RenderType)


// Dibuja la escena
// RenderType indica si vamos a buscar una seleccion (==GL_SELECT)
// o vamos a dibujar el objeto (==GL_RENDER)
{
glPushMatrix();
glTranslatef(0.0,3.0,-5.0);
DibujarMoleculaAgua(RenderType);
glTranslatef(0.0,-6.0,0.0);
DibujarMoleculaOxigeno(RenderType);
glPopMatrix();
}
void __stdcall OnLeftMouseDown(AUX_EVENTREC *event)
{
int x,y;
Path SelectedPath;
x = event->data[AUX_MOUSEX];
y = event->data[AUX_MOUSEY];
int Seleccionado;

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

J.M.Huescar J.C.Serra A.F. Bennasar 54

También podría gustarte