Escuela Técnica Superior de Ingeniería

 

Grado en Ingeniería Informática

Realidad Virtual

Curso 2023/2024

 

Práctica 2

Programas gráficos

 

Objetivos

 

El objetivo de esta práctica es desarrollar una aplicación gráfica sencilla formada por un único triángulo en movimiento. Para ello es necesario definir el contenido de las etapas programables del proceso de renderizado (vertex shader y fragment shader), generar los atributos de los vértices del triángulo y desarrollar la interfaz del modelo para responder a los distintos eventos recibidos en la ventana de la aplicación.

 

 

Código de la práctica

 

 

Un modelo 3D sencillo

 

El código descrito en la práctica anterior permite generar una plantilla para crear aplicaciones gráficas con OpenGL. Para completar las aplicaciones debemos definir la clase CGModel dando contenido a las funciones que hemos utilizado en la plantilla.

A continuación vamos a presentar un ejemplo de definición de la clase CGModel para desarrollar una aplicación sencilla. Se trata de mostrar un triángulo rojo que se desplaza por una pantalla azul, rebotando en los bordes. El tamaño del triángulo puede aumentarse y disminuirse pulsando sobre las teclas UP y DOWN. El funcionamiento se puede resetear pulsando la tecla R.

El primer paso para desarrollar la aplicación consiste en crear el fichero de cabecera CGModel.h, que contiene la definición de la clase. Además de predefinir los métodos utilizados en la plantilla, la clase incluye una serie de campos que almacenarán la información necesaria para desarrollar el modelo. Así, los campos posX y posY contendrán la posición del centro del triángulo, el campo size indicará el tamaño del triángulo y los campos stepX y stepY almacenarán la velocidad de movimiento del triángulo sobre los ejes X e Y. Para dibujar el triángulo se va a crear una clase específica denominada CGTriangle que describiremos más adelante. Para poder desarrollar el proceso de renderizado se utilizará una clase denominada CGShaderProgram que describe un programa gráfico basado en shaders.

#pragma once

#include <GL/glew.h>
#include "CGShaderProgram.h"
#include "CGTriangle.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);

private:
  GLfloat posX;
  GLfloat posY;
  GLfloat size;
  GLfloat stepX;
  GLfloat stepY;

  CGTriangle* triangle;
  CGShaderProgram* program;
};

Para definir el código de las funciones de la clase CGModel se crea el fichero CGModel.cpp. En primer lugar se define la función initialize(). Los argumentos de esta función corresponden al tamaño de la ventana principal de la aplicación, expesados en píxeles. Este método es el encargado de crear y compilar el programa gráfico, inicializar los campos de la clase y configurar las opciones generales de OpenGL. En este punto se suelen cargar también las texturas que pueda necesitar el modelo, pero en esta sencilla aplicación no se utilizarán texturas. Para crear el programa gráfico se ha utilizado un constructor basado en recursos pero se ha dejado comentado una versión basada en el nombre de los ficheros fuente de los shaders.

El siguiente método a desarrollar es finalize(). Este método se encarga de liberar los recursos reservados por el modelo. En este caso se eliminan el objeto que describe el triángulo y el programa gráfico.

El método resize() se ejecuta cuando se producen cambios en el tamaño de la ventana. En este caso se limita a modificar el viewport, aunque normalmente se modifica también la matriz de proyección entre el sistema de coordenadas del modelo y el sistema de coordenadas clip.

El método render() es el encargado de dibujar el modelo. En primer lugar "limpia" la imagen a mostrar llamando al comando glClear(). El color de fondo con el que se limpia se asigna con el comando glClearColor() al inicializar el modelo. A continuación dibuja el triángulo llamando al método Draw() de ese objeto.

El método update() se ejecuta para actualizar el estado del modelo siguiendo la frecuencia de refresco utilizada en la aplicación (60 fps). Para generar la animación se incrementan los valores de posX y posY con los valores de velocidad stepX y stepY. Cuando la posición del triángulo alcanza los límites del Clip  se cambia el signo de la velocidad para generar el efecto de rebote.

El método key_pressed() es la respuesta a eventos de teclado. En este caso se aumenta el tamaño del triángulo al pulsar sobre la tecla UP y se disminuye al pulsar la tecla DOWN. La tecla R se usará para reinicializar la posición y el tamaño del triángulo.

Para finalizar, los métodos mouse_button() y mouse_move() son la respuesta a los eventos de ratón. En esta aplicación no se va a programar ninguna respuesta a estos eventos, por lo que estos métodos se dejan en blanco.

#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)
{
  // Crea el programa
  program = new CGShaderProgram(IDR_SHADER1, IDR_SHADER2, -1, -1, -1);
  //program = new CGShaderProgram("shaders/VertexShader.glsl", 
                                  "shaders/FragmentShader.glsl", NULL, NULL, NULL);
  if (program->IsLinked() == GL_TRUE) program->Use();
  else std::cout << program->GetLog() << std::endl;

  // Crea la escena
  triangle = new CGTriangle();
  posX = 0.0f;
  posY = 0.0f;
  size = 5.0f;
  stepX = 0.02f;
  stepY = 0.01f;

  // Asigna el viewport
  resize(w, h);

  // Opciones de dibujo
  glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
  glPolygonMode(GL_FRONT, GL_FILL);
}

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

//
// 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()
{
  // Limpia el framebuffer
  glClear(GL_COLOR_BUFFER_BIT);

  // Dibuja la escena
  triangle->Draw(program, posX, posY, size);
}

//
// FUNCIÓN: CGModel::update()
//
// PROPÓSITO: Anima la escena
//
void CGModel::update()
{
  GLfloat length = 0.01f * size;
  posX += stepX;
  if (posX - length < -1.0f) { posX -= stepX; stepX = 0.02f; }
  if (posX + length > 1.0f) { posX -= stepX; stepX = -0.02f; }

  posY += stepY;
  if (posY - length < -1.0f) { posY -= stepY; stepY = 0.01f; }
  if (posY + length > 1.0f) { posY -= stepY; stepY = -0.01f; }
}

//
// 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:
    posX = 0.0f;
    posY = 0.0f;
    size = 5.0f;
    break;
  case GLFW_KEY_UP:
    size += 0.5;
    if (size >= 50.0f) size = 50.0f;
    break;
  case GLFW_KEY_DOWN:
    size -= 0.5;
    if (size <= 1.0f) size = 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)
{
}

 

 

Programa gráfico

 

Para realizar el programa gráfico es necesario crear el código del Vertex Shader y del Fragment Shader e incluirlos en el programa. Una forma de definir el código de estos shaders es incluirlo directamente en el código C++ en forma de string. Sin embargo, cuando el código del shader es largo, esta forma de definirlo resulta muy incómoda. Una forma alternativa consiste en incluir el código de los shaders en ficheros externos y cargarlos en tiempo de ejecución. Esto obliga a distribuir los ficheros junto con el ejecutable de la aplicación y se corre el riesgo de que pueden ser manipulados externamente. La solución utilizada en estas prácticas consiste en definir el código de los shaders en ficheros externos, pero incluir estos ficheros como recursos de la aplicación. Los recursos de una aplicación (por ejemplo, los iconos) se incrustan en el fichero ejecutable. De esta forma se evita tener que distribuirlos como ficheros asociados.

Para crear un recurso en VisualStudio hay que abrir la ventana de "Agregar recurso". Se puede alcanzar esta ventana desde el explorador de soluciones (a la derecha de la ventana principal de VisualStudio) pulsando el botón derecho sobre el directorio "Archivos de recursos", seleccionando la opción de "Agregar" de este menú. En esta ventana hay que crear un nuevo tipo de recurso pulsando sobre la opción "Personalizar".

Figura1

La ventana de personalización permite crear un nuevo tipo de recurso que llamaremos "SHADER".

Figura2

Una vez creado el recurso es posible editar sus propiedades utilizando la "Vista de recursos". Esta vista se puede obtener con el acelerador Ctrl+Mayús.+E. En la propiedad de nombre de fichero vamos a introducir el nombre "shaders\VertexShader.glsl" para el archivo de código del Vertex Shader y "shaders\FragmentShader.glsl" para el archivo de código del Fragment Shader.

Figura3

Una vez creado el archivo "shaders\VertexShader.glsl" podemos introducir el código de esta etapa. En este caso se va a considerar un único atributo de entrada llamado VertexPosition (que contendrá la posición de los vértices del triángulo respecto del centro del triángulo). Para poder modificar la posición y el tamaño del triángulo se van a considerar tres variables uniformes: posX y posY, que describen la posición del centro del triángulo en coordenadas Clip, y size, que describe el tamaño del triángulo. La posición final se calcula multiplicando los atributos por el tamaño y desplazando a la posición del centro del triángulo.

#version 400

in vec3 VertexPosition;

uniform float posX;
uniform float posY;
uniform float size;

void main()
{
  gl_Position = vec4( size*VertexPosition.x + posX
                    , size*VertexPosition.y + posY
                    , VertexPosition.z, 1.0);
}

Por su parte, el Fragment Shader utilizado es muy sencillo. La salida del Fragment Shader debe ser el color de cada pixel del fragmento. En este caso todos los píxeles del interior del triángulo se dibujarán de rojo.

#version 400

out vec4 FragColor; 

void main()
{
  FragColor = vec4(1.0,0.0,0.0,1.0);
}  

Para describir un programa gráfico se va a definir la clase CGShaderProgram. El archivo "CGShaderProgram.h" contiene la cabecera de esta clase. Los miembros privados de la clase incluyen los identificadores de los shader y del programa, así como el campo linked para indicar si el programa se ha compilado y enlazado correctamente.

#pragma once

#include <GL/glew.h>
#include <glm/glm.hpp>

#define NO_SHADER 0xffff

//
// CLASE: CGShaderProgram
//
// DESCRIPCIÓN: Clase que desarrolla un programa GLSL
// 
class CGShaderProgram {
private:
  GLuint program;
  GLuint vertexShader;
  GLuint fragmentShader;
  GLuint geometryShader;
  GLuint tessControlShader;
  GLuint tessEvaluationShader;
  GLboolean linked;

  char* GetShaderCodeFromResource(int idr);
  char* GetShaderCodeFromFile(const char* filename);
  GLuint CreateShader(int mode, int idr);
  GLuint CreateShader(int mode, const char* filename);

public:
  CGShaderProgram(int vs, int fs, int gs, int tcs, int tes);
  CGShaderProgram(const char* vs, const char* fs, const char* gs, 
                                  const char* tcs, const char* tes);
  ~CGShaderProgram();
  GLboolean IsLinked();
  GLvoid Use();
  GLvoid SetUniformF(const char* name, GLfloat f);
  GLvoid SetUniformVec4(const char* name, glm::vec4 m);
  GLvoid SetUniformVec3(const char* name, glm::vec3 m);
  GLvoid SetUniformMatrix4(const char* name, glm::mat4 m);
  GLvoid SetUniformI(const char* name, GLint i);
};

El desarrollo de la clase CGShaderProgram se encuentra en el archivo "CGShaderProgram.cpp". El constructor de la clase se encarga de crear los shaders indicados en los argumentos. A continuación se crea el programa y se enlazan los shaders, estudiando si se producen errores al enlazar. El destructor de la clase elimina los shaders y el programa cargados en la tarjeta gráfica. El método GetShaderCodeFromResource() se encarga de acceder a los ficheros de recursos y cargar el contenido en forma de string. Por su parte, el método GetShaderCodeFromFile() permite cargar el código directamente de un fichero externo no insertado como recurso. Los métodos CreateShader() crean un shader, cargan su código y lo compilan,  especificando el tipo de shader y el recurso o nombre del fichero en el que se encuentra el código GLSL. El método IsLinked() informa del resultado del proceso de creación del programa. El método Use() activa el uso del programa en la tarjeta gráfica. Los métodos SetUniform...() permiten asignar los valores a las variables uniformes.

#include <GL/glew.h>
#include <iostream>
#include <SDKDDKVer.h>
#include <Windows.h>
#include "CGShaderProgram.h"
#include "resource.h"

CGShaderProgram::CGShaderProgram(int vs, int fs, int gs, int tcs, int tes)
{
  vertexShader = NO_SHADER;
  fragmentShader = NO_SHADER;
  geometryShader = NO_SHADER;
  tessControlShader = NO_SHADER;
  tessEvaluationShader = NO_SHADER;
  linked = GL_FALSE;

  // Crea y compila los shaders
  if (vs != -1) vertexShader = CreateShader(GL_VERTEX_SHADER, vs);
  if (fs != -1) fragmentShader = CreateShader(GL_FRAGMENT_SHADER, fs);
  if (gs != -1) geometryShader = CreateShader(GL_GEOMETRY_SHADER, gs);
  if (tcs != -1) tessControlShader = CreateShader(GL_TESS_CONTROL_SHADER, tcs);
  if (tes != -1) tessEvaluationShader = CreateShader(GL_TESS_EVALUATION_SHADER, tes);

  //Crea el programa y carga los shaders
  program = glCreateProgram();
  if (vertexShader != NO_SHADER) glAttachShader(program, vertexShader);
  if (fragmentShader != NO_SHADER) glAttachShader(program, fragmentShader);
  if (geometryShader != NO_SHADER) glAttachShader(program, geometryShader);
  if (tessControlShader != NO_SHADER) glAttachShader(program, tessControlShader);
  if (tessEvaluationShader != NO_SHADER) glAttachShader(program, tessEvaluationShader);
  glLinkProgram(program);

  GLint status;
  glGetProgramiv(program, GL_LINK_STATUS, &status);
  if (status == GL_FALSE)
  {
    linked = GL_FALSE;
    GLint logLength;
    glGetProgramiv(program, GL_INFO_LOG_LENGTH, &logLength);
    char* logInfo = (char*)malloc(sizeof(char) * logLength);
    GLsizei written;
    glGetProgramInfoLog(program, logLength, &written, logInfo);
    std::cout << logInfo << std::endl;
    return;
  }

  linked = GL_TRUE;
}

CGShaderProgram::CGShaderProgram(const char* vs, const char* fs, const char* gs, 
                                                 const char* tcs, const char* tes)
{
  vertexShader = NO_SHADER;
  fragmentShader = NO_SHADER;
  geometryShader = NO_SHADER;
  tessControlShader = NO_SHADER;
  tessEvaluationShader = NO_SHADER;
  linked = GL_FALSE;

  // Crea y compila los shaders
  if (vs != NULL) vertexShader = CreateShader(GL_VERTEX_SHADER, vs);
  if (fs != NULL) fragmentShader = CreateShader(GL_FRAGMENT_SHADER, fs);
  if (gs != NULL) geometryShader = CreateShader(GL_GEOMETRY_SHADER, gs);
  if (tcs != NULL) tessControlShader = CreateShader(GL_TESS_CONTROL_SHADER, tcs);
  if (tes != NULL) tessEvaluationShader = CreateShader(GL_TESS_EVALUATION_SHADER, tes);

  //Crea el programa y carga los shaders
  program = glCreateProgram();
  if (vertexShader != NO_SHADER) glAttachShader(program, vertexShader);
  if (fragmentShader != NO_SHADER) glAttachShader(program, fragmentShader);
  if (geometryShader != NO_SHADER) glAttachShader(program, geometryShader);
  if (tessControlShader != NO_SHADER) glAttachShader(program, tessControlShader);
  if (tessEvaluationShader != NO_SHADER) glAttachShader(program, tessEvaluationShader);
  glLinkProgram(program);

  GLint status;
  glGetProgramiv(program, GL_LINK_STATUS, &status);
  if (status == GL_FALSE)
  {
    linked = GL_FALSE;
    GLint logLength;
    glGetProgramiv(program, GL_INFO_LOG_LENGTH, &logLength);
    char* logInfo = (char*)malloc(sizeof(char) * logLength);
    GLsizei written;
    glGetProgramInfoLog(program, logLength, &written, logInfo);
    std::cout << logInfo << std::endl;
    return;
  }

  linked = GL_TRUE;
}

GLuint CGShaderProgram::CreateShader(int mode, int idr) 
{
  GLint status;
  GLuint shader = glCreateShader(mode);
  char* code = GetShaderCodeFromResource(idr);
  glShaderSource(shader, 1, &code, NULL);
  glCompileShader(shader);
  free(code);
  glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
  if (status == GL_FALSE)
  {
    GLint logLength;
    glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLength);
    char* logInfo = (char*)malloc(sizeof(char) * logLength);
    GLsizei written;
    glGetShaderInfoLog(shader, logLength, &written, logInfo);
    std::cout << logInfo << std::endl;
    return NO_SHADER;
  }
  return shader;
}

GLuint CGShaderProgram::CreateShader(int mode, const char* filename)
{
  GLint status;
  GLuint shader = glCreateShader(mode);
  char* code = GetShaderCodeFromFile(filename);
  glShaderSource(shader, 1, &code, NULL);
  glCompileShader(shader);
  free(code);
  glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
  if (status == GL_FALSE)
  {
    GLint logLength;
    glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLength);
    char* logInfo = (char*)malloc(sizeof(char) * logLength);
    GLsizei written;
    glGetShaderInfoLog(shader, logLength, &written, logInfo);
    std::cout << logInfo << std::endl;
    return NO_SHADER;
  }
  return shader;
}

char* CGShaderProgram::GetShaderCodeFromResource(int idr)
{
  HRSRC shaderHandle = FindResource(NULL, MAKEINTRESOURCE(idr), L"SHADER");
  HGLOBAL shaderGlobal = LoadResource(NULL, shaderHandle);
  LPCTSTR shaderPtr = static_cast<LPCTSTR>(LockResource(shaderGlobal));
  DWORD shaderSize = SizeofResource(NULL, shaderHandle);
  char* shaderCodeLine = (char*)malloc((shaderSize + 1) * sizeof(char));
  memcpy(shaderCodeLine, shaderPtr, shaderSize);
  shaderCodeLine[shaderSize] = '\0';
  FreeResource(shaderGlobal);
  return shaderCodeLine;
}

char* CGShaderProgram::GetShaderCodeFromFile(const char* filename)
{
  FILE* file = NULL;
  fopen_s(&file,filename, "r");
  fseek(file, 0L, SEEK_END);
  int size = ftell(file);
  fseek(file, 0L, SEEK_SET);
  char* code = (char*)malloc(sizeof(char) * (size + 1));
  int read = fread(code, sizeof(char), size, file);
  code[read] = '\0';
  fclose(file);
  return code;
}

CGShaderProgram::~CGShaderProgram()
{
  if (vertexShader != NO_SHADER) glDeleteShader(vertexShader);
  if (fragmentShader != NO_SHADER) glDeleteShader(fragmentShader);
  if (geometryShader != NO_SHADER) glDeleteShader(geometryShader);
  if (tessControlShader != NO_SHADER) glDeleteShader(tessControlShader);
  if (tessEvaluationShader != NO_SHADER) glDeleteShader(tessEvaluationShader);
  glDeleteProgram(program);
}

GLboolean CGShaderProgram::IsLinked()
{
  return linked;
}

GLvoid CGShaderProgram::Use()
{
  glUseProgram(program);
}


void CGShaderProgram::SetUniformF(const char * name, GLfloat f)
{
  GLuint location = glGetUniformLocation(program, name);
  if (location >= 0) glUniform1f(location, f);
}

GLvoid CGShaderProgram::SetUniformMatrix4(const char *name, glm::mat4 m)
{
  GLuint location = glGetUniformLocation(program, name);
  if (location >= 0) glUniformMatrix4fv(location, 1, GL_FALSE, &m[0][0]);
}

void CGShaderProgram::SetUniformVec4(const char * name, glm::vec4 v)
{
  GLuint location = glGetUniformLocation(program, name);
  if (location >= 0) glUniform4fv(location, 1, &v[0]);
}

void CGShaderProgram::SetUniformVec3(const char * name, glm::vec3 v)
{
  GLuint location = glGetUniformLocation(program, name);
  if (location >= 0) glUniform3fv(location, 1, &v[0]);
}

void CGShaderProgram::SetUniformI(const char * name, GLint i)
{
  GLuint location = glGetUniformLocation(program, name);
  if (location >= 0) glUniform1i(location, i);
}

 

 

Creación de un triángulo

 

Para completar el código de la aplicación queda desarrollar la clase que describa el triángulo. La estructura de la clase se va a definir en un fichero de cabecera llamado "CGTriangle.h". Este fichero define la cabecera de una clase formada por dos campos (el identificador del VertexArrayObject del objeto y el identificador del VertexBufferObject que almacenará los valores del atributo VertexPosition). La clase incluye el método Draw() que se encargará de activar el VAO y lanzar la primitiva que dibuje el triángulo.

#pragma once

#include <GL/glew.h>
#include "CGShaderProgram.h"

//
// CLASE: CGTriangle
//
// DESCRIPCIÓN: Clase que representa un triángulo descrito mediante
//              VAO para su renderizado mediante shaders
// 
class CGTriangle {
private:
  GLuint VBO;
  GLuint VAO;

public:
  CGTriangle();
  ~CGTriangle();
  void Draw(CGShaderProgram* program, GLfloat posX, GLfloat posY, GLfloat size);
};

El desarrollo de la clase se va a definir en el fichero "CGTriangle.cpp". Para construir el objeto se crea un array de valores con las coordenadas de los vértices que formarán el triángulo. A partir de ahí se crea el VAO y el VBO y se introduce el contenido. El destructor de la clase se encarga de eliminar el VAO y el VBO, liberando de esta forma la memoria que ocupan en la tarjeta gráfica. El método Draw() asigna los valores de las variables uniformes y a continuación activa el VAO y lanza una primitiva GL_TRIANGLES indicando que se tomen 3 vértices, por lo que se dibujará un único triángulo.

#include "CGTriangle.h"
#include <GL/glew.h>

//
// FUNCIÓN: CGTriangle::CGTriangle()
//
// PROPÓSITO: Crea un triángulo formado por tres vértices
//
CGTriangle::CGTriangle()
{
  int numVertices = 3;
  int numFaces = 1;

  GLfloat vertices[9] = {
    -0.01f, -0.01f, 0.5f,
    0.01f, -0.01f, 0.5f,
    0.0f, 0.01f, 0.5f
  };

  // Create the Vertex Array Object
  glGenVertexArrays(1, &VAO);
  glBindVertexArray(VAO);

  // Create the Vertex Buffer Objects
  glGenBuffers(1, &VBO);

  // Vertex data
  glBindBuffer(GL_ARRAY_BUFFER, VBO);
  glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * numVertices * 3, vertices, 
                                                                   GL_STATIC_DRAW);

  glEnableVertexAttribArray(0); // Vertex position
  glBindBuffer(GL_ARRAY_BUFFER, VBO);
  glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
}

//
// FUNCIÓN: CGTriangle::~CGTriangle()
//
// PROPÓSITO: Destruye el triángulo
//
CGTriangle::~CGTriangle()
{
  // Delete vertex buffer objects
  glDeleteBuffers(1, &VBO);

  // Delete vetex array object
  glDeleteVertexArrays(1, &VAO);
}

//
// FUNCIÓN: CGTriangle::Draw(CGShaderProgram*, GLfloat, GLfloat, GLfloat)
//
// PROPÓSITO: Dibuja el triángulo
//
void CGTriangle::Draw(CGShaderProgram* program, GLfloat posX, GLfloat posY, 
                                                              GLfloat size)
{
  program->SetUniformF("posX", posX);
  program->SetUniformF("posY", posY);
  program->SetUniformF("size", size);

  glBindVertexArray(VAO);
  glDrawArrays(GL_TRIANGLES, 0, 3);
}

 

 

Aspecto final

 

Una vez generado los ficheros anteriores la aplicación puede ser compilada y ejecutada. A continuación se muestra el aspecto de la ventana principal de la aplicación en un instante.

Figura4