Está en la página 1de 31

Introduccin Windows

Introduccin

la

programacin

en

Soy consciente de que hay cientos de tutoriales en Internet que abarcan este tema incluso mucho mejor, de hecho este tutorial est basado en uno de Joseph "Ironblayde" Farrell, pero el principal objetivo de este tutorial es explicar al lector los conceptos bsicos de la programacin en Windows, de manera clara y sencilla sin entrar en explicaciones excesivamente tcnicas. Aunque ya est algo iniciado en la programacin bajo este entorno, le recomiendo que lea este tutorial ya que le podra ayudar a aclarar algunos conceptos que suelen ser algo oscuros cuando se empieza. La estructura del fichero es la siguiente: * * * * * * * * * La funcin WinMain() Clases de Ventana Creando una Ventana Mensajes Leer de la cola de mensajes Enviar mensajes Flujo del programa Ejecutable y Cdigo fuente Conclusiones

La funcin WinMain()
Al igual que los programas de C bajo MS-DOS comienzan su ejecucin con la funcin main(), en Windows los programas comienzan con la funcin WinMain(). Una funcin WinMain() vaca tiene la siguiente forma: int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int CmdShow) { return(0); } A continuacin explicamos cada uno de los parmetros pasados a la funcin: HINSTANCE hinstance: Es un handle a la instancia de nuestra aplicacin. Es como un puntero """""" HINSTANCE hPrevInstance: No debemos preocuparnos por este parmetro ya que est obsoleto. En antiguas versiones de Windows era un handle a la instancia de la aplicacin que haba llamado a la nuestra. La nica razn de que an se incluya es para asegurar la compatibilidad.

LPSTR lpCmdLine: Es un puntero a una cadena que contiene los parmetros de la linea de comandos usados cuando se ha invocado al programa. Como puedes ver no hay ningun parmetro que nos diga cuantos parmetros hemos recibido as que lo tendremos que hacer por nosotros mismos. int CmdShow: Especifica como se abrir la ventana principal de la aplicacin. No es necesario utilizarlo, ya que se puede hacer de otras maneras. Los valores que toma comienzan por SW_. Como por ejemplo SW_SHOWNORMAL, SW_MAXIMIZE o SW_MINIMIZE.

Clases de Ventana
Cuando queremos crear una ventana en windows, debemos de hacerlo por medio de una clase, y a esta le pasamos los parmetros que definen las propiedades que queremos que tenga nuestra ventana. Por ejemplo, el icono, un men (si tiene asociado uno), el nombre... Para hacer esto utilizamos la clase WNDCLASSEX, el sufijo de EX viene de EXTENDED ya que es una clase con algunas modificaciones respecto a la antigua clase de ventana (WNDCLASS), nosotros utilizaremos la primera. La estructura es la siguiente: typedef struct _WNDCLASSEX { UINT cbSize; UINT style; WNDPROC lpfnWndProc; int cbClsExtra; int cbWndExtra; HANDLE hInstance; HICON hIcon; HCURSOR hCursor; HBRUSH hbrBackground; LPCTSTR lpszMenuName; LPCTSTR lpszClassName; HICON hIconSm; } WNDCLASSEX; La descripcin de cada uno de los parmetros: UINT cbSize: Este es el tamao de la estructura en bytes. Siempre ser sizeof(WNDCLASSEX) UINT style: Este es el estilo de la ventana, que viene definido por constantes con prefijos CS_. Podemos combinar estas constantes utilizando el operador | (OR).Algunos ejemplos son: CS_HREDRAW CS_VREDRAW CS_OWNDC Especifica que la ventana se dibujar si se redimensiona horizontalmente. Especifica que la ventana se dibujar si se redimensiona verticualmente. Permite que cada ventana tenga un nico contexto de dispositivo (DC)

WNDPROC lpfnWndProc: Un puntero a la funcin CALLBACK que gestiona los mensajes que recibe la ventana. Si nunca has utilizado punteros a funciones, la direccin de la funcin es sencillamente el nombre de la funcin sin los parentesis, facil eh? :) int cbClsExtra: Informacin reservada para la clase que sinceramente tampoco le he encontrado ninguna utilidad. La inicializamos a 0. int cbWndExtra: Igual que la anterior pero reservada para la ventana. HANDLE hInstance: Instancia a la aplicacin que est usando la ventana. Es uno de los parmetros de WinMain() HICON hIcon: Un handle al icono que representa el programa. Generalmente se suele inicializar por medio de la funcin LoadIcon(). El prefijo IDI_ representa iconos de Windows. Para cargar el icono genrico de Windows (hasta que aprendamos a cambiarlo) escribimos: LoadIcon (NULL, IDI_WINLOGO) HCURSOR hCursor: Un handle al cursor que utiliza el ratn cuando estn sobre nuestra ventana. Es cargado con la funcin LoadCursor(). Al igual que el anterior, se pueden cargar desde los recursos, pero mientras tanto utilizaremos la siguiente llamada para cargar el estandar: LoadCursor (NULL, IDC_ARROW) HBRUSH hbrBackGround: Cuando la ventana recibe el mensaje de refrescar (repintar) Windows pinta el area de la ventana con un color slido o "brush". El "brush" es definido por este parmetro. Los "brush" se pueden cargar por medio de GetStockObject(). Algunos ejemplos son: BLACK_BRUSH, WHITE_BRUSH... Por ahora utilizaremos: GetStockObject (BLACK_BRUSH) LPCTSTR lpszMenuName: Si queremos crear una ventana que tenga un menu de tipo pull-down, este parametro especifica el nombre del mismo. Como por ahora no vamos a crear ninguno lo inicializamos a NULL. LPCSTR lpszClassName: Nombre de la clase, por ejemplo "Clase_Juego". HICON hIconSm: Es un handle al icono pequeo que aparece en la barra superior de la aplicacin Se carga de la misma manera que el hIcon. Ahora que ya estamos algo ms familiarizados con la estructura WNDCLASSEX vamos a rellenar cada uno de los parmetros: LPCTSTR lpszMenuName: Si queremos crear una ventana que tenga un menu de tipo pull-down, este parametro especifica el nombre del mismo. Como por ahora no vamos a crear ninguno lo inicializamos a NULL.

WNDCLASSEX ClaseEjemplo; // Declaramos una variable de la estructura ClaseEjemplo.cbSize = sizeof(WNDCLASSEX); // Usa siempre este ClaseEjemplo.style = CS_DBLCLKS | CS_OWNDC | CS_HREDRAW | CS_VREDRAW; // Estandar :) ClaseEjemplo.lpfnWndProc = GestorMensajes; //Se explica en este tutorial :) ClaseEjemplo.cbClsExtra = 0; // Info extra de la clase. NO USADO ClaseEjemplo.cbWndExtra = 0; // Info extra de la ventana. NO USADO ClaseEjemplo.hInstance = hinstance; // Parametro pasado a WinMain() ClaseEjemplo.hIcon = LoadIcon(NULL, IDI_WINLOGO); // Logo de Windows :( ClaseEjemplo.hCursor = LoadCursor(NULL, IDC_ARROW); // Cursor estandar ClaseEjemplo.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); // Fondo Negro ClaseEjemplo.lpszMenuName = NULL; // Sin men ClaseEjemplo.lpszClassName = "Clase de Ejemplo" // Nombre de la clase ClaseEjemplo.hIconSm = LoadIcon(NULL, IDI_WINLOGO); // Otra vez el logo de Windows >:(( Como nota, explicar que cuando cargamos el "brush" hemos escrito (HBRUSH) delante de la llamada a la funcin GetStockObject(), hemos hecho esto porque esta funcin no solo se utiliza para cargar este tipo de dato sino que es mucho ms general y devuelve un valor de tipo HGDIOBJ, por lo que lo tenemos que convertir a HBRUSH. Lo ultimo que tenemos que hacer es registrar nuestra nueva clase, para que Windows la pueda usar para crear una ventana esto se hace utilizando la funcin RegisterClassEx(). Solamente toma como parmetro la direccin de nuestra estructura. La llamada sera: RegisterClassEx(&ClaseEjemplo);

Creando una ventana


Para crear una ventana lo "nico" que tenemos que hacer es llamar a la funcin CreateWindowEx(), eso s, toma bastantes parmetros :( Este es el prototipo de la funcin: HWND CreateWindowEx { DWORD dwExStyle, LPCTSTR lpClassName, LPCTSTR lpWindowName, DWORD dwStyle, int x, int y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, aplicacin LPVOID lpParam ventana );

// // // // // // // // // //

Estilo de ventana extendido Puntero a la clase de ventana registrada Puntero al nombre de la ventana Estilo de ventana Posicin horizontal de la ventana Posicin vertical de la ventana Ancho de la ventana Alto de la ventana Puntero (Handle) al padre de nuestra ventana Puntero (handle) a un men o a la ventana hija // Puntero (handle) a la instancia de la // Puntero a los datos de la creacin de la

Lo primero de todo es comentar el valor de retorno de la funcin. Esta funcin devuelve un valor de tipo HWND que es un handle a la ventana. Esta valor es muy importante y debemos de guardarlo en alguna variable ya que es usado en un gran nmero de funciones de Windows. Los dems parmetros son: DWORD dwExStyle: Las constantes usadas para este parmetro comienzan por WS_ES_ pero raramente utilizaremos alguna, as que lo inicializamos a NULL. LPCTSTR lpClassName: El nombre de la clase que hemos creado ("Clase de ejemplo") LPCTSTR lpWindowsName: El ttulo que aparece en la barra de la aplicacin. DWORD dwStyle: Especifica que tipo de ventana vamos a crear. Hay muchas constantes que podemos usar, comenzando por WS_, y pueden ser combinadas con el operador |. Algunas comunes son: WS_POPUP WS_OVERLAPPED WS_OVERLAPPEDWINDOW WS_VISIBLE La ventana no contiene controles. Ventana simple con barra de titulo y borde. Ventana con barra de titulo y todos los controles estandar. Especifica que la ventana inicialmente es visible.

La constate WS_OVERLAPPEDWINDOW es en realidad una combinacin de otras constantes. Si lo que queremos poner en nuestra ventana son los botones Maximizar, minimizar, cerrar, etc... utilizaremos la anterior. Si queremos solo el titulo de barra y su borde utilizamos WS_OVERLAPPED. Si por el contrario no queremos nada de esto, utilizareoms la constante WS_POPUP que har que inicialmente la ventana aparezca como un rectangulo negro, que generalmente es lo que se usa para aplicaciones a pantalla completa (videojuegos :D ). int nWidth, nHeight: Especifican la anchura y la altura (en pixels) de la ventana. HWND hWndParent: Es un handle a la ventana padre de la ventana que hemos creado. Para una ventana principal, se establece a NULL (Que especifica el escritorio). HMENU hMenu: Es un handle al menu que est asociado a la ventana. Si lo cargamos de los recursos utilizaremos la funcion LoadMenu(). Para nuestra ventanan no queremos men as que lo ponemos a NULL. HINSTANCE hInstance: Instancia a la aplicacin, usado en WinMain(). LPVOID lpParam: Este parmetro no es muy usual usarlo, sobre todo para videojuegos, se utiliza para crear cosas como interfaz de documento mltiplo. As que otro NULL. :) Ahora lo que tenemos que hacer es rellenar la informacin y crear nuestra ventana :D

HWND hwnd; if (!(hwnd = CreateWindowEx( NULL, "Clase de Ejemplo", "Mi primera ventana ", WS_POPUP | WS_VISIBLE, 0, 0, 320, 240, NULL, NULL, hinstance, NULL))) return(0); Se ha incluido la funcin CreateWindowsEx dentro de una sentencia if ya que si falla la creacin de la ventana devueve NULL, con lo cual debemos de salir de la aplicacin. Con todo esto ya hemos creado una ventana totalmente funcional, pero ahora necesitamos interactuar tanto con el usuario, como con el sistema operativo y las demas aplicaciones. // // // // // // // // // Estilo extendio NO USADO Identificador del a clase Ttulo de la ventana Parametros de estilo Posicin inicial, tamao Handle a la ventana padre (Escritorio) Handle al menu (Ninguno) Handle a la instancia de la aplicacin NO USADO.

Mensajes
Cuando programas en MS-DOS no te tienes que preocupar acerca de otros programas que pueden estar ejecutandose ya que MS-DOS como todos sabemos no es un sistema operativo multitarea. Sin embargo cuando programamos en Windows si tenemos que considerar este dato. Por esta razn, y otrs ms, Windows usa lo que se conoce como mensajes para comunicarse con las aplicaciones y decirles que es lo que tienen que hacer. Los mensajes pueden ser usados para muchos propsitos diferentes. Nos informan cuando una ventana se cambia de tamao, se mueve o se cierra; nos dice cuando debemos refrescar la imagen; tambien pueden ser usados para controlar el movimiento del ratn y las pulsaciones, por ejemplo. La lista es bastante grande y como es obvio nuestro programa debera controlarlos. La manera de hacerlo es por medio de una funcin especial denominada CALLBACK. Las funciones CALLBACK son funciones que no son llamadas directamente desde tu cdigo, sino que son llamadas externamente cuando se producen unas ciertas condiciones. El prototipo de una funcin manejadora de mensajes es: LRESULT CALLBACK GestorMensajes( HWND hwnd, // Ventana handle UINT msg, // El identificador del mensaje WPARAM wparam, // Parmetros del mensaje LPARAM lparam // Ms parmetros del mensaje }; El tipo LRESULT es usado comnmente en funciones de proceso de mensajes.

HWND hwnd: Este es el handle de la ventana que ha enviado el mensaje que se est procesando. UINT msg: Este es el identificador del mensaje. Los valores de este parmetro son constantes que comienzan por el prefijo WM_ (Windows Message). A continuacin se muestran algunos de los ms importantes:

WM_ACTIVATE WM_CLOSE WM_COMMAND WM_CREATE WM_LBUTTONDBLCLK WM_LBUTTONDOWN WM_MOUSEMOVE WM_MOVE WM_PAINT WM_SIZE WM_USER

La ventana ha obtenido el foco. La ventana va a ser cerrada. Ha sido seleccionada una opcin del men. La ventana ha sido creada. Se ha hecho doble-click en el botn izquierdo Se ha hecho click en el botn izquierdo. El ratn se ha movido. La ventana se ha movido. Partes de repintarse la ventana necesitan

Se ha cambiado el tamao de la ventana Para uso personal

WPARAM wparam, LPARAM lparam: El valor de estos parmetros depende del tipo de mensajes enviado pero bsicamente contienen el contenido del mensajes. Si se te ha pasado por la cabeza controlar cada uno de los mensajes que tu ventana puede recibir, sientate, relajate, respira un poco y sigue leyendo :) Seguramente haya muchos mensajes que no te interese controlar. Para este fin est la funcin DefWindowsProc(). As podemos crear nuestro primer manejador de mensajes totalmente funcional y correcto (aunque algo inutil): LRESULT CALLBACK GestorMensajes(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { return(DefWindowProc(hwnd, msg, wparam, lparam)); } Al menos simple es no?. Aunque generalmente nos interesa manejar algunos mensajes, para lo que incluimos el cdigo necesario y devolvemos 0 para decirle al programa que ya hemos terminado de procesar el mensaje. Aqui se muestra un ejemplo de un manejador de mensajes que llama a un funcin de inicializacin cuando se crea la ventana y llama al manejador por defecto para cualquier otro mensaje. LRESULT CALLBACK GestorMensajes(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { if (msg == WM_CREATE)

{ Inicializar_Juego(); return(0); } return(DefWindowProc(hwnd, msg, wparam, lparam)); } Seguramente tu manejador de mensajes comience con una sentencia SWITCHs para manejar de una manera ms estructurada los diferentes mensajes que queremos tratar.

Leer de la cola de mensajes


No debemos olvidarnos de mirar en la cola de mensajes (donde todos los mensajes pendientes estn almacenados) hay algo para nosotros. Si es as, hay algunas cosas que debemos hacer para que nuestro manejador procese los mensajes correctamente. La funcin que necesitamos es PeekMessage(). Este es el prototipo: BOOL PeekMessage ( LPMSG lpMsg HWND hWnd UINT wMsgFilterMin UINT wMsgFilterMax UINT wRemoveMsg } Parametros: BOOL (retorno): Devuelve TRUE si hay un mensaje esperando en la cola y FALSE si no. LPMSG lpMsg: Puntero a una variable de tipo MSG. Si hay un mensaje esperando esta variable contendr la informacin del mensaje. HWND hWnd: Hande a la ventana de la que queremos comprobar la cola de mensajes. UINT wMsgFilterMin, UINT wMsgFilterMax: Los indices del primer y del ltimo mensaje en la cola. Generalmente a nosotros nos interesar solo el primer mensaje, as que pondremos ambos parametros a 0. UINT wRemoveMsg: Generalmente solo toma dos valores: PM_REMOVE o PM_NOREMOVE. El primero se utiliza si queremos que el mensaje se elimine de la cola de mensajes una vez leido, y el segundo para que siga en la cola. Casi siempre utilizaremos PM_REMOVE. Si hay un mensaje esperando, necesitamos hacer algunas cosas para que el manejador se encarge de ellos. Esto se hace por medio de dos simples llamadas: BOOL TranslateMessage (CONST MSG *lpmsg);

// Puntero a la estructura del mensaje // Handle a la ventana // Primer mensaje // Ultimo mensaje // Flag de eliminacin

LONG DispatchMessage (CONST MSG *lpmsg); La primera funcin, como su nombre indice, hace una pequea traduccin del mensaje. La segunda invoca nuestro manejador de mensajes y le pasa la informacin apropiada de la estructura MSG. Y bien... !esto es todo lo que necesitamos!. Con cada iteracin del bucle principal, si hay un mensaje esperando, llamamos a estas dos funciones y el manejador de mensajes se encarga del resto :) El cdigo de ejemplo es: if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); }

Enviar mensajes
Ya que estamos tratando el tema de los mensajes, tambien conviene saber como enviarlos manualemente. Para enviar mensajes manualmente hay dos opciones, utilizar la funcin PostMessage() o SendMessage(). Sus prototipos son muy parecidos: BOOL PostMessage( HWND hWnd, UINT Msg, WPARAM wParam LPARAM lParam );

// // // //

Handle de la ventana destino Mensaje a enviar Primer parmetro del mensaje Segundo parmetro del mensaje

LRESULT SendMessage( HWND hWnd, UINT Msg, WPARAM wParam LPARAM lParam );

// // // //

Handle de la ventana destino Mensaje a enviar Primer parmetro del mensaje Segundo parmetro del mensaje de mensajes.

Los parmetros son los mismos que utiliza nuestra funcin manejadora Lo nico que debemos saber es la diferencia entre las dos funciones.

PostMessage se usa cuando lo que queremos hacer es meter el mensaje que enviamos en la cola de mensajes de la ventana y que la lgica de nuestro programa se encarge de procesarlo (como hemos visto anteriormente). La funcin devuelve TRUE si se ha conseguido meter bien, o FALSE en caso contrario. SendMessage sin embargo lo que hace no es poner el mensaje en la cola sino que lo traduce e invoca directamente al manejador de mensajes de la ventana inmediatamente y no termina hasta que no se ha procesado el mensaje completamente. Por lo tanto esta funcin ser usada para mensajes de prioridad alta que necesitan procesarse lo ms rpido posible.

Flujo del programa


Como ya hemos comentado anteriormente la programacin en Windows es diferente que bajo DOS ya que tenemos que encargarnos de los mensajes que recibimos y a la vez tener en cuanta la interaccin con los dems programas. Veamos un ejemplo de flujo de programa en pseudo-codigo para ver ms clara la diferencia: // Flujo de programa principal while (juego_activo) { // Aqui manejamos los mensajes ... // Actualizamos la pantalla si es necesario if (actualizar) { fadeout(); CargarNuevoMapa(); fadeIn(); } // Lgica del juego ComprobarEntrada(); ActualizaJugadores(); RenderizaMapa(); } Si estuvieramos en MS-DOS la funcin fadeout() la implementariamos de manera que cuando se llama, realiza un bucle en el que va oscureciendo la pantalla poco a poco (incluyendo un delay por ejemplo), cuando termina de oscurecer todo (negro), devuelve true. Y la funcin ComprobarEntrada() sera una funcin del tipo de la anterior que espera hasta que se pulse una tecla. Muy bien, esto es lo que se hara en MS-DOS, pero en Windows NO!! veamos porqu. El problema radica en que si lo hacemos como lo anterior, cuando se evalua actualizar a verdadero, se ejecutara el codigo de fadeout(), CargarNuevoMapa() y fadeIn(), que supongamos que tarda 10 segundos, tiempo en el que no hemos procesado los mensajes que recibimos y tiempo ms que suficiente para haber recibido muchos mensajes algunos como por ejemplo Cambiar de tamao (deberiamos de cambiar el tamao de la ventana para oscurecerla toda completa), Minimizar (Terminariamos con la funcin fadeout()), o Cerrar. Todo esto puede provocar resultados incorrectos, errores de proteccin general,... en definitiva nada bueno :( Lo que debemos hacer es ponernos como punto de partida que nuestro programa se ejecute a una velocidad de 30 FPS (frames per second) como mnimo, esto significa que el bucle principal se ejecuta 30 veces por segundo y en cada iteracin del bucle principal mostramos un solo frame oscurecido (No todos como haciamos con la fadeout() de MS-DOS).

Ejecutable y Codigo fuente


El cdigo de ejemplo que acompaa a este rticulo muestra una ventana vacia en pantalla. La principal utilidad del cdigo es como esqueleto bsico para crear una ventana y aplicacin en Windows. Tutorial_2.zip (54,9Kb) Descargar versin imprimible del tutorial (.PDF)

Conclusiones
Con este tutorial ya deberias ser capaz de crear una ventana y controlar algunos mensajes. Aunque seguramente si eres nuevo en la programacin en Windows te resulte un poco dificil al principio. Si tu problema no es la programacin en Windows en s, sino que no conoces el entorno Visual Studio (VC++) o el lenguaje C++ te recomendara que leyeras sobre el tema antes de seguir con los tutoriales. Hay algunos muy buenos de la serie Learn XXX in 21 days donde XXX es VC++ o C++, no seais mal pensados X'D. Si tienes cualquier duda no dudes en escribirme. Agradecera si alguien encuentra algn error, quiere hacer un comentario o pregunta sobre este artculo que no dude en ponerse en contacto conmigo tanto por email como por foro (preferiblemente este ltimo para que sirva para los demas lectores).

Inicializando OpenGL
Introduccin
Despues de haber leido el tutorial anterior sobre programacin en Windows, estamos en condiciones (o deberamos) de crear una ventana. En este tutorial lo que vamos a hacer es inicializar la librera OpenGL para poder utilizarla en nuestra ventana. Para ello vamos a crear una Clase llamada CGL que encapsula todas las funciones necesarias para la inicializacin (y posterior eliminacin) de OpenGL. Seguramente este Tutorial pueda parecer bastante confuso (y eso para ser el primero) pero el problema es que la inicializacin es siempre algo muy tedioso (Al igual que en DX) pero tampoco hace falta saber exactamente para que sirve cada funcin en la inicializacin sino ms bien pillar el concepto de que es lo que estamos haciendo. Para este fin, entre otros, he creado la clase CGL, la cual encapsula toda la inicializacin de OpenGL y nos permite reutilizarla en futuros programas y solamente nos tendremos que preocupar de la apliacin en si y no de la inicializacin ya que practicamente con dos lineas aadidas a nuestro programa podemos crear nuestra ventana:

#include <CGL.H> CGL *gl ... gl->CreaVentanaGL(640,480,"Mi ventana",PantallaCompleta); Y con estas lineas ya tenemos creada nuestra ventana. Por ahora implementaremos una clase muy sencilla pero que es lo suficientemente potente como para servir en los siguientes tutoriales. Ms adelante crear una clase ms compleja y fea sobre todo para la gente que sea nueva en la programacin orientada a objetos (Punteros a funciones, funciones tipo CALLBACK, Clases derivadas...) y cuando la implemente lo que har ser explicar ms bien como usarla en vez de como crearla, porque al fin y al cabo no tenemos porqu saber como est implementado todo lo que usamos no?. :) Pues bien empazamos!!! ... La estructura de este fichero es la siguiente: * Descripcin de las funciones principales y variables * Definicin de la clase CGL * Implementacin * Programa de ejemplo * Ejecutable y Cdigo fuente * Conclusiones

Descripcin de las funciones principales y variables


Funciones Principales
Los pasos necesarios para crear una ventana en OpenGL los podemos clasificar como sigue: - Creamos una ventana. - Establecemos el marco de visin y la perspectiva. - Inicializamos las variables de estado de OpenGL. Las funciones principales necesarias para inicializar GL son: CreaVentanaGL: Se puede considerar como la funcin principal. Crea una ventana de Windows, selecciona el formato de pixel, crea su RC y DC y posteriormente llama a las funciones siguientes para asignarle propiedades de OpenGL. EliminaVentanaGL: Libera todos los recursos usados por la ventana. InicializaEscenaGL: Establece el marco de visin (Viewport) de la escena, as como la perspectiva que vamos a utilizar. InicializaGL: Inicializamos todos los valores internos de OpenGL, como por ejemplo el tipo de sombreado y luces, si vamos a utilizar mapeado de texturas, el color de fondo de la escena,...

De este modo la funcin CreaVentanaGL, despus de crear la ventana, llamara a InicializaEscenaGL y posteriormente a InicializaGL y ya tendramos nuestra ventana creada. Si por algn motivo la llamada a las dos ltimas funciones fallara, eliminariamos la ventana mediante EliminaVentanaGL y saldramos de la aplicacin.

Variables
Para empezar lo primero que haremos ser hacer una breve descripcin de las variables que vamos a necesitar a la hora de inicializar OpenGL. HGLRC hRC: Contexto de render (Rendering context). Cada programa creado con OpenGL necesita un RC, que es lo que enlaza las llamadas a OpenGL con el contexto de dispositivo. HDC hDC: Contexto de dispositivo (Device context). Para poder dibujar algo en la ventana tenemos que crear un DC que es el que enlaza la ventana con el GDI (Graphics Devide Interface). As el RC conecta OpenGL con el DC y permite entonces mostrar imagenes en la pantalla. HWND hWnd: Handle de nuestra ventana. (Comentado en el Tutorial 2). HINSTANCE hInstance: Instancia a nuestro programa (Comentado en el Tutorial 2). Tambien vamos a definir otras dos variables que nos sern de utilidad: bool PantallaCompleta: Su nombre lo dice todo :)

Definicin de la clase CGL


La definicin de la clase CGL, que se almacenar en CGL.H es la siguiente:

class CGL { public: virtual BOOL nAlto, int nBits, bool GLvoid GLvoid Elimina la ventana CGL ~CGL CreaVentanaGL bPantallaCompleta); InicializaEscenaGL EliminaVentanaGL (); (); (char* szTitulo, int nAncho, int (GLsizei ancho, GLsizei alto); (GLvoid); //

// C

// Funciones para acceder a las variables miembro void PonPantallaCompleta (bool bPCompleta) {m_bPantallaCompleta=bPCompleta;};

HDC m_hDC;}; HGLRC HWND m_hWnd;}; HINSTANCE m_hInstance;}; bool m_bPantallaCompleta;}; private:

ObtHDC ObtHRC ObtHWnd ObtHInstance ObtPantallaCompleta

() () () () ()

{return {return m_hRC;}; {return {return {return

int InicializaGL inicializacines de OpenGl vienen aqui // Variables miembro bool m_bPantallaCompleta; HDC m_hDC; HGLRC m_hRC; HWND m_hWnd; HINSTANCE m_hInstance; };

(GLvoid);

// Todas la

Implementacin de las funciones


Pasamos ahora a implementar cada una de las funciones de nuestra clase.

CreaVentanaGL
La primera funcin que implementaremos ser la CreaVentanaGL ya que ser la primera a la que llamaremos desde nuestro programa principal. Sin duda esta es la funcin ms grande y tediosa de toda la clase pero no nos desanimemos :). La definicin de la funcin es la siguiente: BOOL CreaVentanaGL (char* Titulo, int Ancho, int Alto, int Bits, bool PantallaCompleta); char *Titulo: Ttulo que tendr la ventana que vamos a crear. int Ancho, Alto: Dimensiones de la ventana. int Bits: Bits por pixel (16,24,32) PantallaCompleta: TRUE en pantalla completa, FALSE en ventana. Las variables que utilizaremos sern las siguientes: GLuint FormatoPixel: Cuando le pedimos a Windows que busque el formato de Pixel que queremos, esta variable almacena el modo resultante en esta variable. WNDCLASS ClaseVentana: Esta variable almacena nuestra Clase Ventana, donde est contenida la informacin

sobre la misma. Es importante no olvidar que despus de crear la ventana debemos de registrar la clase que tiene asignada. DWORD dwEstiloExt,dwEstilo: Contienen informacin acerca del estilo de la ventana (Extendido y normal). RECT RectanguloVentana: Con esta variable creamos el rectngulo con las dimensiones que queremos que tenga la ventana y haciendo uso del mismo ajustamos la resolucin de la ventana. Para hacer esto lo inicializamos como sigue: RectanguloVentana.left=(long)0; RectanguloVentana.right=(long)ancho; RectanguloVentana.top=(long)0; RectanguloVentana.bottom=(long)alto; Almacenamos en la variable de miembro m_bPantallaCompleta la opcin elegida: m_bPantallaCompleta = PantallaCompleta; A continuacin guardamos una instancia para nuestra ventana y inicializamos la clase Ventana. hInstance=GetModuleHandle(NULL); ClaseVentana.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; // Forzamos que se pinte de nuevo cuando se cambie el tamao. DC propio ClaseVentana.lpfnWndProc = (WNDPROC) GestorMensajesGlobal; // Manejador de mensajes ClaseVentana.cbClsExtra = 0; // No utilizamos datos extras de la clase ClaseVentana.cbWndExtra = 0; // No utilizamos datos extras de ventana ClaseVentana.hInstance = hInstance; // Establecemos la instancia ClaseVentana.hIcon = LoadIcon(NULL, IDI_WINLOGO); // Cargamos el icono por defecto ClaseVentana.hCursor = LoadCursor(NULL, IDC_ARROW); // Cargamos el cursor por defecto ClaseVentana.hbrBackground = NULL; // No hace falta establecer un color de fondo para OGL ClaseVentana.lpszMenuName = NULL; // No utilizamos men ClaseVentana.lpszClassName = "Ventana OpenGL"; // El nombre de la clase

Ahora debemos registrar la ventana, si hubiera un error, lo mostrariamos en pantalla y saldriamos: if (!RegisterClass(&ClaseVentana)) { MessageBox(NULL,"Fallo al registrar la clase ventana." , "ERROR",MB_OK|MB_ICONEXCLAMATION); return FALSE; }

A continuacin comprobamos si hemos se ha solicitado mostrar la ventana a pantalla completa, si as es lo hacemos (o intentamos). Para ello una de las cosas que tenemos que tener en cuenta es que debemos cambiar a pantalla completa despues de haber creado la ventana: if (PantallaCompleta) { DEVMODE dmOpcionesPantalla; // Modo de dispositivo. memset(&dmOpcionesPantalla,0,sizeof(dmOpcionesPantalla)); // Limpiamos la memoria dmOpcionesPantalla.dmSize=sizeof(dmOpcionesPantalla); // Tamao de la estructura dmOpcionesPantalla.dmPelsWidth = Ancho; // Ancho de pantalla dmOpcionesPantalla.dmPelsHeight = Alto; // Alto de pantalla dmOpcionesPantalla.dmBitsPerPel = Bits; // Bits por pixel dmOpcionesPantalla.dmFields=DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT; Despus de almacenar la informacin sobre el modo de pantalla que queremos, intentamos cambiar a pantalla completa por medio de la funcin ChangeDisplaySettings. A la que le pasamos como parmetro la estructura que hemos creado (dmOpcionesPantalla) y la constante CDS_FULLSCREEN que elimina la barra de Inicia de la parte inferior de la pantalla. // Intentamos cargar el modo seleccionado if (ChangeDisplaySettings(&dmScreenSettings,CDS_FULLSCREEN)!=DISP_CHANGE_SUCCESSFUL) { // Si el modo falla. Hay dos opciones: Salir o poner modo ventana if (MessageBox(NULL,"El modo pantalla completa no es soportado por\nsu tarjeta grfica. Desea usar modo ventana?","Ventana?",MB_YESNO|MB_ICONEXCLAMATION)== IDYES) { PantallaCompleta=FALSE; // Modo ventana seleccionado. } } else { // Mostramos un mensaje y cerramos MessageBox(NULL,"El programa cerrar.", "ERROR", MB_OK|MB_ICONSTOP); return FALSE; } } } Debido a que el cdigo anterior para pantallac completa puede fallar y el usuario puede decidir ejecutar la aplicacin en modo ventana, debemos comprobar si seguimos estando en pantalla completa o no. if (PantallaCompleta)

Si estamos en pantalla completa debemos establecer el estilo extendido de la ventana en WS_EX_APPWINDOW. y en el estilo de la ventana que vamos a crear ponemos WS_POPUP. Este tipo de ventana no tiene borde por lo es ideal para el modo en pantalla completa. Para finalizar ocultamos el puntero del ratn (opcional). { dwExStyle=WS_EX_APPWINDOW; dwStyle=WS_POPUP; ShowCursor(FALSE); } else { Si por el contrario nos encontramos en modo ventana, en el modo extendido volvemos a incluir WS_EX_APPWINDOW pero adems aadimos WS_EX_WINDOWEDGE que le da una apariencia 3D. Para el estilo utilizamos WS_OVERLAPPEDWINDOW que crea una ventana con barra de ttulo, bordes para ajustar tamao, men de ventana y los bonotes de max/min. dwExStyle=WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; dwStyle=WS_OVERLAPPEDWINDOW; } Ahora ajustamos la ventana al estilo que hayamos elegido, esto lo hacemos para garantizar que la ventana sea de la resolucin que hemos escogido ya que a veces los bordes pueden ocultar parte de nuestra escena. NOTA: Este comando no tiene efecto en pantalla completa. AdjustWindowRectEx(&WindowRect, dwStyle, FALSE, dwExStyle); Una vez hecho esto creamos la ventana y comprobamos que ha sido creada correctamente. Para ello llamamos a la funcin CreateWindowEx(). Para comprobar si se ha creado correctamente sabemos que si ha sido correcto hWnd contiene el puntero a la ventana, en caso contrario contiene NULL. NOTA: Hemos incluido las constantes WS_CLIPSIBLINGS y WS_CLIPCHILDREN que son necesarias para el correcto funcionamiento de OpenGL. if (!hWnd=CreateWindowEx( dwExStyle, "OpenGL", Titulo, WS_CLIPSIBLINGS | WS_CLIPCHILDREN | // Estilo de ventana requerido por OpenGL dwStyle, 0,0, WindowRect.right-WindowRect.left, // Ancho de la ventana WindowRect.bottom-WindowRect.top, // Alto de la ventana NULL, NULL, hInstance, NULL))) {

EliminaVentanaGL(); MessageBox(NULL,"Error en la creacin de la ventana","ERROR",MB_OK|MB_ICONEXCLAMATION); return FALSE; } A continuacin seleccionamos el formato de pixel. Vamos a elegir uno que soporte OpenGL :), doble buffer y con RGBA (Red, Green, Blue, Alpha channel). Intentamos buscar el formato de pixel acorde con el nmero de bits que nos han indicado (16,24,32) y finalmente creamos un Z-Buffer de 16bits. static PIXELFORMATDESCRIPTOR pfd = { sizeof(PIXELFORMATDESCRIPTOR), // Tamao de este descriptor 1, // Nmero de versin PFD_DRAW_TO_WINDOW | // El formato debe soportar ventana PFD_SUPPORT_OPENGL | // El formato debe soportar OpenGL PFD_DOUBLEBUFFER | // Debe soportar Doble Buffer PFD_TYPE_RGBA, // Tambin debe soportar RGBA bits, // Bits por pixels seleccionados 0,0,0,0,0,0, // Bits de color ignorados 0, // Sin buffer alpha 0, // Shift bit ignorado 0, // Buffer de acumulacin ignorado 0,0,0,0, // Bits de acumulacin ignorados 16, // Z-Buffer de 16 bits 0, // Sin buffer de pincel (Stencil) 0, // Sin buffer auxiliar PFD_MAIN_PLANE, // Layer de dibujo principal 0, // Reservado 0,0,0, // Layers de mscara ignorados }; Intentamos obtener el Contexto del dispositivo OpenGL (hDC). Si no es posible mostramos un mensaje de error y salimos. if (!(hDC=GetDC(hWnd))) { EliminaVentanaGL(); MessageBox(NULL, "No se ha podido crear el contexto de dispositivo GL","ERROR",MB_OK|MB_ICONEXCLAMATION"); return FALSE; }; Si disponemos del DC buscamos un formato de pixel que corresponda a la descripcin que hemos hecho antes. if (!(PixelFormat=ChoosePixelFormat(hDC,&pfd))) {

EliminaVentanaGL(); MessageBox(NULL, "No se ha encontrado un formato de pixel correcto","ERROR",MB_OK|MB_ICONEXCLAMATION"); return FALSE; }; Si lo hemos encontrado probamos a seleccionarlo. if (!SetPixelFormat(hDC,PixelFormat,&pfd)) { EliminaVentanaGL(); MessageBox(NULL, "No se ha podido establecer el formato de pixel","ERROR",MB_OK|MB_ICONEXCLAMATION"); return FALSE; }; Ahora intentamos obtener el contexto de render (Rendering context) de OGL. if (!(hRC=wglCreateContext(hDC))) { EliminaVentanaGL(); MessageBox(NULL, "No se ha podido crear el RC de GL","ERROR",MB_OK|MB_ICONEXCLAMATION"); return FALSE; }; Si todo ha ido bien lo que tenemos que hacer ahora es activar el RC de OpenGL. Para esto hacemos: if (!wglMakeCurrent(hDC,hRC)) EliminaVentanaGL(); MessageBox(NULL, "No se ha podido activar el RC de GL","ERROR",MB_OK|MB_ICONEXCLAMATION"); return FALSE; }; Si hemos llegado hasta aki ya tenemos creada la ventana OpenGL ahora lo que debemos hacer es mostrarla :), ponerle ms prioridad y ponerle el foco de ventana. Luego llamamos a InicializaEscenaGL (pasandole el tamao de la ventana) para establecer la perspectiva de la escena. ShowWindow(hWnd,SW_SHOW); // Muestra la ventana SetForegroundWindow(hWnd); // Establecler prioridad alta SetFocus(hWnd); // Establece el foco del teclado en la ventana InicializaEscenaGL(ancho,alto); // Inicializamos la perspectiva de OpenGL de la ventana Finalmente llamamos a InicializaGL() donde se inicializa las Luces, Texturas,... en general todos los estados de OpenGL. Y comprobamos que no haya habido ningun error. if (!InicializaGL()) EliminaVentanaGL(); MessageBox(NULL, "Fallo al inicializar GL","ERROR",MB_OK|MB_ICONEXCLAMATION"); return FALSE; }; Nuestra ventana se ha creado correctamente :D salimos y devolvemos el control a WinMain. Devolvemos TRUE para indicar a WinMain que no ha habido ningun error. return TRUE; }

EliminaVentanaGL
Esta funcin elimina la ventana que hemos creado (obvio no?) para lo cual elimina el RC, DC y finalmente el puntero a la ventana hWnd. La definicin de la funcin es: GLvoid EliminaVentanaGL (GLvoid) Lo primero que hace la funcin es comprobar si estamos en modo pantalla completa. Si estamos volvemos a modo ventana para eliminar la ventana. Se puede eliminar estado en pantalla completa pero el escritorio puede aparecer corrupto debido a las diferencias entre algunas tarjetas grficas. Haciendolo as garantizamos que funcione para todas. if (PantallaCompleta) { Usamos ChangeDisplay(NULL,0) para volver a nuestro escritorio original. Con estos parmetros forzamos a que Windows restablezca del registro la resolucin por defecto, bits por pixel, frecuencia, etc.... Despues de esto volvemos a mostrar el cursor del ratn. ChangeDisplay(NULL,0); ShowCursor(TRUE); } Comprobamos si disponemos de hRC, sino vamos directamente a comprobar el DC. if (hRC) { // Vemos si podemos eliminar el hRC (Eliminar el enlace entre hDC y hRC) if (wglMakeCurrent(NULL,NULL)) { // Si no podemos eliminarlo mostramos un mensaje de error MessageBox(NULL,"Fallo en la eliminacin de DC y RC","ERROR",MB_OK | MB_ICONINFORMATION); } // Intentamos eliminar el RC. Si falla mostramos un mensaje de error. if (!wglDeleteContext(hRC)) { MessageBox(NULL,"Fallo en la eliminacin RC","ERROR",MB_OK | MB_ICONINFORMATION); } hRC=NULL; } Ahora comprobamos si nuestro programa dispone de DC y si dispone de el intentamos eliminarlo. Si no podemos eliminarlo mostramos un mensaje de error y ponemos hDC a NULL. if (hDC && !ReleaseDC(hWnd,hDC)) { MessageBox(NULL,"Fallo en la eliminacin del DC","ERROR",MB_OK | MB_ICONINFORMATION); hDC=NULL; } La siguiente comprobacin es ver si disponemos de puntero a la ventana hWnd. Si no conseguimos eliminarlo mostramos un mensaje y lo ponemos a NULL. if (hWnd && !DestroyWindow(hWnd))

{ MessageBox(NULL,"Fallo en la eliminacin del hWnd","ERROR",MB_OK | MB_ICONINFORMATION); hWnd=NULL; } Lo ltimo que nos queda por hacer es desregistrar nuestra clase Ventana. Esto nos permite eliminar correctamente la ventana y volver a abrir otra ventana de nuevo sin recibir el mensaje de error de "Windows Class already registered". if (!UnregisterClass("OpenGL",hInstance) { MessageBox(NULL,"Fallo al desregistrar la clase","ERROR",MB_OK | MB_ICONINFORMATION); hInstance=NULL; }

Inicializa EscenaGL
Con esta funcin inicializamos el marco de visin y asignamos la perspectiva a nuestra escena GL. Creada en la ventana de dimensiones ancho y alto (parmetros). GLvoid InicializaEscenaGL (GLsizei ancho, GLsizei alto) { if (alto==0) // Hacemos esta comprobacin para evitar la divisin entre 0 { alto=1; } glViewport(0,0,ancho,alto); // Establecemos el nuevo marco de visin. Ahora asignamos la perspectiva que queremos darle a la escena. Vamos a asignar un angulo de visin de 45 y una distancia de visin de 0.1f para el plano cercano y 100.0f para el lejano. Esto es el plano mnimo y mximo respectivamente dentro del cual los objetos son visibles, fuera de aqui ya no son visibles. Los comandos glMatrixMode() seleccionan la matriz actual sobre la que vamos a operar. Aqui seleccionamos primero la matriz de proyeccin ya que esta es la encargada de darle perspectiva a la escena. Una vez seleccionada la reseteamos utilizando glLoadIdentity() y de este modo le asignamos el estado inicial (Matriz identidad). A lo mejor puede parecer algo confusos estos comandos. Para ms informacin y profundizar ms puede mirar el Apendice Tutorial 1: Las proyecciones. glMatrixMode(GL_PROJECTION); // Seleccionamos la matriz de proyeccin glLoadIdentity(); // Resetamos la matriz de proyeccin gluPerspective(45.0f,(GLfloat)ancho/(GLfloat)alto,0.1f,100.0f); Establecemos la perspectiva de la escena glMatrixMode(GL_MODELVIEW); // Seleccionamos la matriz de modelado glLoadIdentity(); // Reseteamos la matriz de modelado }

//

InicializaGL
Aqui es donde inicializamos los estados de OpenGL referentes al color de fondo, luces, modo de dibujo de polgonos,

modo de suavizado... int InicializaGL(GLvoid) { Lo primero que hacemos es activar el sombreado GL_SMOOTH que hace que el color se distribuya en los polgonos de manera "suave" as como los efectos de luz. glShadeModel(GL_SMOOTH); // Activamos sombreado suave Ahora establecemos el color de fondo cuando se borra la pantalla. Estos valores corresponden con el esquema RGBA y pueden estar comprendidos en el intervalo 0.0-1.0. glClearColor(0.0f,0.0f,0.0f,0.0f); // Ponemos el fondo negro Con las siguientes lineas configuramos el Buffer de profundidad (Z-Buffer) que es muy importante en OpenGL. Podemos asociarlo como si fueran capas de la pantalla en las que se encuentran puntos de nuestra escena y gracias a estos podemos ordenar estos puntos con respecto a su profundiad glClearDepth(1.0f); glEnable(GL_DEPTH_TEST); // Activamos el test de profundidad glDepthFunc(GL_LEQUAL); // Seleccionamos el tipo de test a realizar A continuacin establecemos la correccin de perspectiva. Hemos seleccionado la mejor correcin, la que produce los efectos ms reales, pero como es lgico tambien es ms lenta que las dems. glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST); // Correccin de perspectiva muy buena Para finalizar devolvemos TRUE ya que la inicializacin que hemos llevado a cabo no era nada complicada. Tambien se poda haber puesto algun control de errores para comprobar que todo era correcto y si no fuera as devolver FALSE y el programa terminara. return TRUE; }

El programa de ejemplo
Creando un nuevo proyecto
Para crear el programa de ejemplo se ha usado Visual C++ 6.0 aunque no debe de haber problemas para compilarlo con versiones anteriores (Si surgiera alguna duda contacta conmigo). Para comenzar se crea un proyecto Win32 Application vacia. Posteriormente se aaden los ficheros de la clase CGL (CGL.H y CGL.CPP) y el fichero principal (PRINCIPAL.CPP). Despues de hacer esto debemos incluir los ficheros de librera OpenGL32.lib, GLU32.lib y GLAux.lib dentro de Project->Setting->Link (despues de Kernel32.lib) para que el compilador pueda enlazarlos.

Aclaraciones sobre la clase CGL


Hay algunos conceptos que hay que aclarar con respecto al programa de ejemplo. El primero es el uso de la funcin GestorMensajes dentro de la clase CGL estando esta implementada en el mdulo PRINCIPAL.CPP. Para poder usarla he optado por la forma ms facil, cmoda y "fea" y no es otra que utilizando un extern dentro del mdulo de definicin de la clase (CGL.H) de modo que quedara lo siguiente: extern LRESULT CALLBACK GestorMensajes(HWND, UINT, WPARAM, LPARAM); Justamente antes de la declaracin de la clase CGL. De este modo podremos utilizarla en nuestra clase. Vuelvo a repetir que no sera la opcin que yo elegira para implementar esta clase para mi uso personal pero no me pareca muy correcto meterme con Clases derivadas, punteros a funciones, etc... siendo este el "primer" tutorial sobre OpenGL, ya que la gente podra venirse abajo :(. Pero prometo hacer otro tutorial ms adelante con una implementacin ms robusta y flexible.

Modificaciones del mdulo PRINCIPAL.CPP


Al mdulo PRINCIPAL.CPP explicado en el tutorial anterior sobre Introduccin a la Programacin en Windows le hemos aplicado algunas modificaciones para que se adapte a nuestras necesidades actuales. Para comenzar los primero que haremos ser definir nuevas variables globales (A continuacin se explicar su utilidad): bool bTeclas[256]; // Usado para el control del teclado bool bActiva; // Nos permite saber cuando est activa o no nuestra aplicacin CGL *cObjetoGL; // Es una instancia de nuestro clase CGL :) Tambien debemos de incluir el fichero de cabecera donde se encuentra la declaracion de nuestra clase CGL #include <CGL.H> * NUEVA FUNCIN: int RenderizaGL (GLvoid) Esta es la funcin a la que llamaremos desde nuestro bucle principal. Sencillamente borra la pantalla con el color que tengamos declarado como color de fondo (glClearColor()), y elimina tambien el buffer de profundidad (Depth Buffer). Despus devuelve el control al bucle principal. int Renderiza(GLvoid) { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Limpia la pantalla y el buffer de profundidad glLoadIdentity(); // Resetea la matriz actual de modelado return TRUE; // Todo ha ido bien as que volvemos } * CONTROL DEL TECLADO

Para comenzar hemos incluido la posibilidad de controlar el teclado, sin usar ninguna API (ms adelante veremos como hacerlo usando algunas de las API ms conocidas). Para este fin hemos definido una variable global: bool bTeclas[256]; Esta variable nos informa si una tecla est pulsada o no. Para controlar esta pulsacin hemos utilizado los mensajes WM_KEYDOWN y WM_KEYUP del Gestor de mensajes. Cuando estos mensajes se activan indican que la tecla de cdigo wparam se ha pulsado o ha dejado de estar pulsada respectivamente. Por lo tanto dentro de nuestro Gestor de mensajes incluiremos las siguientes lineas: case WM_KEYDOWN: { bTeclas[wParam]=TRUE; return 0; } case WM_KEYUP: { bTeclas[wParam]=FALSE; return 0; } * OTRAS MODIFICACIONES EN EL GESTOR DE MENSAJES Hemos incluido otras modificaciones en el gestor de mensajes para tratar algunos mensajes que nos sern de utilidad Estos son: Interceptamos mensajes del siste case WM_SYSCOMMAND: { switch (wParam) { case SC_SCREENSAVE; pantalla case SC_MONITORSAVE; monitor (ahorro de energa) return 0; } break; }

// Comprobamos que tipo de llamada es // Evitamos que se active el protector de // Evitamos que se active el modo sleep del

Cuando se cambia el tamao de la ventana case WM_SIZE: { // Inicializamos la nueva escena (marco de visin y perspectiva ObjetoGL->InicializaEscenaGL(LOWORD(lParam),HIWORD(lParam)); return 0; }

Para controlar cuando est activa nuestra ventana case WM_ACTIVATE: {

if (!HIWORD(wParam)) ventana { bActiva=TRUE; } else { bActiva=FALSE; } return 0; }

// Comprobamos si se ha minimizado la

* MODIFICACIONES DEL A FUNCION WinMain En esta funcin las modificaciones que vamos a hacer son basicamente inicializar el objeto CGL, llamar a la funcin RenderizaGL y para finalizar destruir el objeto. Dentro de este mdulo vamos a declarar unas variables: BOOL bPCompleta; // Estamos en modo pantalla completa? BOOL bAplicacionFinalizada; // Ha finalizado la aplicacin? Inicializar el Objeto GL Para inicializar el objeto ObjetoGL vamos a preguntar al usuario si desea una ventana o en pantalla completa. Para esto utilizamos un cuadro de mensajes (MessageBox): if (MessageBox(NULL,"Ejecutar en modo pantalla completa?", "Mi primera ventana OpenGL",MB_YESNO|MB_ICONQUESTION)==IDNO) { bPCompleta=FALSE; // Modo ventana } A continuacin creamos la ventana y comprobamos si se ha creado bien, sino ha sido as mostramos un mensaje y salimos: ObjetoGL = new CGL; if (!ObjetoGL.CreaVentanaGL("Mi programa Base",640,480,16,bPCompleta)) { return 0; // Salir si la ventana no se ha creado } Bucle principal El bucle principal ha sido levemente modificado y es el siguiente: while(!bAplicacionFinalizada) // Bucle que se ejecuta mientras bAplicacionFinalizada=FALSE { if (PeekMessage(&msg,NULL,0,0,PM_REMOVE)) // Hay un mensaje esperando? { if (msg.message==WM_QUIT) // Hemos recibido el mansaje de SALIDA? { bAplicacionFinalizada=TRUE; // Si es, bAplicacionFinalizada=TRUE } else // Sino lo pasamas al gestor de mensajes

{ TranslateMessage(&msg); DispatchMessage(&msg); } } else { // Dibujamos la escena. if (bActiva && bTeclas[VK_ESCAPE]) o est minimizada? { bAplicacionFinalizada=TRUE; } else actualizamos la pantalla { Renderiza(); SwapBuffers(ObjetoGL.ObtHDC()); (Double Buffering) } } } Salir de la aplicacin Cuando salgamos de nuestro programa, antes debemos de eliminar nuestra ventana OpenGL: ObjetoGL->EliminaVentanaGL(); delete ObjetoGL; // Sino hay mensajes // Se ha pulsado la tecla ESC // Salimos // Sino tenemos que salir, // Dibujamos la pantalla // Intercambiamos los buffers // Traducimos el mensaje // Lo procesamos

Transformaciones geomtricas
Introduccin
En el tutorial 4 aprendimos como crear un tringulo. En este tutorial aprenderemos como aplicarle las tres transformaciones bsicas: traslacin, escalado y rotacin. No es mi intencin hacer un manual sobre geometra 3D asi que me limitar a comentar las funciones que dispone OpenGL, si hubiera alguna duda, no dudes en ponerte en contacto conmigo. La estructura de este fichero es la siguiente: * Creando un cuadrado * Aplicandole transformaciones * Control de teclado * Nuestro tringulo utilizando clases * Ejecutable y Cdigo fuente * Conclusiones

Creando un cuadrado
En este ejemplo vamos a crear un cuadrado que ser al que le apliquemos las transformaciones. Para esto utilizaremos dos triangulos unidos ya que, como ya dije en el tutorial anterior, es ms recomendable usar tringulos que otro tipo de primitiva debido a que es lo que las aceleradores grficas suelen renderizar ms rpido. Tambien es verdad que se podra mejorar an ms nuestra aplicacin haciendo que nuestro cuadrado en vez de tener dos tringulos (GL_TRIANGLE) que son 6 vrtices en total, utilice la primitiva GL_TRIANGLE_STRIP y necesitara solamente 4. Esta mejora la implanteremos en prximos tutoriales. Por ahora lo haremos as :P Nuestro cuadrado ser el siguiente:

Para esto debemos de modificar declarar una variable de la clase CTriangulo de la forma:

CTriangulo *Cuadrado=new CTriangulo[2]; A continuacin, dentro del WinMain(), lo inicializamos: // Inicializamos el Cuadrado // Triangulo 1 Cuadrado[0].m_cVertices[0].m_cPunto.Inicializa(-0.5f,-0.5f,0.0f); Cuadrado[0].m_cVertices[0].Inicializa_Color(1.0f,0.0f,0.0f); Cuadrado[0].m_cVertices[1].m_cPunto.Inicializa(-0.5f,0.5f,0.0f); Cuadrado[0].m_cVertices[1].Inicializa_Color(1.0f,1.0f,0.0f); Cuadrado[0].m_cVertices[2].m_cPunto.Inicializa(0.5f,0.5f,0.0f); Cuadrado[0].m_cVertices[2].Inicializa_Color(0.0f,0.0f,1.0f); // Triangulo 2 Cuadrado[1].m_cVertices[0].m_cPunto.Inicializa(-0.5f,-0.5f,0.0f); Cuadrado[1].m_cVertices[0].Inicializa_Color(1.0f,0.0f,0.0f); Cuadrado[1].m_cVertices[1].m_cPunto.Inicializa(0.5f,0.5f,0.0f); Cuadrado[1].m_cVertices[1].Inicializa_Color(0.0f,0.0f,1.0f); Cuadrado[1].m_cVertices[2].m_cPunto.Inicializa(0.5f,-0.5f,0.0f); Cuadrado[1].m_cVertices[2].Inicializa_Color(1.0f,1.0f,0.0f); IMPORTANTE: No debemos de olvidar que cuando terminemos la aplicacin debemos de liberar la memoria consumida por las variables tanto ObjetoGL como Cuadrado. Esto se hace mediante: delete ObjetoGL; delete [] Cuadrado;

Aplicandole transformaciones
En el ejemplo vamos a aplicar transformaciones de rotacion y traslacin. La de escalado no la vamos a utilizar principalmente para evitar "malos hbitos" debido a que este comando es MUY lento y el uso continuo del mismo hace descender nuestro queridos FPS. Para las transformaciones geomtricas, OpenGL utiliza, al igual que D3D, matrices de 4x4. Aunque al contrario que D3D, OGL no se apoya directamente en la notacin matemtica y en su lugar, utiliza la traspuesta de la matemtica. Veamos un ejemplo:

Los comandos de traslacin, rotacin y escalado vienen explicados en Apendice 1: Visualizacin as que aqui me limitar a nombrarlos.

Traslacin
void glTranslate{fd}(TYPE x, TYPE y, TYPE z); Multiplica la matriz actual por una matriz de traslacin que mueve el objeto.s

Rotacin
void glRotate{fd}(TYPE angle,TYPE x, TYPE y, TYPE z); Multiplica la matriz actual por una matriz de rotacin que rota el objeto en la direccin de las agujas del relojs sobre el radio de origen en el punto x,y,z.

Escalado
void glScale{fd}(TYPE x, TYPE y, TYPE z); Multiplica la matriz actual por una matriz que cambia el tamao del objeto a lo largo de los ejes. Las coordenadas x, y, z de cada objeto es multiplicada por los parmetros x, y, z. C Como dijimos anteriormente en nuestro ejemplo solamente aplicaremos las transformaciones de rotacin y traslacin. Para poder mover y rotar el cuadrado, vamos a declarar unas variables globales: float fRotx,fRoty; float fDesz=-3.0f; Las dos primeras son los ngulos que vamos a rotar con respecto al eje x e y respectivamente. La tercera variable corresponde con el desplazamiento a travez del eje z (perpendicular a la pantalla). Grficamente sera:

Ahora debemos cambiar nuestra funcin Renderiza() para conseguir el efecto deseado: int Renderiza(GLvoid) { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Borramos la pantalla y el buffer de profundidad glLoadIdentity(); glTranslatef(0.0f,0.0f,fDesz); glRotatef(fRotx,1.0f,0.0f,0.0f); glRotatef(fRoty,0.0f,1.0f,0.0f); // Trasladamos a lo largo del eje Z // Rotamos alrededor del eje X // Rotamos alrededor del eje Y

// I

// Ahora dibujamos el cuadradoglBegin(GL_TRIANGLES); for (int j=0;j<2;j++) { for (int i=0;i<3;i++) { glColor3fv( Cuadrado[j].m_cVertices[i].m_fColor); glVertex3f( Cuadrado[j].m_cVertices[i].m_cPunto.m_fX, Cuadrado[j].m_cVertices[i].m_cPunto.m_fY, Cuadrado[j].m_cVertices[i].m_cPunto.m_fZ); } } glEnd(); return TRUE; }

Control del teclado


Ahora solamente nos queda controlar el valor de las variables. Para eso utilizaremos el teclado: Tecla Derecha Izquierda Arriba Abajo + Efecto fRotX+ fRotXfRotY+ fRotYfDesZ+ fDesZ-

Para controlar la entrada hemos creado una nueva funcin, para dejar ms clara y legible la funcin WinMain(): void Control_Entrada() { if (bTeclas[VK_LEFT]) fRoty-=0.5f; if (bTeclas[VK_RIGHT]) fRoty+=0.5f; if (bTeclas[VK_UP])

fRotx+=0.5f; if (bTeclas[VK_DOWN]) fRotx-=0.5f; if (bTeclas[VK_ADD]) fDesz+=0.1f; if (bTeclas[VK_SUBTRACT]) fDesz-=0.1f; }; La llamada a esta funcin se realzar en el bucle principal despus de llamar a Renderiza(): if (bActiva && bTeclas[VK_ESCAPE]) // Se ha pulsado la tecla ESC o se ha minimizado? { bAplicacionFinalizada=TRUE; // Salimos } else // Sino tenemos que salir actualizamos la pantalla { Renderiza(); // Dibujamos la pantalla SwapBuffers(ObjetoGL->ObtHDC()); // Intercambiamos los buffer (Double buffering) Control_Entrada(); <-- MODIFICADO }

También podría gustarte