 La libreria grafica que vamos a usar es OpenGL. Ahora comenzamos a acostumbrarnos a su uso y entender como funciona.
Ya explique porque usaremos OpenGL. Ahora una breve explicacion para aclarar algun tema que suele ser confuso cuando se empieza con esta libreria grafica. Lo normal es pensar que hace falta bajarse unas librerias para programar con OpenGL y un "run time" para ejecutar los programas hechos con ella. Con DirectX si hace falta y ocupa lo suyo; pero con OpenGL no hay "run time" y solo hacen falta dos pequeñas librerias que vienen con los compiladores (en realidad solo haria falta una). En realidad el nucleo de OpenGL instalado en tu ordenador, esta en el driver grafico de tu tarjera de video y es el fabricante de la misma quien la implementa para su hardware. La libreria que viene con el compilador es solo un interface para usar las rutinas del fabricante, y este interface incluye un metodo para ampliar el OpenGL para futuras mejoras de la libreria grafica (para usar OpenGL 1.0 se usa la misma libreria que para usar OpenGL 2.0) es lo que se llaman extensiones. Bastante mas adelante las veremos.
En resumen: no hace falta bajarse OpenGL de ningun sitio, y para hacer nuestro programa solo necesitaremos incluir "gl/gh.h" y "gl/glu.h" y añadir a nuestro proyecto las librerias opengl32.lib y glu32.lib. Para que nuestro programa funcione convendra tener bien instalado el driver de la tarjeta grafica, aunque al usar caracteristicas basicas de OpenGL va a funcionar siempre ya que windows lo puede ejecutar por software (si usamos caracteristicas avanzadas propias de nuestro hardware de video y no tenemos los driver, fallara).
Aunque este curso es eminentemente practico, no esta de mas la explicacion de algunos conceptos basicos en programacion grafica.
El buffer. No solo en graficos, sino en programacion general, es un espacio de memoria reservado para almacenar algo. OpenGL usa varios bufferes. Los primeros y mas importantes son los que se usan para dibujar y el que se usa para presentar en pantalla.
Se usa la tecnica de doble buffer (pueden ser mas). Entender esto es la base para empezar a saber como trabaja un juego. ¡ Esto hay que entenderlo !.
En la tecnica de boble buffer, dibujamos en un buffer que no es visible para que no se aprecie como se va haciendo nuestro dibujo y mientras presentamos el dibujo anterior. Cuando hemos acabado de dibujar suicheamos (intercambiamos) un buffer por otro, y asi se presenta el dibujo instantaneamente. Despues seguimos dibujando en siguiente fotograma (frame), pero esta vez lo estaremos haciendo en el otro buffer, el que ahora esta oculto (back buffer), mientras el jugador esta viendo en pantalla el fotograma que hemos dibujado antes. Estos bufferes son de memoria de video (la de la tarjeta de video, no memoria RAM del PC). El sistema no mueve la informacion de un buffer a otro, si no que, hace que la tarjeta de video cambie el rango de memoria de video que se traspasa al monitor, y el rango de memoria en la que esta el back buffer (el que se presenta en pantalla se llama front buffer). Ejemplo: Tenemos dos bufferes, el 1 y el 2 (por ejemplo). Que siempre van a ser el mismo trozo de memoria cada uno:
- Ahora el 1 es el back y el 2 es el front. En el 1 pintamos mientras se ve el 2 en el monitor.
- Intercambamos bufferes (SwapBuffers(DevContex))
- Ahora el 2 es el back y el 1 es el front. En el 2 pintamos mientras se ve el 1 en el monitor con el dibujo anterior.
- Intercambamos bufferes (SwapBuffers(DevContex))
- Ahora el 1 es el back y el 2 es el front. En el 1 pintamos mientras se ve el 2 en el monitor.
- ...
Por si hace falta la aclaracion, la animacion en un juego -y en una pelicula- se hace presentando un dibujo tras otro con pequeñas variaciones en el mismo. Siempre deben ser mas de 24 dibujos segundo para que el ojo no perciba el cambio y el movimiento sea suave.
Otro buffer importante es el de profundidad o z-buffer, muuuuuy importante si pretendemos hacer algo en 3D. En el se guarda la profundidad de cada pixel al pintarlo (distancia de lo que se pinta a la camara). Cuando se pinta de nuevo el mismo pixel de pantalla (con otro objeto) se compara la profundidad de el ultimo con la que estaba guardada de antes, si es mayor la nueva (esta mas lejos), no se pinta el pixel, y si es menor (esta mas cerca), se pinta el pixel del color nuevo en el buffer de dibujo (back buffer) y se pone la nueva profundidad -menor- en el buffer de profundidad.
Hay mas bufferes como el de estarcido (stencil) o el de acumulacion pero de momento no nos hacen falta.
Tras la explicacion, el codigo que creo que tiene suficientes comentarios para seguirlo facilmente. En esta segunda version he puesto en funciones aparte la creacion de la ventana y la inicializacion de OpenGL.
#pragma comment( lib, "openGL32.lib" ) // Estas directivas del preprocesador #pragma comment( lib, "glu32.lib" ) // le dicen a el enlazador (linker o // linkador o ...) que incluya estas // librerias. Esto se puede hacer en // la configuracion del proyecto en el // Visual C++ pero esto me parece mas // claro. #include <windows.h> // Fichero de inclusion para programas Windows #include <GL/gl.h> // Los ficheros de inclusion de OpenGL. #include <GL/glu.h> // Estrictamente solo es necesario el primero. // El segundo es de funciones utiles que se podrian // hacer de otra manera. No nos vamos a complicar la // vida y la usaremos. HINSTANCE IdAplicacion; // Para guardar el identificador del programa HWND IdVentana; // Para guardar el identificador de la ventana HDC DevContex; // Device Context para conectar la ventana con OpenGL. HGLRC OGLrc; // Render Context para la ventana OpenGL. int AnchoVentana = 600; // Lo que dice el nombre de la variable int AltoVentana = 400; // Lo que dice el nombre de la variable //-------------------------------------------------------------- // Funcion para inicializar OpenGL. void IniciaGL() { // Definimos una estructura de tipo PIXELFORMATDESCRIPTOR para definir // las caracteristicas graficas que queremos usar (dentro de las que nos // permite el OpenGL de nuestra tarjeta de video) static PIXELFORMATDESCRIPTOR pfd = { sizeof(PIXELFORMATDESCRIPTOR), // Tamaño de este descriptor 1, // Número de versión 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, // También debe soportar RGBA 32, // 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 acumulación ignorado 0,0,0,0, // Bits de acumulación ignorados 32, // Z-Buffer de 32 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 máscara ignorados }; // Estas funciones son las que hacen que la ventana acepte el modo grafico // que queremos dado el descriptor de pixel anterior. (en el que uno de // los parametros es que funcione con OpenGL). DevContex=GetDC(IdVentana); // Obtengo el "device context" de la ventana. int PixF=ChoosePixelFormat(DevContex,&pfd); // Busco un indice de una conbinacion // que coincida con mis especificaciones SetPixelFormat(DevContex,PixF,&pfd); // Pongo la ventana en el formato que quiero. // Dos funciones de OpenGL por fin (aunque sean especificas de windows). OGLrc=wglCreateContext(DevContex); // Indico a OpenGL que la ventana esta disponible para el. wglMakeCurrent(DevContex,OGLrc); // Le digo a OpenGL que nuestra ventana es donde tiene // que dibujar a partir de ahora. // A partir de ahora podemos considerar OpenGL inicializado. // Aun nos queda decirle los parametros del modo en que dibujar, aunque // eso se puede, y de hecho se hace continuamente, cambiar durante la // ejecucion del programa. // Ahora pondremos algunos parametros puramente de OpenGL de inicio. glClearDepth(1.0f); // Profundidad del buffer de profundidad. Hace que lo que esta // mas cerca se vea (dibuje) encima de lo que esta mas lejos. glDepthFunc(GL_LEQUAL); // Comparacion del buffer de profundidad. glEnable(GL_DEPTH_TEST); // Habilita test de profundidad. A partir de ahora, lo que // esta mas cerca se pinta encima. glClearColor(0,0,0,1.0f); // Color del fondo. Color con el que se borra la pantalla, o la // ventana donde pintamos. Cuando usemos la funcion glClear. glShadeModel(GL_SMOOTH); // Renderizado suave. Cuanta mejor calidad mas lento. Nosotros // tenemos un ordenador reciente(+ o -) y queremos que se vea bien. glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST); // Calidad buena de perspectiva. glViewport(0,0,AnchoVentana,AltoVentana); // Le dice a OpenGL el tamaño del lienzo en el que va a pintar en // pixels. Coincidira, en principio, con el tamaño de nuestra ventana // y empezando por 0,0 (la esquina de nuestra ventana). glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); // Borramos la ventana con el color antes establecido // (en realidad solo el backbuffer (la pantalla oculta // donde en realidad estamos pintando) // y borramos tambien el buffer de profundidad SwapBuffers(DevContex); // Le digo a nuestro espacio de dibujo que cambie "muy rapido" // el buffer de dibujo por el de visualizacion y viceversa // (suichear en Espanglish) } // Fin IniciaGL //------------------------------------------------------------------ // Funcion para controlar lo que ocurre en la ventana segun los eventos // que vienen de Windows. Mas tarde se asocia a la ventana que crearemos. // De momento solo interceptamos la tecla ESC para poder salir del // programa cuando queramos y el evento de destruccion de ventana con el // que terminamos la aplicacion LRESULT FAR PASCAL ProcesaMensajes(HWND Ventana, UINT Mensaje, WPARAM wParam, LPARAM lParam) { switch(Mensaje) // Según el mensaje recibido { case WM_KEYDOWN: // Caso de mensaje de pulsación de una tecla switch(wParam) // y segun el contenido de la variable wParam { case VK_ESCAPE: // ESC causa la salida del programa // Funcion para enviar mensaje de cerrado a la ventana // y despues de la aplicacion PostMessage(Ventana, WM_CLOSE, 0, 0); break; } break; case WM_DESTROY:// Mensaje de destrucción de la ventana (provocado por // nosotros al pulsar ESC o cerrar la ventana. PostQuitMessage(0); // Funcion para salir del programa break; } // Efectuar el proceso por defecto del mensaje (si viene cualquier mensaje // que no hemos usado, querremos que haga lo que suele hacer) return DefWindowProc(Ventana, Mensaje, wParam, lParam); } // fin ProcesaMansajes //------------------------------------------------------------------ // Funcion para crear la ventana de nuestro programa, que asociaremos // al OpenGL para pintar en ella void CreaVentana() { WNDCLASS ClaseVentana; // Declaramos un tipo de ventana, el nuestro. //Definimos nuestro tipo de ventana... ClaseVentana.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; // Se redibujara si // cambia el tamaño de la ventana horizontal y verticalmente, y // un solo DC (device context) para cada ventana (para cuando // empezemos con OpenGL) ClaseVentana.lpfnWndProc = ProcesaMensajes; // La definimos antes para contolar // los mansajes como los del teclado ClaseVentana.cbClsExtra = 0; //Sin memoria extra para la clase ClaseVentana.cbWndExtra = 0; //Sin memoria extra para la ventana ClaseVentana.hInstance = IdAplicacion; // Identificador del programa para asociar // esta ventana con este programa. ClaseVentana.hIcon = NULL; // De momento pasamos de icono ClaseVentana.hCursor = LoadCursor(NULL, IDC_ARROW); // Flecha normal de raton ClaseVentana.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); // Por ahora lo ponemos blanco ClaseVentana.lpszMenuName = NULL; // Pasamos de menu ClaseVentana.lpszClassName = L"UnSitioWeb"; // Nombre de la clase (la "L" antes de la cadena es // por que solo admite cadenas de caracteres // unicode -LPCWSTR o TCHAR- y con la "L" se convierte). RegisterClass(&ClaseVentana); // Registramos nuestro tipo de ventana IdVentana = CreateWindowEx( // Funcion que crea la ventana. Guardamos el identificativo. WS_EX_APPWINDOW, // estilo extendido de ventana L"UnSitioWeb", // Nombre de la clase puesto antes (la "L" para UNICODE) L"Un Sitio Web", // Titulo que aparecera en la ventana. WS_POPUPWINDOW|WS_CAPTION|WS_MINIMIZEBOX, // Parametros de como sera la ventana // se pueden combinar varios. 100, // Posicion Horizontal. 100, // Posicion Vertical. AnchoVentana, // Ancho de la ventana. AltoVentana, // Alto de la ventana. (HWND) NULL, // No depende de otra ventana. (HMENU) NULL, // No le damos un menu diferente al de la clase(que es ninguno) IdAplicacion, // Identificador del programa al que pertenece la ventana. Al // empezar lo guardamos en una variable para usarlo despues. (LPVOID) NULL ); // Puntero a "no se que" datos (pasamos del tema, es para // aplicaciones MIDI). ShowWindow(IdVentana, SW_SHOW); // Mostramos nuestra ventana. UpdateWindow(IdVentana); // La actualizo para que muestre lo que tenga que mostrar // (ahora nada)
} // Fin CreaVentana //------------------------------------------------------------------ // Funcion principal de un programa Windows (como main en C normal, // aqui WinMain) el prorama empieza a ejecutarse a partir de esta funcion. int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { UNREFERENCED_PARAMETER(lpszCmdLine); // Para que no moleste al compilar, no lo usamos. IdAplicacion = hInstance; // Guardo el identificador del programa, luego lo usamos. CreaVentana(); // Funcion que crea la ventana. Definida mas arriba. IniciaGL(); // Funcion para inicializar OpenGL. Definida mas arriba. // Este es el bucle habitual de windows que se esta ejecutando // continuamente hasta que recibe el mensaje de acabar (ya lo hemos // preparado en la funcion "ProcesaMensajes" asociada a // la ventana, tambien asociada a este programa atraves de su // identificativo) MSG Mensaje; // Varible para contener los mensajes que van llegando. while(TRUE) // Se ejecuta continuamente. { if(PeekMessage(&Mensaje, NULL, 0, 0, PM_NOREMOVE)) // Exploramos la cola de mensajes. {// procesándolos adecuadamente if(!GetMessage(&Mensaje, NULL, 0, 0)) return (int)Mensaje.wParam; // Terminamos. TranslateMessage(&Mensaje); DispatchMessage(&Mensaje); } else WaitMessage(); // en caso contrario esperamos un mensaje } } // fin WinMain //------------------------------------------------------------------
Una ultima explicacion para los que investiguen por internet a cerca de programacion OpenGL. Existe una libreria muy usada, y la mas habitual en los ejemplos de codigo, llamada glut que simplifica el tema del teclado, inicializacion y demas aspectos del programa base.
Yo no la uso, tel vez por mania o por cabezoneria, pero prefiero gestionar yo mismo estos temas ya que no me parece tan complicado, como habeis visto, y te da mayor control sobre tu programa. En todo caso es una decision personal y el funcionamiento de OpenGL es el mismo en los dos casos.
Suerte compilando.
|