Etiquetas

C (31) Cpp (28) Linux (14) asm (8) Telegram (5) bot (5) libreria (5) Algoritmo (3) Errores comunes (3) python (3) Opengl (2) kali (2) Android (1) Snippet (1) nano (1) recursividad (1)

domingo, 10 de diciembre de 2023

OpenGL en Dev++

/*Escrito el 6-agosto-2006*/
/*Este tutorial aborda la creación de una ventana en Windows
con un contexto gráfico OpenGL, pero a diferencia de los
tutoriales basados en GLUT o AUX, esta aplicación se
desarrollará a partir de las librerías de Windows, haciendo
todo “manualmente”. Surge la inquietud de ¿por qué hacer este
tutorial, si podría hacerse más fácilmente con GLUT, AUX o
cualquier otra librería?, la respuesta es porque este
tutorial permite conocer mejor la estructura de un programa de
Windows, que es útil si se necesita algo mas de control (y
problemas) sobre la aplicación.
Además, es divertido “ensuciarse” un poco las manos con el API
de Windows de bajo nivel.

PROGRAMANDO ORIENTADO A EVENTOS
Windows y en general los sistemas operativos GUI utilizan una
arquitectura orientada a eventos para manejar la interacción
usuario – sistema – aplicación de manera organizada y
eficiente. El modelo es bastante simple, cada vez que el
usuario realiza una acción, el sistema determina sobre que
aplicación se realizó y le envía un mensaje describiendo la
acción, el programa lee y procesa el mensaje, si es que este
le interesa (un clic sobre el menú por ejemplo) y realiza los
cambios pertinentes.
Un ejemplo con un clic podría ser como este*/

IMAGEN

/*En este caso la aplicación, por ejemplo un juego de
estrategia, averiguaría la posición en la que en la que se
encontraba el puntero cuando se hizo el clic y movería una
unidad a esta posición. Es importante anotar que este modelo
fue creado para ser eficiente en uso de recursos cuando
varias aplicaciones corren “simultáneamente”, un videojuego
por el contrario es una aplicación destinada a consumir
recursos por su naturaleza de aplicación en “tiempo real”, la
razón de esto es que aunque el usuario se tome una siesta, el
videojuego está realizando cálculos, reproduciendo sonidos,
dibujando la pantalla, la IA esta intentando conquistar el
mundo, etc.

LA APLICACION
Las siguientes estructuras y tipos de datos serán usados en
el desarrollo de la aplicación:

HDC: Handle to Device context. Es el identificador de un
Device Context. Un Device Context es una aplicación a través
de la cual tienen que pasar las operaciones de dibujo, para
que estas puedan ser mostradas en un dispositivo físico, en
este caso particular un monitor.

PFD: Píxel Format Descriptor. Estructura que describe las
propiedades de los píxeles en una superficie de dibujado.

HWND: Handle to Window. Es el identificador de una ventana.

WNDCLASS: Estructura que contiene los datos necesarios para
el registro de una clase ventana, como son su nombre, estilo,
función de paso de mensajes, icono, cursor, entre otros.

HGLRC: Handle to GL Render Context. Es el identificador de un
Render Context, similar al HDC, solo que este no es usado por
GDI sino por OpenGL para realizar el dibujado sobre la pantalla.

Es posible dividir esta aplicación en 4 tareas principales:

Crear la ventana win32.
Preparar la ventana y ligarla a OpenGL.
Inicializar OpenGL.
Hacer el ciclo de mensajes y dibujado.

Las macros y variables del programa son:*/

#define VENTANA_ANCHO 640
#define VENTANA_ALTO  480
HDC     hdc=NULL;
HGLRC  hglrc=NULL;
HWND  hWnd=NULL;
HINSTANCE hInstance

/*CREAR LA VENTANA WIN32
Para crear la ventana es necesario llenar el WNDCLASS,
registrarlo y finalmente crear la ventana. El WNDCLASS */
    WNDCLASS    Cwnd;
    hInstance      = GetModuleHandle(NULL);
    Cwnd.style    = CS_OWNDC;
    Cwnd.lpfnWndProc    = (WNDPROC) WndProc;
    Cwnd.cbClsExtra       = 0;
    Cwnd.cbWndExtra     = 0;
    Cwnd.hInstance        = hInstance;
    Cwnd.hIcon    = NULL;
    Cwnd.hCursor            = LoadCursor(NULL, IDC_ARROW);
    Cwnd.hbrBackground   = NULL;
    Cwnd.lpszMenuName   = NULL;
    Cwnd.lpszClassName   = "OpenGL";
    RegisterClass(&Cwnd);

hWnd=CreateWindow("OpenGL","Hola Mundo",
WS_OVERLAPPEDWINDOW | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,100,
100,VENTANA_ANCHO,VENTANA_ALTO,NULL,NULL,hInstance,NULL);

/*
La función GetModuleHandle() obtiene un identificador para la
aplicación actual. Los datos de la estructura WNDCLASS son
los siguientes:

WNDCLASSS{
UINT style : Estilo de la aplicación, en este caso se quiere
un DC propio (CS_OWNDC)

WNDPROC lpfnWndProc : Manejador de mensajes
int cbClsExtra : Bytes extra para la estructura
int cbWndExtra : Bytes extra para la clase

HINSTANCE hInstance : Instancia de la aplicación que crea la
ventana

HICON hIcon : Icono de la ventana, al ser NULL, windows
provee el icono por defecto.

HCURSOR hCursor : Cursor usado al pasar sobre la ventana,
LoadCursor(NULL,IDI_ARROW) es el icono por defecto

HBRUSH hbrBackground : Color del fondo de la ventana,debido a
que todo el espacio de la ventana va a estar ocupado por la
ventana, este valor se provee como NULL

LPCTSTR lpszMenuName : Nombre del recurso que identifica el
menú de la aplicación, es NULL ya que no se utiliza menú.

LPCTSTR lpszClassName : Nombre de la clase que se usara al
registrarla.}

El manejador de mensajes al que hace referencia es en este
caso uno muy simple:*/

LRESULT CALLBACK WndProc
(HWND   hWnd,UINT uMsg,WPARAM wParam,LPARAM    lParam){
switch (uMsg){
case WM_CLOSE:
{PostQuitMessage(0);return 0;}}
return DefWindowProc(hWnd,uMsg,wParam,lParam);}

/*Las entradas de esta función son:

HWND hWnd : Identificador de la ventana que recibe el mensaje
UINT uMsg : El mensaje
WPARAM wParam : Información adicional sobre el mensaje
LPARAM lParam : Información adicional sobre el mensaje

Esta función solo intercepta un mensaje, aquel que es
enviado por el sistema para indicar a la aplicación que debe
cerrarse (WM_CLOSE) y si es cualquier otro mensaje lo envía
al DefWindowProc().
RegisterClass() registra una clase ventana, con los
parámetros dados por un WNDCLASS, en este caso CWnd.
Es hora de crear una ventana a partir de la clase creada con
el RegisterClass(), para esto se utiliza la función
CreateWindow() cuyos parámetros son los siguientes:

HWND CreateWindow(
LPCTSTR lpClassName : Nombre de la clase a partir de la cual
se crea la ventana
LPCTSTR lpWindowName : Titulo de la ventana.
DWORD dwStyle : Estilo de la ventana, en este caso con
titulo y borde (WS_OVERLAPED)
int x, : Posición x de la ventana
int y : Posición y de la ventana
int nWidth : Ancho de la ventana
int nHeight : Alto de la ventana
HWND hWndParent : Identificador de la ventana “padre”, en
este caso NULL, ya que no tiene HMENU menú : Identificador
del menú de la ventana, en este caso NULL, ya que no tiene
HINSTANCE hInstance : Identificador de la aplicación
poseedora de la ventana, en NT/2000/XP este valor es ignorado
LPVOID lpParam : Cadena con el parámetro a pasar al llamar
el mensaje WM_CREATE. En este NULL, ya que no hay ningun
parámetro a pasar)

 PREPARAR LA VENTANA Y LIGARLA A OPENGL.
Ahora es necesario:*/
GLuint  PixelFormat;
static  PIXELFORMATDESCRIPTOR pfd=
{sizeof(PIXELFORMATDESCRIPTOR),1,
PFD_DRAW_TO_WINDOW |PFD_SUPPORT_OPENGL |PFD_DOUBLEBUFFER,
PFD_TYPE_RGBA,32,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0};

    hdc=GetDC(hWnd);
    PixelFormat=ChoosePixelFormat(hdc,&pfd);
    SetPixelFormat(hdc,PixelFormat,&pfd);
    hglrc=wglCreateContext(hdc);
    wglMakeCurrent(hdc,hglrc);
/*
El PIXELFORMATDESCRIPTOR tiene los siguientes datos:

PIXELFORMATDESCRIPTOR {
WORD nSize : Tamaño de la estructura
WORD nVersion : Versión de la estructura, siempre es 1

DWORD dwFlags : Propiedades del buffer de píxeles, en este
caso dibujar sobre una ventana (PFD_DRAW_TO_WINDOW), soporte
a OpenGL (PFD_SUPPORT_OPENGL) y doble buffer (PFD_DOUBLEBUFFER)

BYTE iPixelType : La forma de describir los píxeles, en este
caso como RGBA (PFD_TYPE_RGBA)

BYTE cColorBits : Tamaño de un píxel individual en bits, en
este caso 32 que es el mas común
BYTE cRedBits : Ignorado
BYTE cRedShift : Ignorado
BYTE cGreenBits : Ignorado
BYTE cGreenShift : Ignorado
BYTE cBlueBits : Ignorado
BYTE cBlueShift : Ignorado
BYTE cAlphaBits : Ignorado
BYTE cAlphaShift : Ignorado
BYTE cAccumBits : Ignorado
BYTE cAccumRedBits : Ignorado
BYTE cAccumGreenBits : Ignorado
BYTE cAccumBlueBits : Ignorado
BYTE cAccumAlphaBits : Ignorado

BYTE cDepthBits : Bits para el buffer de profundidad, pueden
ser 16 o 32 como en este caso.

BYTE cStencilBits : Ignorado
BYTE cAuxBuffers : Ignorado
BYTE iLayerType : Ignorado
BYTE bReserved : Ignorado
DWORD dwLayerMask : Ignorado
DWORD dwVisibleMask : Ignorado
DWORD dwDamageMask : Ignorado
}

GetDC() obtiene el Device Context asociado a la ventana
identificada por hWnd.

ChoosePixelFormat() utiliza dos parámetros, el hdc sobre el
cual se buscara un formato de píxeles compatible y el pfd que
especifica el formato deseado por la aplicación. Esta función
devuelve el formato de píxeles mas cercano encontrado, con
respecto al pfd.

SetPixelFormat() utilizar tres parámetros, el hdc sobre el
cual se va a establecer el formato de píxeles, un entero, el
PixelFormat, donde esta guardado el índice a dicho formato de
píxeles y el pfd de referencia que se uso para encontrar el
formato, el ultimo valor no afecta el funcionamiento de esta
función, ya que se utiliza solamente como registro.

wglCreateContext() crea un contexto de dibujo para OpenGL, a
partir de un contexto de dibujo de GDI.

wglMakeCurrent() elige un HGLRC sobre el cual se va a dibujar
los OpenGL, esto es debido a que una aplicación puede tener
varios contextos de dibujo, pero solo puede operar un proceso
sobre un contexto a la vez.

INICIALIZAR OPENGL
La inicialización de OpenGL es en este caso bastante simple y
más bien utilitaria, es realizada por las siguientes líneas de
código:*/

glClearColor(0.0f, 0.0f, 0.0f, 0.5f);
glMatrixMode (GL_PROJECTION);
glViewport (0, 0, VENTANA_ANCHO , VENTANA_ALTO);
gluPerspective(45.0f,VENTANA_ANCHO/VENTANA_ALTO,0.1f,100.0f);
gluLookAt( 0,0,16,  0,0,0, 0,1,0);
glMatrixMode (GL_MODELVIEW);
glLoadIdentity();

/*
glClearColor() establece el color de fondo
glMatrixMode() escoge una matriz para operar sobre ella.
Primero se elige la de proyección (GL_PROJECTION)

glViewport() establece el tamaño del marco de dibujo de OpenGL

gluPerspective() establece el frustrum, en este caso apertura
de 45, aspecto de 640/480, plano mas visible a 0.1 de
distancia y mas lejano a 100.

gluLookAt() posiciona la "cámara" (en realidad hace
transformaciones sobre la matriz proyección. Los parámetros
están agrupado de a 3 (x, y, z) y representan la posición de
la camara, el punto al que mira y que vector es considerado
"hacia arriba"

Finalmente se vuelve al modo de matriz GL_MODELVIEW que es
el utilizado para dibujar las primitivas.

HACER EL CICLO DE MENSAJES Y DIBUJADO
El ciclo de mensajes se encarga de revisar constantemente los
mensajes, en busca de alguno de interés para la aplicación,
de no haber mensajes, se dibuja la escena de OpenGL. El
código es así
*/

MSG mensaje;
BOOL continuar=TRUE;
while(continuar){

if (PeekMessage(&mensaje,NULL,0,0,PM_REMOVE)){
if (mensaje.message==WM_QUIT){continuar=FALSE;}
else{
TranslateMessage(&mensaje);
DispatchMessage(&mensaje);
}}
else{
render();
SwapBuffers(hdc);
}}

/*
La variable continuar indica cuando el ciclo debe detenerse,
cuando el usuario o el sistema decidan cerrar la aplicación,
bien sea con la X sobre la barra de la ventana, con el método
abreviado, mediante el administrador de aplicaciones, etc.
En algunos ejemplos se obvia esta variable al hacer un ciclo
while(1) y utilizando un break; en caso de recibir el mensaje.

PeekMessage() busca y almacena un mensaje de la pila de
mensajes de la aplicación. Si tal mensaje existe lo almacena
en el primer parámetro y devuelve TRUE, de lo contrario
devuelve FALSE. El ultimo parámetro (PM_REMOVE) le indica a la
función al leer un mensaje debe borrarlo de la pila de mensajes.

En este caso solo estamos observando el mensaje WM_QUIT, que
indica que el WndProc llamo la función PostQuitMessage(), es
decir que intercepto algún mensaje de salida de la aplicación.

Si no es este mensaje, se vuelve a colocar en la pila de
mensajes mediante las funciones TranslateMessage() y
DispatchMessage, para que este pueda ser leído por el
wndProc() creado anteriormente o por el manejador de mensajes
por defecto (DefWindowProc()).

Finalmente si no hay ningún mensaje por leer en la pila de
mensajes, se hace el dibujado(render()) y se cambia de buffer
(SwapBuffers()). El código del render() es bastante simple y
pinta un triangulo
*/

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glBegin(GL_TRIANGLES);
glColor3f(0.0f,0.0f,1.0f);
glVertex3f(3.0f,-2.0f,0.0f);
glVertex3f(0.0f,1.0f,0.0f);
glVertex3f(-3.0f,-2.0f,0.0f);
glEnd();

/*
glClear() limpia los buffers pasados como parámetro de acuerdo
al color de limpiado (negro) y entre las funciones glBegin() y
glEnd() esta la especificación de un triangulo de color azul

El codigo entero*/
#include <windows.h>
#include <gl\gl.h>
#include <gl\glu.h>
#pragma comment(lib,"opengl32.lib")
#pragma comment(lib,"glu32.lib")
#define VENTANA_ANCHO 640
#define VENTANA_ALTO  480

HDC   hdc=NULL;
HGLRC      hglrc=NULL;
HWND        hWnd=NULL;
HINSTANCE   hInstance;

void inicializarGL(void){
glClearColor(0.0f, 0.0f, 0.0f, 0.5f);
glMatrixMode (GL_PROJECTION);
glViewport (0, 0, VENTANA_ANCHO , VENTANA_ALTO);
gluPerspective(45.0f,VENTANA_ANCHO/VENTANA_ALTO,0.1f,100.0f);
gluLookAt( 0,0,16,  0,0,0, 0,1,0);
glMatrixMode (GL_MODELVIEW);
glLoadIdentity();
    }

void render(void){
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glBegin(GL_TRIANGLES);
glColor3f(0.0f,0.0f,1.0f);
glVertex3f(3.0f,-2.0f,0.0f);
glVertex3f(0.0f,1.0f,0.0f);
glVertex3f(-3.0f,-2.0f,0.0f);
glEnd();
    }

void deInicializar(void){
wglMakeCurrent(NULL,NULL);
wglDeleteContext(hglrc);
ReleaseDC(hWnd,hdc);
DestroyWindow(hWnd);
UnregisterClass("OpenGL",hInstance);
    }

LRESULT CALLBACK WndProc
(HWND hWnd,UINT uMsg,WPARAM wParam,LPARAM lParam){
switch (uMsg){
case WM_CLOSE:
{
PostQuitMessage(0);
return 0;
}}
return DefWindowProc(hWnd,uMsg,wParam,lParam);}

int WINAPI WinMain
(HINSTANCE hinst,HINSTANCE hinstAnterior,
LPSTR cadenaDeComandos,int parametroVentana){
        MSG  mensaje;
        BOOL    continuar=TRUE;
        GLuint  PixelFormat;
        WNDCLASS    Cwnd;

        hInstance         = GetModuleHandle(NULL);
   Cwnd.style      = CS_OWNDC;
   Cwnd.lpfnWndProc    = (WNDPROC) WndProc;
   Cwnd.cbClsExtra  = 0;
   Cwnd.cbWndExtra  = 0;
   Cwnd.hInstance    = hInstance;
   Cwnd.hIcon      = NULL;
   Cwnd.hCursor        = LoadCursor(NULL, IDC_ARROW);
   Cwnd.hbrBackground  = NULL;
   Cwnd.lpszMenuName   = NULL;
   Cwnd.lpszClassName  = "OpenGL";
   RegisterClass(&Cwnd);

hWnd=CreateWindow("OpenGL","Hola Mundo",WS_OVERLAPPEDWINDOW
,100,100,VENTANA_ANCHO,VENTANA_ALTO,NULL,NULL,hInstance,NULL);

static  PIXELFORMATDESCRIPTOR pfd={
sizeof(PIXELFORMATDESCRIPTOR),1,
PFD_DRAW_TO_WINDOW |PFD_SUPPORT_OPENGL |PFD_DOUBLEBUFFER,
PFD_TYPE_RGBA,32,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0};

hdc=GetDC(hWnd);
PixelFormat=ChoosePixelFormat(hdc,&pfd);
SetPixelFormat(hdc,PixelFormat,&pfd);
hglrc=wglCreateContext(hdc);
wglMakeCurrent(hdc,hglrc);
ShowWindow(hWnd,SW_SHOW);
SetForegroundWindow(hWnd);
SetFocus(hWnd);
inicializarGL();

while(continuar){

if (PeekMessage(&mensaje,NULL,0,0,PM_REMOVE)){
if (mensaje.message==WM_QUIT){continuar=FALSE;}
else{
TranslateMessage(&mensaje);
DispatchMessage(&mensaje);
}}

else{
render();
SwapBuffers(hdc);
}}

deInicializar();
return (mensaje.wParam);}

/*Por ultimo:
La aplicación luce así:

Captura

Aun este programa tiene que abarcar bastantes prestaciones no
contempladas, por dar simpleza (de ser posible eso) al
Hola Mundo. Entre estas prestaciones están:

Solicitar modo pantalla completa, que es muy común entre los
videojuegos.

Atender a los errores en todo el proceso de creación de la
ventana y asociación a OpenGL. Muchas de las funciones
utilizadas en la aplicación tienen un retorno que indica si
la función pudo llevarse a cabo o no.

Cambiar de tamaño la escena de OpenGL al cambiar el tamaño de
la ventana. Esto se hace añadiendo los estilos CS_VREDRAW y
CS_HREDRAW al WNDCLASS e interceptando el mensaje
WM_SYSCOMMAND, Wparam SC_SIZE.

Pausar la aplicación cuando la ventana es puesta detrás de
otra o minimizada.

También es importante aclarar que este ejemplo utiliza el
driver de software de OpenGL creado por Microsoft en la
década del 90 y que actualmente dicho driver no es mantenido y
es muy ineficiente. Para utilizar el driver de OpenGL que
proporciona la tarjeta gráfica, que tiene la ventaja de ser
acelerado por hardware y mantenido constantemente
(dependiendo de la tarjeta) se pueden usar las funciones
WGL-ARB aprobadas en el año 2000 y que hacen actualmente
parte del estándar, aunque siempre que se quieran usar, se
debe comprobar su disponibilidad, ya que dependen de la
implementación del estándar.

Finalmente es necesario señalar que esta es una de las maneras
mas complicadas de hacer una aplicación con OpenGL para
Windows, ya que se trabaja el API de win32 a muy bajo nivel y
las funciones para ligar OpenGL con GDI no han sido
actualizada en muchos años, de hecho el articulo relevante mas
reciente que se puede encontrar en MSDN acerca de OpenGL data
del año 1995.*/

No hay comentarios.:

Publicar un comentario