Escuela Técnica Superior de Ingeniería

 

Grado en Ingeniería Informática

Realidad Virtual

Curso 2023/2024

 

Práctica 1

OpenGL sobre MS-Windows

 

Objetivos

 

El objetivo de esta práctica es construir la estructura básica de un programa basado en OpenGL sobre MS-Windows de manera que en las prácticas siguientes podamos centrarnos estrictamente en las características de OpenGL y no en las características de la plataforma que estamos utilizando. Para ello utilizaremos una biblioteca de manejo de ventanas llamada GLFW con la que construiremos la ventana principal de la aplicación. Esta biblioteca nos permite configurar las respuestas a diferentes eventos de la ventana y utilizar OpenGL sobre el contexto gráfico de la ventana.

 

 

Código de la práctica

 

 

OpenGL

 

OpenGL no es un lenguaje de programación, sino una biblioteca de funciones escritas en C dedicadas a la generación de gráficos. Aunque existe una implementación genérica de OpenGL sobre MS-Windows (que desarrolla el conjunto de funciones de la biblioteca por medio de software), esta implementación genérica no ha sido actualizada desde la versión 1.1 de OpenGL. En la práctica, la versión de OpenGL que se utiliza realmente es la que se distribuye como driver de la tarjeta gráfica. Esta biblioteca se denomina opengl32.dll y se encuentra en el directorio C:\Windows\System32\ junto al resto de librerías del sistema. Se trata de una librería que se enlaza dinámicamente a todas las aplicaciones programadas con OpenGL.

El contenido de la biblioteca opengl32.dll depende de cada fabricante. En algunos casos, cuando una determinada tarjeta gráfica no contiene algún proceso de aceleración gráfica incluido en la especificación de OpenGL, el fabricante lo suple con una implementación software de dicho proceso. Además, los fabricantes suelen incluir sus propias opciones de aceleración que no se encuentran en el estándar de OpenGL. La biblioteca contiene funciones para verificar si una cierta característica está soportada o no por el driver suministrado por el fabricante. Normalmente los programas basados en OpenGL suelen estudiar estas características e intentan aprovechar al máximo las capacidades de las tarjetas.

La primera versión de OpenGL (OpenGL 1.0) fue presentada en 1992. Desde entonces se han presentado numerosas versiones que han ido ampliando el estándar. Hasta el momento, las versiones oficiales de OpenGL han sido las siguientes:1.0, 1.1, 1.2, 1.2.1, 1.3, 1.4, 1.5, 2.0, 2.1, 3.0, 3.1, 3.2, 3.3, 4.0, 4.1, 4.2, 4.3, 4.4, 4.5 y 4.6. Esta variedad de versiones provoca un problema a la hora de compilar los proyectos desarrollados en OpenGL. Aunque en tiempo de ejecución la aplicación se enlace dinámicamente con la biblioteca opengl32.dll desarrollada por el fabricante de la tarjeta gráfica, pueden aparecer conflictos si la versión utilizada en tiempo de compilación es diferente de la usada en tiempo de ejecución. Para evitar estos problemas se suele utilizar la librería OpenGL Extension Wrangler Library (GLEW). Se trata de una librería multiplataforma escrita en C/C++, destinada a ayudar en la carga y consulta de extensiones de OpenGL. La librería GLEW incluye métodos eficientes, en tiempo de ejecución, para determinar qué extensiones de OpenGL son soportadas. Todas las extensiones de OpenGL son listadas en un solo archivo de cabecera, que se genera automáticamente respecto la lista oficial de extensiones.

Un aspecto fundamental de OpenGL es que no pretende ser un window manager. Esto quiere decir que no contiene funciones de manejo de ventanas (creación, configuración, etc) ni de control de eventos de la interfaz de usuario (control de teclado, ratón, sonido) ni de interfaz de entrada/salida (acceso a ficheros, directorios, ...). Corresponde al sistema operativo nativo la gestión de las ventanas y de estos eventos. Las funciones incluidas en OpenGL se limitan a trabajar sobre un contexto gráfico que permita dirigir las imágenes creadas por la tarjeta gráfica a un área determinada de una ventana controlada por el sistema operativo nativo.

Para crear y manejar los aspectos de la ventana se suele utilizar alguna biblioteca que permita trabajar con ventanas de una forma más sencilla. Existen varias bibilotecas de este tipo, como GLUT, FreeGLUT, CPW, FLTK, SDL o GLFW (la página oficial de OpenGL ofrece un listado de estas bibliotecas). En estas prácticas vamos a utilizar la biblioteca GLFW que hemos incluido en el directorio \ComputerGraphics\Tools.

La programación gráfica requiere trabajar tanto sobre el procesador principal (CPU) como sobre los procesadores de la tarjeta gráfica (GPU). La programación de la tarjeta gráfica se realiza en un lenguaje de programación denominado GLSL que utiliza un conjunto de tipos de datos propio que incluye tipos de datos vectoriales y matriciales, así como un conjunto de funciones predefinidas. Resulta bastante útil realizar cálculos con estos tipos de datos en la CPU. Para facilitar esto se utiliza la biblioteca GLM, que define sobre C/C++ los tipos de datos utilizados en GLSL así como numerosas funciones y operadores asociados a estos tipos de datos. La biblioteca GLM también se ha incluido en el directorio  \ComputerGraphics\Tools.

Otra biblioteca incluida en el código de las prácticas es FreeImage. Se trata de una biblioteca dedicada al tratamiento de imágenes que utilizaremos a lo largo de las prácticas para cargar las texturas en nuestras aplicaciones. Esta biblioteca se ha incluido también en el directorio  \ComputerGraphics\Tools.

 

 

Creación de un proyecto de aplicación gráfica

 

A lo largo de estas prácticas vamos a utilizar VisualStudio 2019 como entorno de desarrollo de aplicaciones gráficas para MS-Windows. El primer paso para crear la aplicación es crear un nuevo proyecto en lenguaje C++. La siguiente figura muestra la ventana de creación del nuevo proyecto. Seleccionaremos la plantilla para la creación de un proyecto vacío.

Figura1

Al aceptar la plantilla se abre una ventana para seleccionar el nombre del proyecto y el directorio de trabajo.

Figura2

La plantilla de proyecto seleccionada no contiene ningún archivo de código inicial. El primer paso va a ser crear un primer archivo de código. Para eso hay que seleccionar el menú sobre la carpeta de archivos de orígen (botón derecho) y seleccionar la opción de agregar un nuevo elemento.

Figura3

El primer fichero de código que vamos a añadir es el fichero "main.cpp" donde incluiremos la función main de entrada de la aplicación..

Figura4

Para poder utilizar OpenGL y otras bibliotecas auxiliares, como FreeImage, GLFW y GLM, es necesario realizar algunas modificaciones a las propieades del proyecto. Las bibliotecas auxiliares se han incluido en el código de la práctica en el directorio C:\ComputerGraphics\Tools\. Las propiedades del proyecto se configuran en la última opción del menú Proyecto. Es importante que en la ventana de configuración se seleccione "Todas las configuraciones" y "Todas las plataformas" para la configuración se aplique a cualquier plataforma a la que vayamos a compilar.

El primer paso es añadir los directorios adecuados como directorios de archivos de inclusión (que se utilizan para buscar los archivos indicados por las macros #include) y directorios de archivos de bibliotecas (donde se buscarán los ficheros .dll de estas herramientas). Para añadir los directorios de inclusión de estas herramientas se selecciona la opción "General" dentro del grupo de propiedades de C/C++ y se pulsa en "<Editar>" sobre el campo "Directorios de inclusión adicionales".

Figura5

Los directorios de inclusión a añadir corresponden a las carpetas Include de las herramientas GLEW, GLFW, GLM y FreeImage.

Figura6

Para añadir los directorios de bibliotecas de estas herramientas se selecciona la opción "General" dentro del grupo de propiedades del vinculador y se pulsa en "<Editar>" sobre el campo "Directorios de bibliotecas adicionales".

Figura7

Los directorios de bibliotecas a añadir corresponden a la carpeta lib-vc2019 de la herramienta GLFW y la carpeta Lib de la biblioteca FreeImage y la carpeta lib\Release\x64 de la biblioteca GLEW. La biblioteca GLM se distribuye como código fuente y solo requiere adaptar el directorio de inclusión.

Figura8

El siguiente paso consiste en indicar que nuestra aplicación va a utilizar las bibliotecas de OpenGL (opengl32.dll), GLEW (glew32.dll), FreeImage (FreeImage.dll) y de GLFW (glfw3.dll). Para ello es necesario expandir la opción Entrada dentro del grupo de propiedades de vinculador y pulsar en "<Editar>" sobre el campo Dependencias adicionales como muestra la siguiente figura.

Figura9

En la ventana de edición de dependencias adicionales hay que añadir las librerías "opengl32.lib", "glew32.lib", "FreeImage.lib" y "glfw3.lib".

Figura10

Para terminar la configuración del proyecto hay que indicar que la versión del lenguaje a utilizar es el estándar ISO C++17. Esta versión se define en la opción Idioma del grupo de propiedades de C/C++, seleccionando esta versión en el campo Estándar de lenguaje C++.

Figura11

Por último es importante seleccionar la plataforma x64 como plataforma de compilación del proyecto.

Figura12

 

 

La biblioteca GLEW

 

La biblioteca GLEW desarrolla una pasarela para utilizar las funciones de OpenGL adaptandose a  la versión utilizada en tiempo de ejecución de forma transparente para el programador.

Para utilizar en nuestro código las funciones de OpenGL debemos incluir el fichero de cabecera GL\glew.h.

Para inicializar la biblioteca es necesario ejecutar el método glewInit() después de crear la ventana y el contexto gráfico sobre el que trabajarán las funciones de OpenGL.

Para distribuir las aplicaciones gráficas generadas con GLEW es necesario incluir en la distribución el fichero glew.dll que debe encontrarse en el directorio de ejecución de la aplicación.

 

 

La biblioteca GLFW

 

GLFW es una biblioteca de código abierto dedicada a la gestión de ventanas con soporte para OpenGL, OpenGL ES y Vulkan. Se trata de una biblioteca escrita en C adaptada a múltiples plataformas (MS-Windows, MacOS, X11, Wayland). Las funciones de la biblioteca dan soporte a múltiples ventanas, monitores y eventos de teclado, ratón, joystick e incluso mando de consolas.

La web oficial de la biblioteca es https://www.glfw.org/. En esta página se puede encontrar toda la documentación de la biblioteca así como la zona de descarga. Para el desarrollo de estas prácticas se ha incluido ya la distribución de GLFW en el directorio \ComputerGraphics\Tools.

Para utilizar GLFW hay que comenzar ejecutando la función glfwInit(). Al terminar la aplicación debe ejecutarse glfwTerminate() para liberar todos los recursos que pueda haberse creado. La biblioteca define la estructura GLFWwindow para describir las ventanas. Para crear una ventana se utiliza la función glfwCreateWindow() y para destruirla se usa glfwDestroyWindow(). Para que las funciones de OpenGL utilicen el contexto gráfico de la ventana se utiliza la función glfwMakeContextCurrent().

Las respuestas a los distintos eventos se configura por medio de punteros a funciones. La respuesta al evento de modificación del tamaño de la ventana se configura con la función glfwSetFramebufferSizeCallback(). La respuesta a los eventos de teclado se asigna con la función glfwSetKeyCallback(). La respuesta a los movimientos del ratón se indica por medio de la función glfwSetCursorPosCallback(). La respuesta a los botones del ratón se asigna con la función glfwSetMouseButtonCallback().

Típicamente las funciones de respuesta a eventos deben acceder a la información de la aplicación que ha creado la ventana. Para ello se utiliza la función  glfwSetWindowUserPointer() que permite almacenar una referencia a un objeto de cualquier tipo, lo que permite acceder posteriormente a este objeto por medio de la función glfwGetWindowUserPointer().

 La biblioteca GLFW permite también utilizar ventanas a pantalla completa. Para ello es necesario utilizar la función glfwSetWindowMonitor(), que debe incluir la referencia al monitor, el modo de vídeo utilizado, la posición y el tamaño de la ventana. El monitor puede obtenerse mediante la función glfwGetPrimaryMonitor(). El modo de vídeo utilizado en el monitor se obtiene con la función glfwGetVideoMode(). La posición y tamaño de una ventana se obtiene con las funciones glfwGetWindowSize() y glfwGetWindowPos(). Para usar una ventana normal se puede llamar a la función glfwSetWindowMonitor() con los parámetros de monitor y modo de vídeo a nulo, lo que asigna los valores por defecto. Para crear la ventana a pantalla completa es necesario indicar tanto el monitor como el modo de vídeo, así como el tamaño de la pantalla.

 

 

Generación de la aplicación gráfica

 

Una vez configurado el proyecto vamos a comenzar a desarrollar el código de la aplicación. El primer paso es programar la función principal, incluida en el fichero main.cpp.

#include "CGApplication.h"
#include <iostream>
#include <stdexcept>

//
//// PROYECTO: Project1
//// DESCRIPCIÓN: Creación de una ventana con GLFW y un modelo básico 
// con las respuestas a los eventos de la ventana.
//
int main() 
{
    CGApplication app;

  try
  {
    app.run();
  }
  catch (const std::exception& e)
  {
    std::cerr << e.what() << std::endl;
    return EXIT_FAILURE;
  }

  return EXIT_SUCCESS;
}

El código que vamos a desarrollar se basa inicialmente en dos clases. La clase CGApplication es la clase principal de la aplicación y contienen un método run() que ejecutará la aplicación completa. Los miembros privados de esta clase contienen los métodos y campos necesarios para configurar el proceso de representación gráfica sobre una ventana creada con GLFW. La clase CGModel desarrolla el modelo gráfico y contiene métodos para inicializar el modelo, actualizarlo y modificarlo como respuestas a los eventos de teclado o ratón.

 

 

La clase CGApplication

 

La clase CGApplication es la encargada de crear y gestionar la ventana sobre la que generaremos los gráficos. Los campos window y model permiten almacenar la referencia a la ventana y al modelo gráfico. Los campos windowWidth, windowHeight, windowXpos y windowYpos indican el tamaño y la posición de la ventana. El campo fullScreen indica si la ventana se está mostrando a pantalla completa.  El campo limitFPS contiene la frecuencia de muestreo utilizada (frames per second). Los campos lastTime y deltaTime se usan para generar esa frecuencia de muestreo. A continuación se muestra la declaración de esta clase.

#pragma once

#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include "CGModel.h"

class CGApplication
{
public:
  void run();

private:
  GLFWwindow* window;
  int windowWidth;
  int windowHeight;
  int windowXpos;
  int windowYpos;
  bool fullScreen;
  CGModel model;

  double limitFPS;
  double lastTime;
  double deltaTime;

  // Métodos principales
  void initWindow();
  void initOpenGL();
  void initModel();
  void mainLoop();
  void timing();
  void cleanup();
  void swapFullScreen();

  // Respuesta a eventos
  static void keyCallback(GLFWwindow* window, int key, int scan, int act, int mods);
  static void mouseButtonCallback(GLFWwindow* window, int bt, int action, int mods);
  static void cursorPositionCallback(GLFWwindow* window, double xpos, double ypos);
  static void framebufferResizeCallback(GLFWwindow* window, int width, int height);
};

La clase CGApplication tiene un único método público llamado run() que es el encargado de ejecutar la aplicación. Este método consiste en inicializar la aplicación y quedarse en un bucle de gestión de eventos de la ventana hasta recibir la orden de cierre de la aplicación. El método initWindow() es el encargado de crear y configurar la ventana por medio de las funciones de la biblioteca GLFW. El método initOpenGL() inicializa la biblioteca GLEW para poder comenzar a utilizar las funciones de OpenGL. El método initModel() se encarga de inicializar los campos de control de muestreo y de crear el modelo gráfico. El método mainLoop() es el bucle principal de la aplicación y consiste en un bucle infinito encargado de recibir y gestionar los eventos de la ventana y actualizar y mostrar la representación gráfica. El método timing() es el encargado de la temporización del modelo gráfico. Ejecuta la actualización del modelo en la tasa de muestreo indicada en limitFPS y lanza la representación del modelo gráfico. El método cleanup() se ejecuta al cierre de la aplicación y consiste en liberar el modelo gráfico y la ventana de ejecución. El método swapFullScreen() cambia el modo entre pantalla completa o ventana normal y se lanza al pulsar en la tecla F12. Los métodos Callback() son los encargados de dar respuesta a los diferentes eventos de la ventana. Para ello se almacena en la ventana una referencia al objeto CGApplication que se puede obtener mediante la función glfwGetWindowUserPointer().

#include "CGApplication.h"

//
// FUNCIÓN: CGApplication::run()
//
// PROPÓSITO: Ejecuta la aplicación
//
void CGApplication::run()
{
  initWindow();
  initOpenGL();
  initModel();
  mainLoop();
  cleanup();
}

//
// FUNCIÓN: CGApplication::initWindow()
//
// PROPÓSITO: Inicializa la ventana
//
void CGApplication::initWindow()
{
  glfwInit();

  windowWidth = 800;
  windowHeight = 600;
  fullScreen = false;

  window = glfwCreateWindow(windowWidth, windowHeight, "Computer Graphics", 
                            nullptr, nullptr);
  glfwSetWindowUserPointer(window, this);
  glfwSetFramebufferSizeCallback(window, framebufferResizeCallback);
  glfwSetKeyCallback(window, keyCallback);
  glfwSetCursorPosCallback(window, cursorPositionCallback);
  glfwSetMouseButtonCallback(window, mouseButtonCallback);
  glfwMakeContextCurrent(window);
}

//
// FUNCIÓN: CGApplication::initOpenGL()
//
// PROPÓSITO: Inicializa el entorno gráfico
//
void CGApplication::initOpenGL()
{
  glewInit();
}

//
// FUNCIÓN: CGApplication::initModel()
//
// PROPÓSITO: Inicializa el modelo
//
void CGApplication::initModel()
{
  limitFPS = 1.0 / 60.0;
  lastTime = glfwGetTime();
  deltaTime = 0;

  int width, height;
  glfwGetFramebufferSize(window, &width, &height);
  model.initialize(width, height);
}

//
// FUNCIÓN: CGApplication::mainLoop()
//
// PROPÓSITO: Bucle principal que procesa los eventos de la aplicación
//
void CGApplication::mainLoop()
{
  while (!glfwWindowShouldClose(window))
  {
    glfwPollEvents();
    timing();
    glfwSwapBuffers(window);
  }
}

//
// FUNCIÓN: CGApplication::timing()
//
// PROPÓSITO: Renderizado
//
void CGApplication::timing()
{
  double nowTime = glfwGetTime();
  deltaTime += (nowTime - lastTime) / limitFPS;
  lastTime = nowTime;

  while (deltaTime >= 1.0)
  {
    model.update();
    deltaTime--;
  }
  model.render();
}

//
// FUNCIÓN: CGApplication::cleanup()
//
// PROPÓSITO: Libera los recursos y finaliza la aplicación
//
void CGApplication::cleanup()
{
  model.finalize();
  glfwDestroyWindow(window);
  glfwTerminate();
}

//
// FUNCIÓN: CGApplication::swapFullScreen()
//
// PROPÓSITO: Dibuja la ventana a pantalla completa o a tamaño configurable
//
void CGApplication::swapFullScreen()
{
  if (!fullScreen)
  {
    glfwGetWindowSize(window, &windowWidth, &windowHeight);
    glfwGetWindowPos(window, &windowXpos, &windowYpos);
    fullScreen = true;
    GLFWmonitor* monitor = glfwGetPrimaryMonitor();
    const GLFWvidmode* mode = glfwGetVideoMode(monitor);
    glfwSetWindowMonitor(window, monitor, 0, 0, mode->width, mode->height, 
                                                mode->refreshRate);
  }
  else
  {
    fullScreen = false;
    glfwSetWindowMonitor(window, nullptr, windowXpos, windowYpos, 
                                          windowWidth, windowHeight, nullptr);
  }
}

//
// FUNCIÓN: CGApplication::keyCallback(GLFWwindow* window, int key, int scancode, 
//                                                         int action, int mods)
//
// PROPÓSITO: Respuesta a un evento de teclado sobre la aplicación
//
void CGApplication::keyCallback(GLFWwindow* window, int key, int scancode, 
                                                    int action, int mods)
{
  auto app = reinterpret_cast<CGApplication*>(glfwGetWindowUserPointer(window));
  if (action == GLFW_PRESS || action == GLFW_REPEAT) 
  {
    if (key == GLFW_KEY_F12) app->swapFullScreen();
    else app->model.key_pressed(key);
  }
}

//
// FUNCIÓN: CAApplication::mouseButtonCallback(GLFWwindow* window, int button, 
//                                                         int action, int mods)
//
// PROPÓSITO: Respuesta a un evento de ratón sobre la aplicación
//
void CGApplication::mouseButtonCallback(GLFWwindow* window, int button, 
                                                            int action, int mods)
{
  auto app = reinterpret_cast<CGApplication*>(glfwGetWindowUserPointer(window));
  app->model.mouse_button(button, action);
}

//
// FUNCIÓN: CGApplication::cursorPositionCallback(GLFWwindow* window, double xpos, 
//                                                                    double ypos)
//
// PROPÓSITO: Respuesta a un evento de movimiento del cursor sobre la aplicación
//
void CGApplication::cursorPositionCallback(GLFWwindow* window, double xpos, 
                                                               double ypos)
{
  auto app = reinterpret_cast<CGApplication*>(glfwGetWindowUserPointer(window));
  app->model.mouse_move(xpos, ypos);
}

//
// FUNCIÓN: CGApplication::framebufferResizeCallback(GLFWwindow* window, int width, 
//                                                                       int height)
//
// PROPÓSITO: Respuesta a un evento de redimensionamiento de la ventana principal
//
void CGApplication::framebufferResizeCallback(GLFWwindow* window, int width, 
                                                                  int height)
{
  auto app = reinterpret_cast<CGApplication*>(glfwGetWindowUserPointer(window));
  if (height != 0) app->model.resize(width, height);
}

 

 

La clase CGModel

 

La clase CGModel contiene la descripción del modelo gráfico. En las próximas prácticas esta clase se irá modificando para añadir la proramación gráfica usando OpenGL. En este caso vamos a desarrollar un modelo muy sencillo que tan solo va a utilizar tres funciones básicas de OpenGL. La función glViewport() establece el tamaño de la imagen a generar que debe coincidir con el tamaño del área de dibujo de la ventana. La función glClear() borra toda la imagen utilizando un color de borrado que se puede asignar mediante la función glClearColor(). A continuación se muestra el contenido de cabecera para la clase.

#pragma once

#include <GL/glew.h>

class CGModel
{
public:
  void initialize(int w, int h);
  void finalize();
  void render();
  void update();
  void key_pressed(int key);
  void mouse_button(int button, int action);
  void mouse_move(double xpos, double ypos);
  void resize(int w, int h);
};

El cuerpo de los métodos de la clase CGModel se incluye en el fichero CGModel.cpp que se muestra a continuación. El método initialize() asigna a blanco el color de fondo y llama al método resize() que se encarga de establecer el tamaño de la imagen. El método render() es el encargado de lanzar el proceso de dibujo y se limita a limpiar la imagen con el color de fondo establecido. El método key_pressed() es la respuesta a los eventos de teclado y analiza la tecla pulsada para modificar el color de fondo. El resto de métodos no tienen contenido en este ejemplo.

#include "CGModel.h"
#include <GLFW\glfw3.h>
#include <iostream>

//
// FUNCIÓN: CGModel::initialize(int, int)
//
// PROPÓSITO: Initializa el modelo 3D
//
void CGModel::initialize(int w, int h)
{
  glClearColor(1.0f, 1.0f, 1.0f, 1.0f);

  resize(w, h);
}

//
// FUNCIÓN: CGModel::finalize()
//
// PROPÓSITO: Libera los recursos del modelo 3D
//
void CGModel::finalize()
{
}

//
// FUNCIÓN: CGModel::resize(int w, int h)
//
// PROPÓSITO: Asigna el viewport y el clipping volume
//
void CGModel::resize(int w, int h)
{
  glViewport(0, 0, w, h);
}

//
// FUNCIÓN: CGModel::render()
//
// PROPÓSITO: Genera la imagen
//
void CGModel::render()
{
  glClear(GL_COLOR_BUFFER_BIT);
}

//
// FUNCIÓN: CGModel::update()
//
// PROPÓSITO: Anima la escena
//
void CGModel::update()
{
}

//
// FUNCIÓN: CGModel::key_pressed(int)
//
// PROPÓSITO: Respuesta a acciones de teclado
//
void CGModel::key_pressed(int key)
{
  switch (key)
  {
  case GLFW_KEY_R:
    glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
    break;
  case GLFW_KEY_G:
    glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
    break;
  case GLFW_KEY_B:
    glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
    break;
  case GLFW_KEY_W:
    glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
    break;
  }
}

//
// FUNCIÓN: CGModel:::mouse_button(int button, int action)
//
// PROPÓSITO: Respuesta del modelo a un click del ratón.
//
void CGModel::mouse_button(int button, int action)
{
}

//
// FUNCIÓN: CGModel::mouse_move(double xpos, double ypos)
//
// PROPÓSITO: Respuesta del modelo a un movimiento del ratón.
//
void CGModel::mouse_move(double xpos, double ypos)
{
}

 

 

Aspecto final

 

Una vez generado los ficheros anteriores la aplicación puede ser compilada y ejecutada. Es importante tener en cuenta que la aplicación contiene llamadas a la librería glew.dll que no se encuenta por defecto en el sistema. Para evitar errores de ejecución es importante copiar esta librería en el directorio del ejecutable de la aplicación. A continuación se muestra el aspecto de la ventana principal de la aplicación. El color de la ventana se modifica al pulsar sobre las teclas R (rojo), G (verde), B (azul) y W (blanco).

Captura13