|
Grado en Ingeniería Informática
Realidad Virtual
Curso 2023/2024
|
Objetivos
|
|
En esta práctica se muestra como utilizar texturas para incorporar
información asociada a los puntos internos de las primitivas. Esto
permite incluir información sobre la dirección normal a cada punto
que se utiliza para incorporar efectos de rugosidad en las imágenes.
|
|
Normal maps
|
|
Las texturas permiten proyectar una imagen sobre una
primitiva, pero esa proyección es plana. Una forma de modelar el
efecto de la rugosidad es considerar que las normales en cada punto
de una primitiva no son iguales sino que cada punto tiene una normal
diferente.

Para almacenar la información sobre la normal asociada a cada
pixel se utiliza una textura, pero en este caso la información
almacenada no se interpreta como un color RGB sino como las
coordenadas del vector normal. Este tipo de texturas se conocen como
Normal Maps. Para representar el vector normal es necesario fijar un
sistema de coordenadas. Habitualmente se utiliza un sistema de
coordenadas local a cada punto y tangente a la superficie. De esta
forma, el eje X se considera paralelo a la coordenada U de la
textura; el eje Y se considera paralelo al eje V de la textura; y el
eje Z se considera perpendicular a la primitiva. Las coordenadas xyz
del vector normal se almacenan como coordenadas rgb de la textura.
La mayor parte de los puntos suele tener una dirección normal
perpendicular a la primitiva (es decir, sobre el eje Z en el plano
tangente) por lo que la interpretación de las texturas como colores
suele dar un tono azul en la mayoría de los puntos.

Para poder interpretar la información de un normal map es
necesario conocer no solo el vector normal a cada punto (que indica
el eje Z), sino también los vectores tangente (eje X) y bitangente
(eje Y). En realidad es suficiente con conocer el vector normal y el
vector tangente, ya que el vector bitagente puede obtenerse como el
producto vectorial de los anteriores.

Dados las posiciones de los tres vértices de un triángulo (en
coordenadas locales) y las coordenadas de textura de dichos vértices
se puede calcular los vectores tangente y bitangente. El vector E1
corresponde a la diferencia entre la posición de los vértices P2 y
P1. El vector E2 corresponde a la diferencia entre la posición de
los vértices P3 y P2. Los vectores E1 y E2 se pueden expresar en
términos de los vectores tangente T y bitangente B.

A partir de esta ecuación se pueden calcular los valores de T y B.
Esto permite calcular los valores de T y B para cada
triángulo. En realidad necesitamos los valores de T y B para cada
vértice. Esto se obtiene realizando una media entre los valores de
los triángulos a los que pertenece cada vértice.

|
|
El programa gráfico
|
|
Para incorporar la técnica de NormalMapping es necesario
incluir un nuevo atributo asociado a los vértices, que corresponde
al vector tangente expresado en coordenadas locales. El modelo de
iluminación de Phong se basa en tres vectores: el vector normal,
el vector de posición desde el observador y la dirección de la
luz. El problema es que cada uno de estos vectores está descrito
en un sistema de coordenadas diferente. La dirección de la luz se
define en coordenadas del modelo. La posición del punto desde el
observador se obtiene proyectando el atributo VertexPosition
y se define en coordenadas de observador. La normal se leerá del
fichero de textura NormalMap y está descrita en
coordenadas del espacio tangente.
A continuación se muestra el contenido del VertexShader
asociado a la técnica de NormalMapping. Las salidas del
VertexShader se van a expresar en el sistema de
coordenadas tangente. Para ello es necesario transformar las
coordenadas de observador (que pueden obtenerse con las matrices
de transformación ModelViewMatrix y ViewMatrix)
en coordenadas tangentes. Dentro del shader se calcula la matriz
de transformación TBN utilizando los vectores
VertexNormal y VertexTangent. A continuación se
calcula las variables de salida LightDir y ViewDir
en coordenadas tangentes.
#version 400
layout (location = 0) in vec3 VertexPosition;
layout (location = 1) in vec3 VertexNormal;
layout (location = 2) in vec2 VertexTexCoord;
layout (location = 3) in vec3 VertexTangent;
struct LightInfo {
vec3 Ldir;
vec3 La;
vec3 Ld;
vec3 Ls;
};
uniform LightInfo Light;
uniform mat4 ModelViewMatrix;
uniform mat4 ViewMatrix;
uniform mat4 MVP;
out vec3 LightDir;
out vec2 TexCoord;
out vec3 ViewDir;
void main()
{
vec4 n4 = ModelViewMatrix*vec4(VertexNormal,0.0);
vec4 t4 = ModelViewMatrix*vec4(VertexTangent,0.0);
vec4 v4 = ModelViewMatrix*vec4(VertexPosition,1.0);
// Vectores unitarios del espacio tangente
vec3 N = normalize( vec3(n4) );
vec3 T = normalize( vec3(t4) );
vec3 B = normalize( cross(N,T) );
// Matriz de transformación al espacio tangente
mat3 TBN = mat3(
T.x, B.x, N.x,
T.y, B.y, N.y,
T.z, B.z, N.z);
LightDir = vec3( ViewMatrix * vec4(Light.Ldir,0.0));
LightDir = normalize(TBN * LightDir);
ViewDir = TBN * normalize( vec3( -v4 ) );
TexCoord = VertexTexCoord;
gl_Position = MVP * vec4( VertexPosition, 1.0);
}
|
El FragmentShader recibe como entrada las propiedades de la
luz y el material, así como las texturas necesarias y los vectores
de dirección de la luz y del punto en coordenadas del espacio
tangente. La función ads() se ha modificado para recibir
como parámetros el vector normal y los colores de la textura
ambiental-difusa y de la textura especular. El método main()
se encarga de leer la información de las texturas y ejecutar la
función ads() para obtener el color final asociado a cada
pixel del fragmento.
#version 400
in vec3 LightDir;
in vec2 TexCoord;
in vec3 ViewDir;
uniform bool NormalMapping;
uniform sampler2D ColorTex;
uniform sampler2D NormalMapTex;
uniform sampler2D SpecMapTex;
struct LightInfo {
vec3 Ldir;
vec3 La;
vec3 Ld;
vec3 Ls;
};
uniform LightInfo Light;
struct MaterialInfo{
vec3 Ka;
vec3 Kd;
vec3 Ks;
float Shininess;
float Dissolved;
};
uniform MaterialInfo Material;
out vec4 FragColor;
vec3 ads(vec3 norm, vec3 tex, vec3 spec) {
vec3 r = reflect(LightDir,norm);
float dRate = max(dot(-LightDir,norm), 0.0);
float sRate = pow(max(dot(r,ViewDir),0.0),Material.Shininess);
vec3 ambient = Light.La * Material.Ka;
vec3 difusse = Light.Ld * Material.Kd * dRate;
vec3 specular = Light.Ls * Material.Ks * sRate;
return (ambient + difusse)*tex + specular*spec;
}
void main() {
vec4 normal = vec4(0.0,0.0,1.0,1.0);
if(NormalMapping) normal = 2.0*texture(NormalMapTex,TexCoord)-1.0;
vec4 texColor = texture( ColorTex, TexCoord );
vec4 specColor = texture(SpecMapTex, TexCoord);
FragColor = vec4(ads(normal.xyz, texColor.rgb, specColor.rgb),Material.Dissolved);
}
|
|
|
La clase CGMaterial
|
|
En esta práctica hemos incluido algunas modificaciones a la
definición de los materiales. Por un lado, es necesario incluir la
textura asociada al NormalMap. Además, se ha incluido la
textura asociada a la la luz especular y el campo Dissolved
para almacenar el valor de la opacidad del material. El contenido
del archivo de cabecera de la clase CGMaterial es el
siguiente:
#pragma once
#include <glm/glm.hpp>
#include "CGShaderProgram.h"
class CGMaterial {
private:
glm::vec3 Ka; // Reflectividad ambiental (color ante la luz ambiental)
glm::vec3 Kd; // Reflectividad difusa (color ante la luz difusa)
glm::vec3 Ks; // Reflectividad especular (color ante la luz especular)
GLfloat Shininess; // Factor de brillo (comportamiento ante la luz especular)
GLfloat Dissolved; // Opacidad (1.0 opaco, 0.0 transparente)
GLuint textureId; // Identificador de la textura básica
GLuint normalmapId; // Identificador de la textura de normales
GLuint specmapId; // Identificador de la textura de luz especular
public:
CGMaterial();
void SetAmbientReflect(GLfloat r, GLfloat g, GLfloat b);
void SetDifusseReflect(GLfloat r, GLfloat g, GLfloat b);
void SetSpecularReflect(GLfloat r, GLfloat g, GLfloat b);
void SetShininess(GLfloat f);
void SetDissolved(GLfloat d);
void SetTexture(GLuint id);
void SetNormalMap(GLuint id);
void SetSpecMap(GLuint id);
GLuint LoadTexture(char* filename);
GLuint LoadTexture(int idr);
void SetUniforms(ShaderProgram* program);
GLuint GetTexture();
GLuint GetNormalMap();
GLuint GetSpecMap();
};
|
Los nuevos campos tienen asociados sus respectivos métodos Get() y
Set(). Para no repetir los métodos de inicialización de ficheros de
textura, se han sustituido los métodos InitTexture() por
los
métodos LoadTexture() que cargan la textura desde un fichero
externo o desde un recurso y crean un
identificador que puede asociarse tanto a la textura principal como
a la textura especular y al normal map. También es necesario
modificar el constructor para introducir los valores por defecto de
los nuevos campos y el método SetUniforms() para asignar
las propiedades del material al programa gráfico.
#include "CGMaterial.h"
#include <GL/glew.h>
#include <FreeImage.h>
//
// FUNCIÓN: CGMaterial::CGMaterial()
//
// PROPÓSITO: Construye un material con los valores por defecto
//
CGMaterial::CGMaterial()
{
Ka = glm::vec3(1.0f, 1.0f, 1.0f);
Kd = glm::vec3(1.0f, 1.0f, 1.0f);
Ks = glm::vec3(0.8f, 0.8f, 0.8f);
Shininess = 16.0f;
Dissolved = 1.0f;
textureId = 0;
normalmapId = 0;
specmapId = 0;
}
//
// FUNCIÓN: GLuint CGMaterial::LoadTexture(const char* filename)
//
// PROPÓSITO: Carga una textura
//
GLuint CGMaterial::LoadTexture(const char* filename)
{
GLuint texId;
FREE_IMAGE_FORMAT format = FreeImage_GetFileType(filename, 0);
FIBITMAP* bitmap = FreeImage_Load(format, filename);
FIBITMAP *pImage = FreeImage_ConvertTo32Bits(bitmap);
int nWidth = FreeImage_GetWidth(pImage);
int nHeight = FreeImage_GetHeight(pImage);
glGenTextures(1, &texId);
glBindTexture(GL_TEXTURE_2D, texId);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, nWidth, nHeight,
0, GL_BGRA, GL_UNSIGNED_BYTE, (void*)FreeImage_GetBits(pImage));
FreeImage_Unload(pImage);
return texId;
}
//
// FUNCIÓN: GLuint CGMaterial::LoadTexture(int idr)
//
// PROPÓSITO: Carga una textura a partir de un recurso
//
GLuint CGMaterial::LoadTexture(int idr)
{
GLuint texId;
HRSRC handle = FindResource(NULL, MAKEINTRESOURCE(idr), L"IMAGE");
HGLOBAL hGlobal = LoadResource(NULL, handle);
LPCTSTR rsc_ptr = static_cast<LPCTSTR>(LockResource(hGlobal));
DWORD mem_size = SizeofResource(NULL, handle);
BYTE* mem_buffer = (BYTE*)malloc((mem_size) * sizeof(BYTE));
memcpy(mem_buffer, rsc_ptr, mem_size * sizeof(BYTE));
FreeResource(hGlobal);
FIMEMORY* hmem = FreeImage_OpenMemory(mem_buffer, mem_size * sizeof(BYTE));
FREE_IMAGE_FORMAT fif = FreeImage_GetFileTypeFromMemory(hmem, 0);
FIBITMAP* check = FreeImage_LoadFromMemory(fif, hmem, 0);
FIBITMAP* pImage = FreeImage_ConvertTo32Bits(check);
int nWidth = FreeImage_GetWidth(pImage);
int nHeight = FreeImage_GetHeight(pImage);
glGenTextures(1, &texId);
glBindTexture(GL_TEXTURE_2D, texId);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, nWidth, nHeight,
0, GL_BGRA, GL_UNSIGNED_BYTE, (void*)FreeImage_GetBits(pImage));
FreeImage_Unload(pImage);
FreeImage_CloseMemory(hmem);
free(mem_buffer);
return texId;
}
//
// FUNCIÓN: CGMaterial::SetUniforms(CGShaderProgram* program)
//
// PROPÓSITO: Configura las propiedades de material en el programa gráfico
//
void CGMaterial::SetUniforms(CGShaderProgram* program)
{
program->SetUniformVec3("Material.Ka", Ka);
program->SetUniformVec3("Material.Kd", Kd);
program->SetUniformVec3("Material.Ks", Ks);
program->SetUniformF("Material.Shininess", Shininess);
program->SetUniformF("Material.Dissolved", Dissolved);
program->SetUniformI("BaseTex", 0);
program->SetUniformI("NormalMapTex", 1);
program->SetUniformI("SpecMapTex", 2);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, textureId);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, normalmapId);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, specmapId);
}
...
|
|
|
La clase CGPiece
|
|
Para poder desarrollar la técnica de NormalMapping es necesario
incluir un nuevo atributo asociado a cada vértice con la información
del vector tangente en coordenadas locales. Esto supone modificar la
clase CGPiece para incluir el nuevo atributo. Las subclases de
CGPiece generan los valores de los atributos VertexPosition,
VertexNormal y VertexTexCoord. Los valores del nuevo atributo
(VertexTangent) deben calcularse analizando las primitivas que
forman la pieza para calcular el vector tangente en función de las
posiciones y coordenadas de textura, tal y como se explicó al
principio. El fichero de cabecera de la clase CGPiece queda así:
#pragma once
#include <GL/glew.h>
#include <glm/glm.hpp>
#include "CGMaterial.h"
#include "CGShaderProgram.h"
#define VERTEX_DATA 0
#define INDEX_DATA 1
#define NORMAL_DATA 2
#define TEXTURE_DATA 3
#define TANGENT_DATA 4
//
// CLASE: CGPiece
//
// DESCRIPCIÓN: Clase abstracta que representa una pieza descrita mediante
// VAO para su renderizado mediante shaders
//
class CGPiece {
protected:
GLushort* indexes; // Array of indexes
GLfloat* vertices; // Array of vertices
GLfloat* normals; // Array of normals
GLfloat* textures; // Array of texture coordinates
GLfloat* tangents; // Array of tangent vectors
GLuint numFaces; // Number of faces
GLuint numVertices; // Number of vertices
GLuint VBO[5];
GLuint VAO;
glm::mat4 location; // Model matrix
CGMaterial* material;
public:
~CGPiece();
void InitTangents();
void InitBuffers();
void SetMaterial(CGMaterial* m);
void SetLocation(glm::mat4 loc);
glm::mat4 GetLocation();
void Translate(glm::vec3 t);
void Rotate(GLfloat angle, glm::vec3 axis);
void Draw(CGShaderProgram* prog, glm::mat4 proj, glm::mat4 view, glm::mat4 model);
};
|
El código de la clase incluye un nuevo método llamado
InitTangents() dedicado a calcular los vectores tangentes.
Además, el método InitBuffers() debe modificarse para
incluir el nuevo atributo asociado a los vértices. Las
modificaciones al fichero de código de la clase
CGPiece son las siguientes:
#include "CGPiece.h"
#include <GL/glew.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include "CGMaterial.h"
//
// FUNCIÓN: CGPiece::~CGPiece()
//
// PROPÓSITO: Destructor de la pieza
//
CGPiece::~CGPiece()
{
if (vertices != NULL) delete[] vertices;
if (indexes != NULL) delete[] indexes;
if (normals != NULL) delete[] normals;
if (textures != NULL) delete[] textures;
if (tangents != NULL) delete[] tangents;
// Delete vertex buffer objects
glDeleteBuffers(5, VBO);
glDeleteVertexArrays(1, &VAO);
}
//
// FUNCIÓN: CGPiece::InitTangents()
//
// PROPÓSITO: Calcula los vectores tangente para realizar normal mapping
//
void CGPiece::InitTangents()
{
int* tangentCount = new int[numVertices];
for (int i = 0; i < numVertices; i++) tangentCount[i] = 0;
tangents = new GLfloat[3*numVertices];
for (int i = 0; i < 3*numVertices; i++) tangents[i] = 0.0;
for (int i = 0; i < numFaces; i++)
{
int a = indexes[3 * i];
int b = indexes[3 * i + 1];
int c = indexes[3 * i + 2];
GLfloat Ax = vertices[3 * a];
GLfloat Ay = vertices[3 * a + 1];
GLfloat Az = vertices[3 * a + 2];
GLfloat Bx = vertices[3 * b];
GLfloat By = vertices[3 * b + 1];
GLfloat Bz = vertices[3 * b + 2];
GLfloat Cx = vertices[3 * c];
GLfloat Cy = vertices[3 * c + 1];
GLfloat Cz = vertices[3 * c + 2];
GLfloat Au = textures[2 * a];
GLfloat Av = textures[2 * a + 1];
GLfloat Bu = textures[2 * b];
GLfloat Bv = textures[2 * b + 1];
GLfloat Cu = textures[2 * c];
GLfloat Cv = textures[2 * c + 1];
GLfloat E1x = Bx - Ax;
GLfloat E1y = By - Ay;
GLfloat E1z = Bz - Az;
GLfloat E2x = Cx - Bx;
GLfloat E2y = Cy - By;
GLfloat E2z = Cz - Bz;
GLfloat U1 = Bu - Au;
GLfloat V1 = Bv - Av;
GLfloat U2 = Cu - Bu;
GLfloat V2 = Cv - Bv;
GLfloat f = U1*V2 - U2*V1;
GLfloat Tx = (V2*E1x - V1*E2x) / f;
GLfloat Ty = (V2*E1y - V1*E2y) / f;
GLfloat Tz = (V2*E1z - V1*E2z) / f;
tangents[3*a] = (tangents[3*a]*tangentCount[a] + Tx)/(tangentCount[a]+1);
tangents[3*a+1] = (tangents[3*a+1]*tangentCount[a] + Ty) / (tangentCount[a]+1);
tangents[3*a+2] = (tangents[3*a+2]*tangentCount[a] + Tz) / (tangentCount[a]+1);
tangentCount[a]++;
tangents[3*b] = (tangents[3*b]*tangentCount[b] + Tx) / (tangentCount[b]+ 1);
tangents[3*b+1] = (tangents[3*b+1]*tangentCount[b] + Ty) / (tangentCount[b]+1);
tangents[3*b+2] = (tangents[3*b+2]*tangentCount[b] + Tz) / (tangentCount[b]+1);
tangentCount[b]++;
tangents[3*c] = (tangents[3*c]*tangentCount[c] + Tx) / (tangentCount[c]+1);
tangents[3*c+1] = (tangents[3*c+1]*tangentCount[c] + Ty) / (tangentCount[c]+1);
tangents[3*c+2] = (tangents[3*c+2]*tangentCount[c] + Tz) / (tangentCount[c]+1);
tangentCount[c]++;
}
delete[] tangentCount;
}
//
// FUNCIÓN: CGPiece::InitBuffers()
//
// PROPÓSITO: Crea el VAO y los VBO y almacena todos los datos
// en la GPU.
//
void CGPiece::InitBuffers()
{
InitTangents();
// Create the Vertex Array Object
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
// Create the Vertex Buffer Objects
glGenBuffers(5, VBO);
int bufferSize = sizeof(GLfloat)*numVertices * 3;
// Copy data to video memory
// Vertex data
glBindBuffer(GL_ARRAY_BUFFER, VBO[VERTEX_DATA]);
glBufferData(GL_ARRAY_BUFFER, bufferSize, vertices, GL_STATIC_DRAW);
// Normal data
glBindBuffer(GL_ARRAY_BUFFER, VBO[NORMAL_DATA]);
glBufferData(GL_ARRAY_BUFFER, bufferSize, normals, GL_STATIC_DRAW);
// Tangent data
glBindBuffer(GL_ARRAY_BUFFER, VBO[TANGENT_DATA]);
glBufferData(GL_ARRAY_BUFFER, bufferSize, tangents, GL_STATIC_DRAW);
// Texture coordinates
bufferSize = sizeof(GLfloat)*numVertices * 2;
glBindBuffer(GL_ARRAY_BUFFER, VBO[TEXTURE_DATA]);
glBufferData(GL_ARRAY_BUFFER, bufferSize, textures, 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;
delete[] normals;
delete[] textures;
delete[] tangents;
vertices = NULL;
indexes = NULL;
normals = NULL;
textures = NULL;
tangents = NULL;
glEnableVertexAttribArray(0); // Vertex position
glBindBuffer(GL_ARRAY_BUFFER, VBO[VERTEX_DATA]);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(1); // Vertex normals
glBindBuffer(GL_ARRAY_BUFFER, VBO[NORMAL_DATA]);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(2); // Vertex textures
glBindBuffer(GL_ARRAY_BUFFER, VBO[TEXTURE_DATA]);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(3); // Vertex tangents
glBindBuffer(GL_ARRAY_BUFFER, VBO[TANGENT_DATA]);
glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, 0, 0);
}
...
|
|
|
La clase CGScene
|
|
La escena a mostrar en este caso está formada por dos figuras
importadas de ficheros esternos. Se trata de las figuras
Bridge (un puente de piedra) y
Muro (un trabajador). La clase CGScene contiene
las dos figuras (figure0 y figure1) y un campo (figure)
para seleccionar cual de las dos figuras se muestra en cada momento.
La clase contiene además otro campo (normalMapping) que
permite activar o desactivar la técnica de normal mapping. El fichero de cabecera de la clase
CGScene queda así:
#pragma once
#include <GL/glew.h>
#include <glm/glm.hpp>
#include "CGShaderProgram.h"
#include "CGLight.h"
#include "CGObject.h"
class CGScene {
public:
CGScene();
~CGScene();
void Draw(CGShaderProgram* program, glm::mat4 proj, glm::mat4 view);
void ChangeFigure();
void ChangeMapping();
private:
CGObject* figure0;
CGObject* figure1;
CGLight* light;
int figure;
GLboolean normalMapping;
};
|
El código de la clase incluye dos nuevos métodos llamados
ChangeFigure(), dedicado cambiar la figura a mostrar, y el método
ChangeMapping(), dedicado a activar o desactivar la opción
de normal mapping. El fichero de código de la clase CGScene
es el siguiente:
#include "CGScene.h"
#include <GL/glew.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include "CGShaderProgram.h"
#include "CGObject.h"
#include "CGLight.h"
#include "Bridge.h"
#include "Muro.h"
//
// FUNCIÓN: CGScene::CGScene()
//
// PROPÓSITO: Construye la escena
//
CGScene::CGScene()
{
glm::vec3 Ldir = glm::vec3(1.0f, -0.8f, -1.0f);
Ldir = glm::normalize(Ldir);
light = new CGLight();
light->SetLightDirection(Ldir);
light->SetAmbientLight(glm::vec3(0.2f, 0.2f, 0.2f));
light->SetDifusseLight(glm::vec3(0.8f, 0.8f, 0.8f));
light->SetSpecularLight(glm::vec3(1.0f, 1.0f, 1.0f));
figure0 = new Bridge();
figure1 = new Muro();
figure = 0;
normalMapping = GL_TRUE;
}
//
// FUNCIÓN: CGScene::~CGScene()
//
// PROPÓSITO: Destruye el objeto que representa la escena
//
CGScene::~CGScene()
{
delete figure0;
delete figure1;
delete light;
}
//
// FUNCIÓN: CGScene::Draw()
//
// PROPÓSITO: Dibuja la escena
//
void CGScene::Draw(CGShaderProgram* program, glm::mat4 proj, glm::mat4 view)
{
light->SetUniforms(program);
if(normalMapping == GL_TRUE) program->SetUniformI("NormalMapping", 1);
else program->SetUniformI("NormalMapping", 0);
switch (figure)
{
case 0: figure0->Draw(program, proj, view); break;
case 1: figure1->Draw(program, proj, view); break;
}
}
//
// FUNCIÓN: CGScene::ChangeFigure()
//
// PROPÓSITO: Cambia la figura a mostrar
//
void CGScene::ChangeFigure()
{
figure = (figure + 1) % 2;
}
//
// FUNCIÓN: CGScene::ChangeMapping()
//
// PROPÓSITO: Activa o desactiva el normal mapping
//
void CGScene::ChangeMapping()
{
normalMapping = !normalMapping;
}
|
|
|
Resultado final
|
|
Para terminar se ha modificado el método KeyboardAction()
de la clase CGModel para que la aplicación cambie de figura
(utilizando la tecla 'F') o de forma de mapeo de la normal
(utilizando la tecla 'M'). A continuación se muestran capturas de
estas figuras activando o desactivando la opción de normal
mapping.
|
|