Mi primera aplicación OpenGL con MFC Con esta guía rápida, escribiremos nuestra primera aplicación OpenGL con MFC (Microsoft Foundations Class). Ejecutar Visual C++ 6.0 Elegir la opción File del menú para crear un nuevo espacio de trabajo, hay que elegir la opción New. Escoger MFC AppWizard(exe) como el tipo de proyecto. En Project name podemos elegir como nombre del proyecto OpenGL y seleccionar además una localización adecuada para guardar el proyecto (Location). En el wizard, escoger la opción Single Document y eliminar la opción Printing and Print Preview, aceptar todas las demás opciones como default. Compilar y ejecutar el código. La aplicación Windows creada usando MFC-Appwizard se muestra a continuación. Ahora necesitamos modificar el código para que MFC-Appwizard nos permita dibujar en la ventada, usando OpenGL. Como sabemos, es la View class la responsable de dibujar en la ventana. Necesitamos incorporar todos los pasos necesarios para preparar a la view class y así poder realizar aplicaciones OpenGL. Abrir el ClassWizard y seleccionar COpenGLView class. Agregar los siguientes Windows Messages handlers: WM_CREATE (para OnCreate), WM_DESTROY (para OnDestroy), WM_SIZE (para OnSize), WM_ERASEBKGND (para OnEraseBkground). Agregar los archivos de encabezado OpenGL en el archivo stdax.h. Agregarlos antes de //AFX_INSERT_LOCATION
// OpenGL headers #include <GL\gl.h> #include <GL\glu.h> #include <GL\glaux.h> #include <GL\glut.h> Agregar en el Project Settings las librerias necesarias para poder compilar las aplicaciones como se muestra a continuación. Compilar nuevamente la aplicación. Si la compilación y ejecución del programa fueron exitosas, entonces necesitamos editar los message handlers uno por uno. Después de editar cada uno de los handler, recompilar la aplicación hasta estar seguros de que funciona correctamente. Editar PreCreateWindow() Antes de que sea creada la ventana necesitamos establecer el estilo de la ventana para incluir WS_CLIPCHILDREN y WS_CLIPSIBLINGS para evitar que se dibuje en cualquier otra ventana de Windows. Debemos realizar estos cambios a la función miembro PreCreateWindow(). En el browser de clases, escoger la opción Go to Definition. BOOL COpenGLView::PreCreateWindow(CREATESTRUCT& cs) // TODO: Modify the Window class or styles here by modifying // the CREATESTRUCT cs
return CView::PreCreateWindow(cs); Agregar lo siguiente: BOOL COpenGLView::PreCreateWindow(CREATESTRUCT& cs) // TODO: Modify the Window class or styles here by modifying // the CREATESTRUCT cs // Se bebe crear una ventana OpenGL con las siguientes banderas cs.style = WS_CLIPSIBLINGS WS_CLIPCHILDREN; return CView::PreCreateWindow(cs); Editar OnCreate() y creación del Píxel Format y el contexto de renderizado En OnCreate() debemos inicializar OpenGL para crear el píxel format y el contexto de renderizado. Incluiremos una función miembro llamada InitializeOpenGL() la cual ejecutará estas operaciones. Abrir el archivo OpenGLView.h para agregar las siguientes líneas en la sección pública de la clase: // Attributes public: COpenGLDoc* GetDocument(); HGLRC m_hrc; CDC* m_pdc; //Rendering Context //Device Context HGLRC es un handler para el contexto de renderizado y necesitamos obtener un contexto de renderizado valido de un dispositivo Windows el cual es // Operations public: BOOL InitializeOpenGL(); //Initialize OpenGL BOOL SetupPixelFormat(); //Set up the Pixel Format InitializeOpenGL() es necesario para crear el contexto del dispositivo (Device Context, DC), seleccionar el pixel format para este DC, crear un RC associado con el DC, y seleccionar el RC. InitializeOpenGL llama a SetupPixelFormat para establecer el pixel format. Agregamos a InitializeOpenGL() en el handler OnCreate() como se muestra a continuación: Seleccionar en el browser de clases el handler OnCreate() y escoger la opción Go to Definition.
int COpenGLView::OnCreate(LPCREATESTRUCT lpcreatestruct) if (CView::OnCreate(lpCreateStruct) == -1) return -1; // TODO: Add your specialized creation code here // Inicializa OpenGL InitializeOpenGL(); return 0; Ahora examinaremos el código de InitializeOpenGL() paso a paso. Primero se debe agregar al final del archivo OpenGLView.cpp BOOL COpenGLView::InitializeOpenGL() //Get a DC for the Client Area m_pdc = new CClientDC(this); //Failure to Get DC if(m_pdc == NULL) MessageBox("Error Obtaining DC"); Esta función primero obtiene un DC para el área del cliente y checa que no es NULL. //Failure to set the pixel format if(!setuppixelformat()) Entonces, llama a SetupPixelFormat() para actualizar el pixel format y checar en caso de fallas. Se discute brevemente el código. //Create Rendering Context m_hrc = ::wglcreatecontext (m_pdc->getsafehdc ()); //Failure to Create Rendering Context if(m_hrc == 0) MessageBox("Error Creating RC");
Entonces, este crea el contexto de renderizado del DC obtenido y checa en caso de fallas. La función wglcreatecontext crea un nuevo contexto de renderizado para OpenGL, el cual es adecuado para dibujar en el dispositivo referenciado por DC. //Make the RC Current if(::wglmakecurrent (m_pdc->getsafehdc (), m_hrc)==false) MessageBox("Error making RC Current"); Después de esto, el RC creado es el actual. La función wglmakecurrent hace a un contexto de renderizado OpenGL las llamadas a los hilos del actual contexto de renderizado. Todas las subsiguientes llamadas OpenGL hechas por el hilo se dibujan en el dispositivo identificado por DC. //Specify Black as the clear color ::glclearcolor(0.0f,0.0f,0.0f,0.0f); //Specify the back of the buffer as clear depth ::glcleardepth(1.0f); //Enable Depth Testing ::glenable(gl_depth_test); return TRUE; La función también inicializa ciertas variables de estado de OpenGL en las últimas líneas de código. OpenGL mantiene ciertas variables de estado globales que almacenan información que se encarga de verificar el estado actual de OpenGL como el color actual de la pantalla, los colores, información de las luces, propiedades de los materiales, etc. Ahora revisaremos la función para establecer el píxel format. También se debe de agregar al archivo OpenGLView.cpp BOOL COpenGLView::SetupPixelFormat() Las capacidades de la ventana OpenGL depende del pixel format seleccionado para el renderizado de la ventana OpenGL. Las propiedades de este formato incluyen: static PIXELFORMATDESCRIPTOR pfd = sizeof(pixelformatdescriptor), // size of this pfd 1, // version number PFD_DRAW_TO_WINDOW // support window PFD_SUPPORT_OPENGL // support OpenGL PFD_DOUBLEBUFFER, // double buffered
PFD_TYPE_RGBA, // RGBA type 24, // 24-bit color depth 0, 0, 0, 0, 0, 0, // color bits ignored 0, // no alpha buffer 0, // shift bit ignored 0, // no accumulation buffer 0, 0, 0, 0, // accum bits ignored 16, // 16-bit z-buffer 0, // no stencil buffer 0, // no auxiliary buffer PFD_MAIN_PLANE, // main layer 0, // reserved 0, 0, 0 // layer masks ignored ; nsize especifica el tamaño de la estructura, nversion especifica la versión de la estructura, dwflags es un conjunto de banderas de bits que especifican las propiedades del píxel buffer. PFD_DRAW_TO_WINDOW especifica que buscamos dibujar en una ventana y PFD_SUPPORT_OPENGL especifica que buscamos usar OpenGL. PFD_DOUBLEBUFFER especifica que necesitamos un buffer doble. ipixeltype especifica el tipo de color del dato del píxel. Puede ser RGB o RGBA. ccolorbits especifica el número de los bits de colores planos en cada buffer de color.para el modo RGBA es el tamaño en bits del buffer de color. int m_npixelformat = ::ChoosePixelFormat(m_pDC->GetSafeHdc(), &pfd); if ( m_npixelformat == 0 ) if ( ::SetPixelFormat(m_pDC->GetSafeHdc(), m_npixelformat, &pfd) == FALSE) return TRUE; Una vez que el píxel format se ha establecido intentamos obtener el píxel format más cercano llamando a la función ChoosePixelFormat. La función ChoosePixelFormat intenta emparejar un píxel format apropiado soportado por un dispositivo de contexto a una especificación dada del píxel format. También comprobamos para asegurarnos que el índice devuelto sea diferente de cero. Una vez que se obtiene el emparejamiento más cercano, llamamos a la función SetPixelFormat la cual toma el píxel format del dispositivo de contexto especificado al forma especificado por el índice. También checamos para asegurarnos que trabaja apropiadamente. Si todo funciona correctamente, la función regresa true.
Editar OnSize() En OnSize() es donde usualmente configuramos el Viewport y la proyección, ya que estos parámetros dependen del tamaño de la ventana. En el browser de clases elegimos el handler OnSize y elegimos la opción Go to Definition, agregando lo siguiente: void COpenGLView::OnSize(UINT ntype, int cx, int cy) CView::OnSize(nType, cx, cy); // TODO: Add your message handler code here GLdouble aspect_ratio; if ( 0 >= cx 0 >= cy ) return; // select the full client area ::glviewport(0, 0, cx, cy); // compute the aspect ratio // this will keep all dimension scales equal aspect_ratio = (GLdouble)cx/(GLdouble)cy; // select the projection matrix and clear it ::glmatrixmode(gl_projection); ::glloadidentity(); // select the viewing volume ::gluperspective(45.0f, aspect_ratio,.01f, 200.0f); // switch back to the modelview matrix and clear it ::glmatrixmode(gl_modelview); ::glloadidentity(); Editar OnDraw() Es una clara secuencia de eventos que ocurren en los programas OpenGL cuando se debe renderizar la escena. Estos eventos son: Limpiar los buffers Renderizar la escena Flush el pipeline de renderizado Intercambiar los contenidos del buffer de atrás si se usa doble buffer Seleccionar el handler correspondiente y agregar el siguiente código: void COpenGLView::OnDraw(CDC* pdc)
COpenGLDoc* pdoc = GetDocument(); ASSERT_VALID(pDoc); // TODO: add draw code for native data here // Clear out the color & depth buffers ::glclear( GL_COLOR_BUFFER_BIT GL_DEPTH_BUFFER_BIT ); RenderScene(); // Tell OpenGL to flush its pipeline ::glfinish(); // Now Swap the buffers ::SwapBuffers( m_pdc->getsafehdc() ); La función RenderScene() Cualquier código de representación va aquí. En este programa no renderizamos cualquier cosa sobre la pantalla, así que este cuerpo de la función se deja vacío. Agregar el siguiente código en el archivo OpenGLView.cpp void COpenGLView::RenderScene () También se debe agregar al archivo OpenGLView.h el prototipo de la función. public: BOOL InitializeOpenGL(); //Initialize OpenGL BOOL SetupPixelFormat(); //Set up the Pixel Format void RenderScene(); //Render the Scene Hay que compilar y ejecutar el programa.
si intentamos ajustar el tamaño de la ventana, veremos un parpadeo. Necesitamos hacer algunos ajustes. Editamos OnEraseBkgnd() Esta es la forma original: BOOL COpenGLView::OnEraseBkgnd(CDC* pdc) // TODO: Add your message handler code here and/or call default return CView::OnEraseBkgnd(pDC); Y los nuevos cambios serán los siguientes: BOOL COpenGLView::OnEraseBkgnd(CDC* pdc) // TODO: Add your message handler code here and/or call default // return CView::OnEraseBkgnd(pDC); return TRUE; También se edita OnDestroy() y se agrega el siguiente código: void COpenGLView::OnDestroy() CView::OnDestroy(); // TODO: Add your message handler code here if(::wglmakecurrent (0,0) == FALSE) MessageBox("Could not make RC non-current"); //Delete the rendering context if(::wgldeletecontext (m_hrc)==false) MessageBox("Could not delete RC"); //Delete the DC if(m_pdc) delete m_pdc; //Set it to NULL m_pdc = NULL;