|
Grado en Ingeniería Informática
Realidad Virtual
Curso 2023/2024
|
Práctica 4
|
|
Transformaciones geométricas
|
|
Objetivos
|
|
En esta práctica se explica como utilizar matrices de transformación para trabajar con varios sistemas
de coordenadas. Esto permite definir los objetos en su propio sistema de coordenadas (local) y
situarlos en una escena definida con un sistema de coordenadas del modelo (model). Además podemos
mover el punto de vista del observador por medio de una objeto cámara que utiliza su propio sistema de
coordenadas (view). A esto hay que añadir la transformación entre este sistema de cordenadas y el
sistema de cordenadas de Clip (lo que se conoce como proyección).
|
|
La clase CGFigure
|
|
Para considerar las transformaciones que permitan situar figuras en diferentes posiciones dentro de una
escena es necesario incuir algunos cambios a la clase CGFigure que se introdujo en la práctica
anterior. Estos cambios se centran en incluir un campo location que almacena la matriz de
transformación entre el sistema de cordenadas de la figura (local) y el sistema de coordenadas
del modelo (model). Para facilitar la colocación de las figuras en la escena se van a añadir
también algunos métodos que permiten modificar la situación del objeto por medio de traslaciones y
rotaciones. El fichero de cabecera de esta clase quedaría así:
#pragma once
#include <GL/glew.h>
#include <glm/glm.hpp>
#include "CGShaderProgram.h"
#define VERTEX_DATA 0
#define INDEX_DATA 1
//
// CLASE: CGFigure
//
// DESCRIPCIÓN: Clase abstracta que representa un objeto descrito mediante
// VAO para su renderizado mediante shaders
//
class CGFigure {
protected:
GLushort* indexes; // Array of indexes
GLfloat* vertices; // Array of vertices
int numFaces; // Number of faces
int numVertices; // Number of vertices
GLuint VBO[2];
GLuint VAO;
glm::mat4 location; // Model matrix
public:
~CGFigure();
void InitBuffers();
void ResetLocation();
void Translate(glm::vec3 t);
void Rotate(GLfloat angle, glm::vec3 axis);
void Draw(CGShaderProgram* program, glm::mat4 projection, glm::mat4 view);
};
|
La función ResetLocation() asigna la matriz unidad al campo location.
Esto indica que el sistema de coordenadas de la figura coincide con el sistema de
coordenadas de la escena, es decir, que la posición de la figura se encuentra en el
origen de coordenadas de la escena y que su orientación corresponde a los ejes del
sistema de coordenadas de la escena. La función Translate() añade una traslación
a la matriz de transformación. Matemáticamente esto significa multiplicar la matriz
location por la derecha por una matriz de desplazamiento. El resultado es que
la figura se dibujará desplazada sobre el sistema de coordenadas de la escena.
La función Rotate() añade una rotación a la matriz de transformación. El resultado
es que la figura se dibujará girada con respecto al sistema de coordenadas de la escena.
Matemáticamente esta función multiplica la matriz location por la derecha por
una matriz de rotación. La función Draw() se ha modificado para recibir como
atributos la matriz view (que transforma las coordenadas de la escena en
coordenadas del observador) y la matriz projection (que transforma las coordenadas
del observador en coordenadas clip). La transformación a desarrollar en el
VertexShader es la composición de las tres transformaciones (model-view-projection).
El contenido del fichero de código de la clase CGFigure es el siguiente.
#include "CGFigure.h"
#include <GL/glew.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
CGFigure::~CGFigure()
{
if (vertices != NULL) delete[] vertices;
if (indexes != NULL) delete[] indexes;
// Delete vertex buffer objects
glDeleteBuffers(2, VBO);
// Delete vertex array object
glDeleteVertexArrays(1, &VAO);
}
void CGFigure::InitBuffers()
{
// Create the Vertex Array Object
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
// Create the Vertex Buffer Objects
glGenBuffers(2, VBO);
// Copy data to video memory
// Vertex data
int buffersize = sizeof(GLfloat) * numVertices * 3;
glBindBuffer(GL_ARRAY_BUFFER, VBO[VERTEX_DATA]);
glBufferData(GL_ARRAY_BUFFER, buffersize, vertices, GL_STATIC_DRAW);
// Indexes
buffersize = sizeof(GLushort) * numFaces * 3;
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, VBO[INDEX_DATA]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, buffersize, indexes, GL_STATIC_DRAW);
delete[] vertices;
delete[] indexes;
vertices = NULL;
indexes = NULL;
glEnableVertexAttribArray(0); // Vertex position
glBindBuffer(GL_ARRAY_BUFFER, VBO[VERTEX_DATA]);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
location = glm::mat4(1.0f);
}
void CGFigure::ResetLocation()
{
location = glm::mat4(1.0f);
}
void CGFigure::Translate(glm::vec3 t)
{
location = glm::translate(location, t);
}
void CGFigure::Rotate(GLfloat angle, glm::vec3 axis)
{
location = glm::rotate(location, glm::radians(angle), axis);
}
void CGFigure::Draw(CGShaderProgram* program, glm::mat4 projection, glm::mat4 view)
{
glm::mat4 mvp = projection * view * location;
program->SetUniformMatrix4("MVP", mvp);
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, numFaces * 3, GL_UNSIGNED_SHORT, NULL);
}
|
|
|
La figura CGGround
|
|
Las figuras que vamos a situar en la escena son las mismas que las definidas en la práctica
anterior (cubo, pirámide, cono, cilindro, esfera, disco, toro e icosahedro). Como los cambios
planteados son comunes a todas las figuras y se han incorporado a la clase
CGFigure,
no es necesario plantear modificaciones en el código de estas clases. Para dibujar la escena
se va a añadir un suelo formado por una superficie cuadrada. Este suelo requiere una nueva
clase, que vamos a denominar CGGround, que tenemos que añadir. A continuación se muestra
el fichero de cabecera asociado a esta nueva figura.
#pragma once
#include <GL/glew.h>
#include "CGFigure.h"
//
// CLASE: CGGround
//
// DESCRIPCIÓN: Representa un cuadrado dibujado en el suelo
//
class CGGround : public CGFigure {
public:
CGGround(GLfloat l1, GLfloat l2);
};
|
El constructor de la clase se describe en el siguiente fichero de código.
#include "CGGround.h"
#include <GL/glew.h>
#include "CGFigure.h"
CGGround::CGGround(GLfloat l1, GLfloat l2)
{
numFaces = 2; // Number of faces
numVertices = 4; // Number of vertices
GLfloat p_vertices[4][3] = {
{ l1, 0.0f, l2 },
{ l1, 0.0f, -l2 },
{ -l1, 0.0f, -l2 },
{ -l1, 0.0f, l2 }
};
GLushort p_indexes[2][3] = {
{ 0, 1, 2 },
{ 0, 2, 3 }
};
vertices = new GLfloat[numVertices * 3];
for (int i = 0; i < numVertices; i++)
for (int j = 0; j < 3; j++)
vertices[3 * i + j] = p_vertices[i][j];
indexes = new GLushort[numFaces * 3];
for (int i = 0; i < numFaces; i++)
for (int j = 0; j < 3; j++)
indexes[3 * i + j] = p_indexes[i][j];
InitBuffers();
}
|
|
|
El programa gráfico
|
|
El programa gráfico que vamos a utilizar es el mismo que el
planteado en la práctica anterior. El VertexShader incluye
tan solo una única matriz de transformación llamada MVP,
que debe contener la composición de transformaciones
model-view-projection.
#version 400
in vec3 VertexPosition;
uniform mat4 MVP;
void main()
{
gl_Position = MVP * vec4(VertexPosition, 1.0);
}
|
Por su parte, el FragmentShader sigue siendo el mismo y se
limita a dibujar de rojo los píxeles de cada fragmento.
#version 400
out vec4 FragColor;
void main()
{
FragColor = vec4(1.0,0.0,0.0,1.0);
}
|
|
|
Descripción de la escena (clase
CGScene)
|
|
La escena que queremos mostrar está formada por seis figuras
geométricas situadas sobre un plano. El plano es un cuadrado con un
lado de tamaño 100 generado por una figura CGGround. El sistema de coordenadas
del modelo tiene su origen en el centro del cuadrado, siendo los
ejes X y Z los que marcan la superficie del cuadrado y el eje Y la
vertical. Las figuras incluidas en la escena son un cubo, un cono,
un cilindro, un toro, una esfera
y un icosahedro. Estas figuras se van a colocar formando dos hileras
a una distancia de 25 sobre el eje X respecto al centro de la
escena. Las figuras se van a rotar y desplazar verticalmente para
que aparezcan apoyadas sobre el suelo.
El fichero CGScene.h contiene la descripción de la clase
CGScene que describe la escena completa.
La interfaz pública de la clase contiene el constructor de la clase,
su destructor y el método Draw() que debe generar la
escena. Internamente la clase contiene siete objetos CGFigure,
que almacenarán las figuras geométricas y el suelo.
#pragma once
#include <GL/glew.h>
#include <glm/glm.hpp>
#include "CGShaderProgram.h"
#include "CGFigure.h"
class CGScene {
public:
CGScene();
~CGScene();
void Draw(CGShaderProgram* program, glm::mat4 proj, glm::mat4 view);
private:
CGFigure* ground;
CGFigure* fig0;
CGFigure* fig1;
CGFigure* fig2;
CGFigure* fig3;
CGFigure* fig4;
CGFigure* fig5;
};
|
El fichero CGScene.cpp contiene el desarrollo de la clase
CGScene que describe la escena. El constructor de la clase
es el encargado de crear los objetos CGFigure incluidos en
la escena y situarlos mediante llamadas a los métodos
Translate() y Rotate(). El destructor de la clase se
limita a destruir los objetos que contienen las figuras. El método
Draw() es el encargado de generar la imagen y se limita a
ejecutar los métodos Draw() de las figuras que la forman.
#include "CGScene.h"
#include <GL/glew.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include "CGShaderProgram.h"
#include "CGFigure.h"
#include "CGCube.h"
#include "CGCone.h"
#include "CGCylinder.h"
#include "CGSphere.h"
#include "CGTorus.h"
#include "CGIcosahedron.h"
#include "CGGround.h"
//
// FUNCIÓN: CGScene::CGScene()
//
// PROPÓSITO: Construye el objeto que representa la escena
//
CGScene::CGScene()
{
ground = new CGGround(50.0f, 50.0f);
fig0 = new CGCone(5, 20, 5.0f, 5.0f);
fig0->Translate(glm::vec3(25.0f, 5.0f, 25.0f));
fig0->Rotate(-90.0f, glm::vec3(1.0f, 0.0f, 0.0f));
fig1 = new CGCube(5.0f);
fig1->Translate(glm::vec3(-25.0f, 5.0f, 25.0f));
fig1->Rotate(-90.0f, glm::vec3(1.0f, 0.0f, 0.0f));
fig2 = new CGCylinder(20, 20, 5.0f, 5.0f);
fig2->Translate(glm::vec3(25.0f, 5.0f, 0.0f));
fig2->Rotate(90.0f, glm::vec3(1.0f, 0.0f, 0.0f));
fig3 = new CGTorus(20, 40, 3.0f, 5.0f);
fig3->Translate(glm::vec3(-25.0f, 8.0f, 0.0f));
fig4 = new CGSphere(20, 40, 8.0f);
fig4->Translate(glm::vec3(25.0f, 8.0f, -25.0f));
fig5 = new CGIcosahedron(5.0f);
fig5->Translate(glm::vec3(-25.0f, 8.0f, -25.0f));
}
//
// FUNCIÓN: CGScene3:~CGScene()
//
// PROPÓSITO: Destruye el objeto que representa la escena
//
CGScene::~CGScene()
{
delete ground;
delete fig0;
delete fig1;
delete fig2;
delete fig3;
delete fig4;
delete fig5;
}
//
// FUNCIÓN: CGScene::Draw()
//
// PROPÓSITO: Dibuja la escena
//
void CGScene::Draw(CGShaderProgram* program, glm::mat4 proj, glm::mat4 view)
{
ground->Draw(program, proj, view);
fig0->Draw(program, proj, view);
fig1->Draw(program, proj, view);
fig2->Draw(program, proj, view);
fig3->Draw(program, proj, view);
fig4->Draw(program, proj, view);
fig5->Draw(program, proj, view);
}
|
La siguiente imagen muestra una visión de la escena desde el punto
de vista de un observador situado en una posición centrada respecto
al eje X y algo elevada y alejada para obtener una buena
perspectiva.
|
|
Descripción de la cámara (clase
CGCamera)
|
|
Para poder desplazarse a través de la escena es necesario establecer
la posición y orientación del observador y realizar una
transformación del sistema de coordenadas entre el sistema de
coordenadas del modelo y el sistema de coordenadas del observador.
La posición del observador, en coordenadas del modelo, viene dada
por el vector pos. Los ejes de coordenadas del
observador, en coordenadas del modelo, vienen dados por los vectores
right, up y dir.
Para transformar las coordenadas del modelo en coordenadas del
observador el primer paso es realizar una traslación de (-pos).
De esta forma, el origen de coordenadas del modelo pasa a estar
situado en la posición (-pos) y el origen de
coordenadas del observador pasa a estar situado en la posición
(0,0,0).
Para completar la transformación del sistema de coordenadas es
necesario rotar los ejes. El resultado de esta rotación es que el
vector right debe convertirse en el vector unitario
del eje X en el sistema de coordenadas del observador, es decir, en
el vector (1,0,0); el vector up debe transformarse
en el vector unitario sobre el eje Y, es decir, en el vector
(0,1,0); y el vector dir debe convertirse en el
vector unitario sobre el eje Z, es decir, el vector (0,0,1). A
continuación se muestra el contenido de la matriz de rotación.
La matriz de transformación completa se obtiene multiplicando la
matriz de rotación y la matriz de traslación. Esta matriz de
transformación se puede generar facilmente utilizando la función
glm::lookAt(eye,center,up) incluida en la biblioteca GLM. El
primer argumento es la posición del observador. El segundo argumento
es la posición de un punto situado sobre el eje dir.
El tercer argumento de esta función es el vector up.
Para controlar las propiedades del observador vamos a definir una
nueva clase que denominamos CGCamera. Esta clase nos
permitirá almacenar la posición y orientación del observador y
moverlo libremente a través de la escena. La descripción de esta
clase se encuentra en el fichero CGCamera.h.
Los miembros de la clase, descritos en su zona privada, almacenan
los valores de los vectores pos, right, up
y dir. También se incluyen los campos moveStep y
turnStep que contienen la magnitud de los desplazamientos y
giros a realizar en cada paso de la animación. Los campos
cosAngle y sinAngle almacenan el seno y coseno de
turnStep para que no sea necesario recalcularlos
constantemente. La interfaz pública de la clase CGCamera
consta del constructor, setters y getters parta
los campos, métodos Move..() para realizar desplazamientos
sobre los ejes de coordenadas, métodos Turn...() para
realizar rotaciones sobre los ejes de coordenadas y el método
ViewMatrix() que devuelve la matriz de transformación entre el
sistema de coordenadas del modelo y el sistema de coordenadas del
observador.
#pragma once
#include <GL/glew.h>
#include <glm/glm.hpp>
class CGCamera {
public:
CGCamera();
glm::mat4 ViewMatrix();
void SetPosition(GLfloat x, GLfloat y, GLfloat z);
void SetDirection(GLfloat xDir, GLfloat yDir, GLfloat zDir,
GLfloat xUp, GLfloat yUp, GLfloat zUp);
void SetMoveStep(GLfloat step);
void SetTurnStep(GLfloat step);
glm::vec3 GetPosition();
glm::vec3 GetDirection();
glm::vec3 GetUpDirection();
GLfloat GetMoveStep();
GLfloat GetTurnStep();
void MoveFront();
void MoveBack();
void MoveRight();
void MoveLeft();
void MoveUp();
void MoveDown();
void TurnRight();
void TurnLeft();
void TurnUp();
void TurnDown();
void TurnCW();
void TurnCCW();
private:
glm::vec3 Pos;
glm::vec3 Dir;
glm::vec3 Up;
glm::vec3 Right;
GLfloat moveStep;
GLfloat turnStep;
GLfloat cosAngle;
GLfloat sinAngle;
};
|
El desarrollo de la clase
CGCamera se encuentra en el archivo
CGCamera.cpp, que se muestra a continuación. El constructor de la
clase inicializa la posición y orientación del observador de manera
que coincide con el sistema de coordenadas del modelo. El método
ViewMatrix() desarrolla la transformación de los sistemas de
coordenadas utilizando la función lookAt() de la biblioteca
GLM. Los métodos Get...() se limitan a
obtener los valores de los campos internos de la clase. Con respecto
a los métodos Set...() es importante señalar que la
orientación se fija en un único método que solo asigna los valores
de dir y up. Se asume que estos
vectores son unitarios y perpendiculares entre sí. El vector
right se calcula como el producto vectorial entre
up y dir. Los métodos Move...()
generan traslaciones de módulo moveStep sobre cada uno de
los ejes del sistema de coordenadas del observador. Esto supone
modificar el vector pos añadiendo un desplazamiento
de moveStep por el vector unitario de la traslación. Por
ejemplo, MoveRight() genera un desplazamiento en la
dirección del eje X (vector right) y
MoveFront() genera un desplazamiento hacia adelante (dirección
-Z, es decir, -dir). Los métodos Turn...()
realizan giros de magnitud turnStep sobre cada uno de los
ejes del sistema de coordenadas. Para evitar que por problemas de
redondeo se pierda la ortogonalidad de los vectores right,
up y dir, los giros se calculan
sobre un único vector y el otro vector afectado se recalcula
mediante un producto vectorial. Los métodos TurnLeft() y
TurnRight() realizan giros respecto al eje Y. Los métodos
TurnUp() y TurnDown() realizan giros respecto al
eje X. Los métodos TurnCW() y TurnCCW() realizan
giros respecto al eje Z.
#include "CGCamera.h"
#include <GL/glew.h>
#include <math.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
//
// FUNCIÓN: CGCamera::CGCamera()
//
// PROPÓSITO: Construye una cámara
//
// COMENTARIOS:
// La posición inicial es (0,0,0).
// La orientación incial es el sistema de coordenadas del modelo
// El tamaño del paso inicial es 0.1
// El tamaño del giro inicial es 1.0 grados
//
CGCamera::CGCamera()
{
Pos = glm::vec3(0.0f, 0.0f, 0.0f);
Dir = glm::vec3(0.0f, 0.0f, 1.0f);
Up = glm::vec3(0.0f, 1.0f, 0.0f);
Right = glm::vec3(1.0f, 0.0f, 0.0f);
moveStep = 0.1f;
turnStep = 1.0f;
cosAngle = (GLfloat)cos(glm::radians(turnStep));
sinAngle = (GLfloat)sin(glm::radians(turnStep));
}
//
// FUNCIÓN: CGCamera::ViewMatrix()
//
// PROPÓSITO: Obtiene la matriz View para situar la cámara.
//
glm::mat4 CGCamera::ViewMatrix()
{
return glm::lookAt(Pos, Pos - Dir, Up);
}
//
// FUNCIÓN: CGCamera::SetPosition(GLfloat x, GLfloat y, GLfloat z)
//
// PROPÓSITO: Asigna la posición de la cámara con respecto al
// sistema de coordenadas del modelo.
//
void CGCamera::SetPosition(GLfloat x, GLfloat y, GLfloat z)
{
Pos = glm::vec3(x, y, z);
}
//
// FUNCIÓN: CGCamera::SetDirection(GLfloat xDir, GLfloat yDir, GLfloat zDir,
// GLfloat xUp, GLfloat yUp, GLfloat zUp)
//
// PROPÓSITO: Asigna la orientación de la cámara.
//
void CGCamera::SetDirection(GLfloat xD, GLfloat yD, GLfloat zD,
GLfloat xU, GLfloat yU, GLfloat zU)
{
Dir = glm::vec3(xD, yD, zD);
Up = glm::vec3(xU, yU, zU);
Right = glm::cross(Up, Dir);
}
//
// FUNCIÓN: CGCamera::SetMoveStep(GLfloat step)
//
// PROPÓSITO: Asigna el avance en cada paso.
//
void CGCamera::SetMoveStep(GLfloat step)
{
moveStep = step;
}
//
// FUNCIÓN: CGCamera::SetTurnStep(GLfloat step)
//
// PROPÓSITO: Asigna el ángulo de giro en cada paso.
//
void CGCamera::SetTurnStep(GLfloat step)
{
turnStep = step;
cosAngle = (GLfloat)cos(glm::radians(turnStep));
sinAngle = (GLfloat)sin(glm::radians(turnStep));
}
//
// FUNCIÓN: CGCamera::GetPosition()
//
// PROPÓSITO: Obtiene la posición de la cámara.
//
glm::vec3 CGCamera::GetPosition()
{
return Pos;
}
//
// FUNCIÓN: CGCamera::GetDirection()
//
// PROPÓSITO: Obtiene la orientación de la cámara (eje Z).
//
glm::vec3 CGCamera::GetDirection()
{
return Dir;
}
//
// FUNCIÓN: CGCamera::GetUpDirection()
//
// PROPÓSITO: Obtiene la orientación cenital de la cámara (eje Y).
//
glm::vec3 CGCamera::GetUpDirection()
{
return Up;
}
//
// FUNCIÓN: CGCamera::GetMoveStep()
//
// PROPÓSITO: Obtiene el avance en cada paso.
//
GLfloat CGCamera::GetMoveStep()
{
return moveStep;
}
//
// FUNCIÓN: CGCamera::GetTurnStep()
//
// PROPÓSITO: Obtiene el ángulo de giro en cada paso.
//
GLfloat CGCamera::GetTurnStep()
{
return turnStep;
}
//
// FUNCIÓN: CGCamera::MoveFront()
//
// PROPÓSITO: Mueve el observador un paso (moveStep) en la dirección -Dir
//
void CGCamera::MoveFront()
{
Pos -= moveStep * Dir;
}
//
// FUNCIÓN: CGCamera::MoveBack()
//
// PROPÓSITO: Mueve el observador un paso (moveStep) hacia atrás en la dirección Dir
//
void CGCamera::MoveBack()
{
Pos += moveStep * Dir;
}
//
// FUNCIÓN: CGCamera::MoveLeft()
//
// PROPÓSITO: Mueve el observador un paso (moveStep) hacia la izquierda.
//
void CGCamera::MoveLeft()
{
Pos -= moveStep * Right;
}
//
// FUNCIÓN: CGCamera::MoveRight()
//
// PROPÓSITO: Mueve el observador un paso (moveStep) hacia la derecha.
//
void CGCamera::MoveRight()
{
Pos += moveStep * Right;
}
//
// FUNCIÓN: CGCamera::MoveUp()
//
// PROPÓSITO: Mueve el observador un paso (moveStep) hacia arriba.
//
void CGCamera::MoveUp()
{
Pos += moveStep * Up;
}
//
// FUNCIÓN: CGCamera::MoveDown()
//
// PROPÓSITO: Mueve el observador un paso (moveStep) hacia abajo.
//
void CGCamera::MoveDown()
{
Pos -= moveStep * Up;
}
//
// FUNCIÓN: CGCamera::TurnRight()
//
// PROPÓSITO: Rota el observador un paso (angleStep) hacia su derecha.
//
void CGCamera::TurnRight()
{
Dir.x = cosAngle * Dir.x - sinAngle * Right.x;
Dir.y = cosAngle * Dir.y - sinAngle * Right.y;
Dir.z = cosAngle * Dir.z - sinAngle * Right.z;
// Right = Up x Dir
Right = glm::cross(Up, Dir);
}
//
// FUNCIÓN: CGCamera::TurnLeft()
//
// PROPÓSITO: Rota el observador un paso (angleStep) hacia su izquierda.
//
void CGCamera::TurnLeft()
{
Dir.x = cosAngle * Dir.x + sinAngle * Right.x;
Dir.y = cosAngle * Dir.y + sinAngle * Right.y;
Dir.z = cosAngle * Dir.z + sinAngle * Right.z;
// Right = Up x Dir
Right = glm::cross(Up, Dir);
}
//
// FUNCIÓN: CGCamera::TurnUp()
//
// PROPÓSITO: Rota el observador un paso (angleStep) hacia arriba.
//
void CGCamera::TurnUp()
{
Dir.x = cosAngle * Dir.x - sinAngle * Up.x;
Dir.y = cosAngle * Dir.y - sinAngle * Up.y;
Dir.z = cosAngle * Dir.z - sinAngle * Up.z;
// Up = Dir x Right
Up = glm::cross(Dir, Right);
}
//
// FUNCIÓN: CGCamera::TurnDown()
//
// PROPÓSITO: Rota el observador un paso (angleStep) hacia abajo.
//
void CGCamera::TurnDown()
{
Dir.x = cosAngle * Dir.x + sinAngle * Up.x;
Dir.y = cosAngle * Dir.y + sinAngle * Up.y;
Dir.z = cosAngle * Dir.z + sinAngle * Up.z;
// Up = Dir x Right
Up = glm::cross(Dir, Right);
}
//
// FUNCIÓN: CGCamera::TurnCW()
//
// PROPÓSITO: Rota el observador un paso (angleStep) en sentido del reloj.
//
void CGCamera::TurnCW()
{
Up.x = cosAngle * Up.x + sinAngle * Right.x;
Up.y = cosAngle * Up.y + sinAngle * Right.y;
Up.z = cosAngle * Up.z + sinAngle * Right.z;
// Right = Up x Dir
Right = glm::cross(Up, Dir);
}
//
// FUNCIÓN: CGCamera::TurnDown()
//
// PROPÓSITO: Rota el observador un paso (angleStep) en sentido contrario al reloj.
//
void CGCamera::TurnCCW()
{
Up.x = cosAngle * Up.x - sinAngle * Right.x;
Up.y = cosAngle * Up.y - sinAngle * Right.y;
Up.z = cosAngle * Up.z - sinAngle * Right.z;
// Right = Up x Dir
Right = glm::cross(Up, Dir);
}
|
|
|
Descripción del modelo 3D (clase
CGModel)
|
|
El modelo a representar en nuestra aplicación se desarrolla por
medio de la clase CGModel. Esta clase se describe en el fichero
CGModel.h
mostrado a continuación. En este caso, el modelo está formado
simplemente por el programa gráfico, la cámara y la escena. También
se almacena como campo la matriz de proyección. La interfaz pública de la
clase es la que se utiliza en todas las prácticas.
#pragma once
#include <GL/glew.h>
#include "CGShaderProgram.h"
#include "CGScene.h"
#include "CGCamera.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:
CGShaderProgram* program;
CGScene* scene;
CGCamera* camera;
glm::mat4 projection;
};
|
El código asociado al modelo se describe en el fichero
CGModel.cpp. El método initialize() de la clase CGModel es el
encargado de crear los objetos CGShaderProgram, CGCamera y
CGScene.
Inicialmente la cámara se coloca en una posición que permita
apreciar la escena. El método finalize() se encarga de
destruir los objetos program, camera y scene. El método
render(), dedicado a generar la imagen, se limita a
obtener la matriz de transformación de la
cámara y dibujar la escena. La acción de actualización consiste en
mover la cámara hacia el frente. De esta manera, si el valor de
moveStep en la cámara es positivo la cámara se mueve hacia
adelante de forma automática. El método key_pressed() asocia
movimientos de la cámara a algunas teclas, de manera que el usuario
pueda desplazar libremente la cámara a través de la escena. Las
teclas Q y A mueven la cámara arriba y abajo un único paso. Las
teclas O y P mueven la cámara a izquierda y derecha un único paso.
Las teclas '+' y '-' permiten acelarar y frenar la cámara. Para
detener la cámara se utiliza la tecla S. Los giros de cámara se
realizan con las flechas y las teclas K y L, dedicadas a girar a la
derecha y a la izquierda.
#include "CGModel.h"
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <GLFW/glfw3.h>
#include <iostream>
#include "CGCamera.h"
#include "CGScene.h"
//
// 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_FALSE) return;
program->Use();
// Crea la cámara
camera = new CGCamera();
camera->SetPosition(0.0f, 5.0f, 30.0f);
// Crea la escena
scene = new CGScene();
// Asigna el viewport y el clipping volume
resize(w, h);
// Opciones de dibujo
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
glFrontFace(GL_CCW);
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
}
//
// FUNCIÓN: CGModel::finalize()
//
// PROPÓSITO: Libera los recursos del modelo 3D
//
void CGModel::finalize()
{
delete camera;
delete scene;
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)
{
double fov = glm::radians(15.0);
double sin_fov = sin(fov);
double cos_fov = cos(fov);
if (h == 0) h = 1;
GLfloat aspectRatio = (GLfloat)w / (GLfloat)h;
GLfloat wHeight = (GLfloat)(sin_fov * 0.2 / cos_fov);
GLfloat wWidth = wHeight * aspectRatio;
glViewport(0, 0, w, h);
projection = glm::frustum(-wWidth, wWidth, -wHeight, wHeight, 0.2f, 400.0f);
}
//
// FUNCIÓN: CGModel::render()
//
// PROPÓSITO: Genera la imagen
//
void CGModel::render()
{
glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glm::mat4 view = camera->ViewMatrix();
scene->Draw(program, projection, view);
}
//
// FUNCIÓN: CGModel::update()
//
// PROPÓSITO: Anima la escena
//
void CGModel::update()
{
camera->MoveFront();
}
//
// FUNCIÓN: CGModel::key_pressed(int)
//
// PROPÓSITO: Respuesta a acciones de teclado
//
void CGModel::key_pressed(int key)
{
switch (key)
{
case GLFW_KEY_UP:
camera->TurnDown();
break;
case GLFW_KEY_DOWN:
camera->TurnUp();
break;
case GLFW_KEY_LEFT:
camera->TurnCCW();
break;
case GLFW_KEY_RIGHT:
camera->TurnCW();
break;
case GLFW_KEY_S:
camera->SetMoveStep(0.0f);
break;
case GLFW_KEY_RIGHT_BRACKET:
case GLFW_KEY_KP_ADD:
camera->SetMoveStep(camera->GetMoveStep() + 0.1f);
break;
case GLFW_KEY_MINUS:
case GLFW_KEY_KP_SUBTRACT:
camera->SetMoveStep(camera->GetMoveStep() - 0.1f);
break;
case GLFW_KEY_Q:
camera->SetMoveStep(0.1f);
camera->MoveUp();
camera->SetMoveStep(0.0f);
break;
case GLFW_KEY_A:
camera->SetMoveStep(0.1f);
camera->MoveDown();
camera->SetMoveStep(0.0f);
break;
case GLFW_KEY_O:
camera->SetMoveStep(0.1f);
camera->MoveLeft();
camera->SetMoveStep(0.0f);
break;
case GLFW_KEY_P:
camera->SetMoveStep(0.1f);
camera->MoveRight();
camera->SetMoveStep(0.0f);
break;
case GLFW_KEY_K:
camera->TurnLeft();
break;
case GLFW_KEY_L:
camera->TurnRight();
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)
{
}
|
|
|