Escuela Técnica Superior de Ingeniería

 

Grado en Ingeniería Informática

Realidad Virtual

Curso 2023/2024

 

Práctica 8

Importando modelos externos

 

Objetivo

 

Los objetos que hemos representado hasta ahora son figuras geométricas cuyos atributos pueden ser calculados por medio de fórmulas matemáticas. Para representar objetos más complejos es necesario conocer los valores de los atributos y utilizarlos de la misma forma en que hemos representado las figuras geométricas. Existen numerosas herramientas de diseño gráfico dedicadas a modelar objetos 3D. En esta práctica vamos a estudiar un formato de representación de figuras 3D utilizado por muchas herramientas y vamos a desarrollar un analizador para poder incorporar estos diseños gráficos a nuestras aplicaciones.

Scene

 

 

Código de la práctica

 

 

Figuras compuestas de piezas

 

Los modelos representados en las prácticas anteriores estaban formados por objetos sencillos. Estos objetos, descritos por medio de figuras geométricas, utilizaban un único color y un único fichero de textura. Para considerar modelos más complejos hay que tener en cuenta que las figuras pueden estar compuestas de diferentes piezas. Estas piezas pueden tener colores y texturas diferentes y pueden estar colocadas en su propia posición descrita respecto del sistema de coordenadas de la figura a la que pertenecen.

Por ejemplo, podríamos generar una figura que describa un muñeco de nieve por medio de tres esferas de color blanco (colocadas una encima de otra). El sombrero lo podríamos generar por medio de un cilindro y un disco de color negro. La nariz podría ser un cono de color naranja.

Para considerar todas estas cuestiones vamos a modificar un poco las clases que hemos utilizado hasta ahora. Para describir las piezas vamos a defnir una nueva clase llamada CGPiece que contenga la lista de vértices incluidos en la pieza, así como una referencia al material. Esta clase debe incluir también una matriz de posición que realice la transformación entre las coordenadas locales de la pieza y las coordenadas locales de la figura. Asímismo vamos a definir la clase CGObject para representar figuras compuestas de piezas. Esta clase contendrá una lista de piezas y de materiales, así como la matriz de transformación entre el sistema de coordenadas de la figura y el sistema de coordenadas de la escena.

El fichero de cabecera de la clase CGPiece es el siguiente.

#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

//
// 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

  GLuint numFaces; // Number of faces
  GLuint numVertices; // Number of vertices
  GLuint VBO[4];
  GLuint VAO;

  glm::mat4 location; // Model matrix
  CGMaterial* material;

public:
  ~CGPiece();
  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* program, glm::mat4 projection, 
            glm::mat4 view, glm::mat4 model);
};

El código de la clase CGPiece es muy parecido al de la clase CGFigure presentado en la práctica anterior pero añade la matriz model al método Draw().

#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
//
Piece3D::~Piece3D()
{
  if (vertices != NULL) delete[] vertices;
  if (indexes != NULL) delete[] indexes;
  if (normals != NULL) delete[] normals;
  if (textures != NULL) delete[] textures;

  // Delete vertex buffer objects
  glDeleteBuffers(4, VBO);
  glDeleteVertexArrays(1, &VAO);
}

//
// FUNCIÓN: CGPiece::InitBuffers()
//
// PROPÓSITO: Crea el VAO y los VBO y almacena todos los datos
//            en la GPU.
//
void CGPiece::InitBuffers()
{
  // Create the Vertex Array Object
  glGenVertexArrays(1, &VAO);
  glBindVertexArray(VAO);

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

  // Copy data to video memory
  // Vertex data
  int buffsize = sizeof(GLfloat)*numVertices * 3;
  glBindBuffer(GL_ARRAY_BUFFER, VBO[VERTEX_DATA]);
  glBufferData(GL_ARRAY_BUFFER, buffsize, vertices, GL_STATIC_DRAW);

  // Normal data
  buffsize = sizeof(GLfloat) * numVertices * 3;
  glBindBuffer(GL_ARRAY_BUFFER, VBO[NORMAL_DATA]);
  glBufferData(GL_ARRAY_BUFFER, buffsize, normals, GL_STATIC_DRAW);

  // Texture coordinates
  buffsize =  sizeof(GLfloat) * numVertices * 2;
  glBindBuffer(GL_ARRAY_BUFFER, VBO[TEXTURE_DATA]);
  glBufferData(GL_ARRAY_BUFFER, buffsize, textures, GL_STATIC_DRAW);

  // Indexes
  buffsize = sizeof(GLushort)*numFaces * 3;
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, VBO[INDEX_DATA]);
  glBufferData(GL_ELEMENT_ARRAY_BUFFER, buffsize, indexes, GL_STATIC_DRAW);

  delete[] vertices;
  delete[] indexes;
  delete[] normals;
  delete[] textures;

  vertices = NULL;
  indexes = NULL;
  normals = NULL;
  textures = 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);
  
  location = glm::mat4(1.0f);
}

//
// FUNCIÓN: CGPiece::SetMaterial(CGMaterial* m)
//
// PROPÓSITO: Asigna el material de la pieza
//
void CGPiece::SetMaterial(CGMaterial* m)
{
  material = m;
}

//
// FUNCIÓN: CGPiece::SetLocation(glm::mat4 loc)
//
// PROPÓSITO: Asigna la matriz de posición de la pieza respecto a la posición de la
// figura a la que pertenece.
//
void CGPiece::SetLocation(glm::mat4 loc)
{
  location = loc;
}

//
// FUNCIÓN: CGPiece::GetLocation()
//
// PROPÓSITO: Obtiene la matriz de posición de la pieza respecto a la posición de la
// figura a la que pertenece.
//
glm::mat4 CGPiece::GetLocation()
{
  return location;
}

//
// FUNCIÓN: CGPiece::Translate(glm::vec3 t)
//
// PROPÓSITO: Añade un desplazamiento a la matriz de posición de la pieza 
// respecto a la posición de la figura a la que pertenece.
//
void CGPiece::Translate(glm::vec3 t)
{
  location = glm::translate(location, t);
}

//
// FUNCIÓN: CGPiece::Rotate(GLfloat angle, glm::vec3 axis)
//
// PROPÓSITO: Añade una rotación a la matriz de posición de la pieza 
// respecto a la posición de la figura a la que pertenece.
//
void CGPiece::Rotate(GLfloat angle, glm::vec3 axis)
{
  location = glm::rotate(location, glm::radians(angle), axis);
}

//
// FUNCIÓN: CGPiece::Draw()
//
// PROPÓSITO: Dibuja la pieza
//
void CGPiece::Draw(CGShaderProgram * program, glm::mat4 projection, 
                   glm::mat4 view, glm::mat4 model)
{
  glm::mat4 mvp = projection*view*model*location;
  program->SetUniformMatrix4("MVP", mvp);
  program->SetUniformMatrix4("ViewMatrix", view);
  program->SetUniformMatrix4("ModelViewMatrix", view*model*location);

  material->SetUniforms(program);

  glBindVertexArray(VAO);
  glDrawElements(GL_TRIANGLES, numFaces * 3, GL_UNSIGNED_SHORT, NULL);
}

Por su parte, la clase CGObject se define de la siguiente forma.

#pragma once

#include <GL/glew.h>
#include <glm/glm.hpp>
#include "CGShaderProgram.h"
#include "CGPiece.h"

//
// CLASE: CGObject
//
// DESCRIPCIÓN: Clase abstracta que representa un objeto 
//              formado por varias piezas
// 
class CGObject {
protected:
  glm::mat4 model; // Model matrix

public:
  void ResetLocation();
  void Translate(glm::vec3 t);
  void Rotate(GLfloat angle, glm::vec3 axis);
  void SetLocation(glm::mat4 loc);
  glm::mat4 GetLocation();
  void Draw(CGShaderProgram* program, glm::mat4 projection, glm::mat4 view);

  virtual int GetNumPieces()=0;
  virtual CGPiece* GetPiece(int i)=0;
};

El código de la clase CGObject se muestra a continuación.

#include "CGObject.h"
#include <GL/glew.h>
#include <FreeImage.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>

//
// FUNCIÓN: CGObject::ResetLocation()
//
// PROPÓSITO: Asigna la posición inicial del objecto 
//
void CGObject::ResetLocation()
{
  model = glm::mat4(1.0f);
}

//
// FUNCIÓN: CGObject::SetLocation(glm::mat4 loc)
//
// PROPÓSITO: Asigna la posición del objecto 
//
void CGObject::SetLocation(glm::mat4 loc)
{
  model = loc;
}

//
// FUNCIÓN: CGObject::GetLocation()
//
// PROPÓSITO: Obtiene la posición del objecto 
//
glm::mat4 CGObject::GetLocation()
{
  return model;
}

//
// FUNCIÓN: CGObject::Translate(glm::vec3 t)
//
// PROPÓSITO: Añade un desplazamiento a la matriz de posición del objeto 
//
void CGObject::Translate(glm::vec3 t)
{
  model = glm::translate(model, t);
}

//
// FUNCIÓN: CGObject::Rotate(GLfloat angle, glm::vec3 axis)
//
// PROPÓSITO: Añade una rotación a la matriz de posición del objeto 
//
void CGObject::Rotate(GLfloat angle, glm::vec3 axis)
{
  model = glm::rotate(model, glm::radians(angle), axis);
}

//
// FUNCIÓN: CGObject::Draw(CGShaderProgram * program, 
//                         glm::mat4 projection, glm::mat4 view)
//
// PROPÓSITO: Dibuja el objeto
//
void CGObject::Draw(CGShaderProgram * program, glm::mat4 projection, glm::mat4 view)
{
  int num = GetNumPieces();
  for (int i = 0; i < num; i++)
  {
    GetPiece(i)->Draw(program, projection, view, model);
  }
}

 

 

El formato Wavefront OBJ

 

La compañía Wavefront Technologies Ltd. fue fundada en 1984 y se dedicaba al desarrollo de software de diseño gráfico. En 1995 Silicon Graphics adquirió las empresas Wavefront Technologies y Alias Research formando una nueva empresa llamada Alias/Wavefront. En 2006 la empresa fue absorbida por Autodesk. Actualmente el producto más conocido de esta empreesa es Maya, considerado como una de las mejores aplicaciones de diseño gráfico.

El formato OBJ fue desarrollado por Wavefront para su software Advanced Visualizer. Existen varios formatos de descripción de modelos 3D que corresponden a distintas aplicaciones gráficas (3ds, c4d, blend, max, ...). El formato OBJ tiene la ventaja de ser un formato libre (no propietario), estar descrito en modo texto y estar definido por medio de un lenguaje formal, lo que facilita su incorporación en muchas aplicaciones gráficas. El siguiente enlace contiene la definición formal del formato OBJ (enlace).

A continuación se presenta un ejemplo de especificación en este formato.

# List of geometric vertices, with (x,y,z[,w]) coordinates,
# w is optional and defaults to 1.0.
 
v 0.123 0.234 0.345 1.0
v ...
...
 
# List of texture coordinates, in (u, v [,w]) coordinates, these will vary 
# between 0 and 1, w is optional and defaults to 0.
 
vt 0.500 1 [0]
vt ...
...
 
# List of vertex normals in (x,y,z) form; normals might not be unit vectors.

vn 0.707 0.000 0.707
vn ...
...

# Parameter space vertices in ( u [,v] [,w] ) form; free form geometry statement

vp 0.310000 3.210000 2.100000
vp ...
...

# Polygonal face element
f 1 2 3
f 3/1 4/2 5/3
f 6/4/1 3/5/3 7/6/5
f 7//1 8//2 9//3
f ...
...

# Line element
l 5 8 1 2 4 9
l ...
...

Algunas características del formato son las siguientes:

  • Los comentarios comienzan con el símbolo # y terminan en el final de línea.

  • Las posiciones de los vértices comienzan con el símbolo v seguido de las componentes x,y,z y w. La cuarta componente es opcional y por defecto su valor es 1.0.

  • Las coordenadas de textura de los vértices comienzan con el símbolo vt seguido de las componentes s,t y r. La tercera componente es opcional.

  • Los vectores normales comienzan con el símbolo vn seguido de las componentes x,y,z.

  • La directiva vp permite definir parámetros asociados a vértices, que suelen utilizarse en las primitivas patch utilizadas en el shader de teselado.

  • La directiva f describe polígonos. Generalmente se utiliza para definir triángulos aunque el número de vértices puede ser mayor. En ese caso se puede interpretar la directiva como un triangle_fan que genera el polígono.

  • Los vértices se describen mediante índices a la lista de posiciones, coordenadas de textura y normales. Los índices pueden ser positivos (indicando una numeración desde el primer elemento) o negativos (indicando una numeración desde el último elemento). El índice del primer elemento es 1. Los índices a la coordenada de textura y a la normal son opcionales.

  • La directiva l describe líneas formadas por una lista de vértices. Se interpreta como primitivas LINE_STRIP.

  • La directiva p describe puntos. Se interpreta como primitivas POINTS.

  • El formato permite definir también formas libres. Estas formas pueden ser curvas, superficies y curvas sobre superficies. Para generar estas formas libres se pueden utilizar cinco métodos diferentes: Bezier, basis matrix, B-spline, Cardinal y Taylor.

  • Los diferentes elementos (polígonos, líneas, puntos y formas libres) se pueden agrupar en objetos (definidos mediante una directiva o seguida de un nombre opcional) y grupos (definidos mediante una directiva g seguida de un nombre opcional).

  • La especificación del material de un objeto se introduce por medio de la directiva usemtl seguida de un identificador. El identificador debe corresponder a alguno de los materiales incluidos en la biblioteca de materiales.

  • La biblioteca de materiales se define por medio da la directiva mtllib seguida del nombre del fichero en el que se definen los materiales.

Los materiales se describen en ficheros externos que suelen tener la extensión .mtl. El formato MTL es también un formato libre en modo texto. A continuación se presenta un ejemplo de descripción de materiales en este formato:

newmtl Textured
   Ka 1.000 1.000 1.000
   Kd 1.000 1.000 1.000
   Ks 0.000 0.000 0.000
   d 1.0
   illum 2
   map_Ka lemur.tga           # the ambient texture map
   map_Kd lemur.tga           # the diffuse texture map (most of the time, it will
                              # be the same as the ambient texture map)
   map_Ks lemur.tga           # specular color texture map
   map_Ns lemur_spec.tga      # specular highlight component
   map_d lemur_alpha.tga      # the alpha texture map
   map_bump lemur_bump.tga    # normal mapping

Las directivas utilizadas para definir materiales son las siguientes:

  • newmtl nombre: Crea un nuevo material. Las directivas introducidas a continuación (hasta la declaración del siguiente material o el final del fichero) están dedicadas a configurar las propiedades del material. Para utilizar el material en los archivos OBJ se utiliza la directiva usemtl indicando el mismo nombre.

  • Ka  r g b: Define el valor de la reflectividad ambiental. Los valores (r,g,b) definen las componentes de color con valores entre 0 y 1.

  • Kd r g b: Define el valor de la reflectividad difusa. Los valores (r,g,b) definen el color.

  • Ks r g b: Define el valor dela reflectividad especular. Los valores (r,g,b) definen el color.

  • d  valor: Define el valor de la opacidad, es decir, del canal alpha. Este campo se conoce como dissolved.

  • Tr valor: Define el valor de la transparencia. Es una forma alternativa de definir la opacidad. El valor del canal alpha se define como 1-Tr.

  • Ns valor: Define el valor del factor de brillo, es decir, del exponente aplicado al efecto especular.

  • Ni valor: Define el índice de refracción.

  • Tf r g b: Define el color de un filtro de transmisión.

  • illum código: Define el modelo de ilumniación.

  • map_Ka  fichero: Define el fichero utilizado como textura sobre el efecto ambiental.

  • map_Kd  fichero: Define el fichero utilizado como textura sobre el efecto difuso. Suele coincidir con el ambiental.

  • map_Ks  fichero: Define el fichero utilizado como textura sobre el efecto especular.

  • map_Ns  fichero: Define una textura que contiene el factor de brillo.

  • map_d  fichero: Define una textura utilizada como valores de transparencia.

  • map_bump  fichero: Define el fichero de textura utilizado como normal map.

  • bump  fichero: Equivalente a la directiva map_bump.

  • disp  fichero: Define una textura utilizada como mapa de desplazamiento.

  • decal  fichero: Define una textura utilizada como plantilla para ciertos efectos.

  • refl options  fichero: Define el modelo y la imagen utilizada para incorporar efectos de reflexión.

La directiva illum define el modelo de iluminación asignado al material. Los valores posibles son los siguientes:

0. Color on and Ambient off
1. Color on and Ambient on
2. Highlight on
3. Reflection on and Ray trace on
4. Transparency: Glass on, Reflection: Ray trace on
5. Reflection: Fresnel on and Ray trace on
6. Transparency: Refraction on, Reflection: Fresnel off and Ray trace on
7. Transparency: Refraction on, Reflection: Fresnel on and Ray trace on
8. Reflection on and Ray trace off
9. Transparency: Glass on, Reflection: Ray trace off
10. Casts shadows onto invisible surfaces

Las directivas de textura y reflexión pueden incluir diferentes opciones. Una descripción detallada del formato MTL con todas sus opciones puede encontrarse en este enlace (formato mtl).

 

 

Analizadores para archivos OBJ y MTL

 

Para desarrollar los analizadores de los ficheros OBJ y MTL vamos a seguir el método descrito en la asignatura "Procesadores de Lenguajes". En esta asignatura se explica como desarrollar analizadores semánticos para lenguajes formales y se utiliza la herramienta JavaCC para desarrollar estos analizadores en lenguaje Java. Siguiendo esta técnica vamos a crear una aplicación Java que lea un fichero OBJ y genere los archivos ".cpp" y ".h" compatibles con las definiciones que hemos desarrollado para nuestros proyectos gráficos.

Los archivos contenidos en esta aplicación Java son los siguientes:

  • VertexPosition: Clase que describe la posición de un vértice. Se trata de un contenedor con los campos (x,y,z). Corresponde a la información incluida en una declaración "v   x y z [w]" de un fichero OBJ.

  • VertexNormal: Clase que describe el vector normal de un vértice. Se trata de un contenedor con los campos (x,y,z). Corresponde a la información incluida en una declaración "vn  x y z" de un fichero OBJ.

  • VertexTexCoord: Clase que describe las coordenadas de textura de un vértice. Se trata de un contenedor con los campos (s,t,r). Corresponde a la información incluida en una declaración "vt  s t [r]" de un fichero OBJ.

  • VertexIndex: Clase que describe la referencia a un vértice. En el formato OBJ los vértices se referencian con los índices a la posición, coordenadas de textura y normal descritos previamente. Los índices pueden ser positivos (indicando una numeración desde el primer elemento) o negativos (indicando una numeración desde el último elemento). Además los índices a la coordenada de textura y a la normal son opcionales. La clase VertexIndex almacena la información incluida en declaraciones de tipo "v/vt/vn" o "v/vt" o "v//vn" o "v".

  • AttribVertex: Clase que describe los atributos de un vértice. Dado que OpenGL utiliza un único índice para referenciar toda la información de un vértice, es necesario transformar las listas de posiciones, normales y texturas en listas de atributos (que posiblemente repitan información). Esta clase contiene la información de la posición, coordenadas de textura y vector normal correspondiente a un vértice y añade el índice asociado al vértice (que será utilizado en el array de índices de la pieza).

  • Face: Clase que describe una primitiva (un triángulo). Es un contenedor que almacena los tres índices correspondientes a los objetos VertexIndex que describen los vértices del triángulo. Corresponde a la información incluida en una declaración de tipo "f   v1/vt1/vn1 v2/vt2/vn2  v3/vt3/vn3" de un fichero OBJ.

  • Piece: Clase que describe una pieza. Está formada por una lísta de vértices (objetos AttribVertex) y una lista de primitivas (objetos Face). Contiene además una referencia al material (objeto Material) que compone la pieza. Los objetos Piece corresponden a las declaraciones de grupo "g nombre" o de objeto "o nombre" incluidas en un fichero OBJ.

  • Material: Clase que describe un material. Se trata de un contenedor que almacena los campos Ka (reflectividad ambiental), Kd (reflectividad difusa), Ks (reflectividad especular), Ns (factor especular), d (opacidad), illum (tipo de iluminación), texture (fichero de textura) y normalmap (fichero de textura normalmap). Contiene además dos identificadores: name (identificador utilizado en los ficheros OBJ y MTL) y code (identificador utilizado en el código C++ a generar). Los materiales se describen en los ficheros MTL (directiva newmtl) y se utilizan en los ficheros OBJ (directiva usemtl).

  • MaterialLib: Clase que describe una biblioteca de materiales. Contiene la lista de materiales descritos en un fichero MTL.

  • Figure: Clase que describe una figura definida en un fichero OBJ. Está compuesta por una lista de objetos VertexPosition (correspondiente a las directivas v), una lista de objetos VertexNormal (correspondiente a las directivas vn), una lista de objetos VertexTexCoord (correspondiente a las directivas vt), una lista de objetos MaterialLib (correspondiente a las directivas mtllib) y una lista de objetos Piece (correspondientes a las directivas o y g). La clase Figure contiene una indicación de la última pieza declarada (campo active) de manera que las directivas f permiten añadir una primitiva a la pieza activa y la directiva usemtl permite asignar el material a la pieza activa.

  • MtlParser.jj: Analizador de ficheros MTL descrito en el formato de la herramienta JavaCC. El analizador generado permite leer un archivo MTL y crear el objeto MaterialLib con la biblioteca de materiales descrita en ese archivo.

  • ObjParser.jj: Analizador de ficheros OBJ descrito en el formato de la herrameinta JavaCC. El analizador generado permite leer un archivo OBJ y crear el objeto Figure con la figura descrita en ese archivo.

  • CGObjectParser: Clase principal de la aplicación. Contiene el método main() que toma como argumento de entrada en nombre del fichero OBJ a analizar y la escala. Este método crea un analizador ObjParser, obtiene el objeto Figure correspondiente al modelo descrito en el fichero OBJ y genera los archivos ".cpp" y ".h" que pueden incluirse en los proyectos que estamos desarrollando.

La aplicación se distribuye como un archivo llamado ObjParser.jar. El formato de llamada de la aplicación es el siguiente:

java -jar ObjParser.jar Figura.obj escala

El argumento escala es un número real que se aplicará como factor de escala en los valores de las posiciones de los vértices. Por ejemplo, si el fichero OBJ describe un vehículo en escala real y queremos un objeto en escala 1:32 hay que realizar la llamada con un factor de escala de 0.03125. El resultado de la aplicación consiste en cuatro archivos: Figura.h (fichero de cabecera de la figura descrita en el archivo OBJ), Figura.cpp (fichero de código de la figura descrita en el archivo OBJ), Figura_pieces.h (fichero de cabecera del conjunto de piezas de la figura) y Figura_pieces.cpp (fichero de código del conjunto de piezas de la figura).

 

 

El programa gráfico

 

El VertexShader utilizado para mostrar los objetos es el mismo que ya se introdujo al presentar las texturas.

 #version 400

layout(location = 0) in vec3 VertexPosition;
layout(location = 1) in vec3 VertexNormal;
layout(location = 2) in vec2 VertexTexCoord;

uniform mat4 MVP;
uniform mat4 ViewMatrix;
uniform mat4 ModelViewMatrix;

out vec3 Position;
out vec3 Normal;
out vec2 TexCoord;

void main()
{
  vec4 n4 = ModelViewMatrix*vec4(VertexNormal, 0.0);
  vec4 v4 = ModelViewMatrix*vec4(VertexPosition, 1.0);
  Normal = vec3(n4);
  Position = vec3(v4);
  TexCoord = VertexTexCoord;
  gl_Position = MVP * vec4(VertexPosition, 1.0);
}

Por su parte el FragmentShader se ha modificado para tener en cuenta el canal de transparencia de las texturas. Cuando la textura de una pieza incluye un valor distinto de 1.0 en el canal alpha hay que considerar que la pieza es traslúcida e incorporar este valor al color final de cada fragmento. Para evitar que un valor 0 se traduzca en una pieza totalmente transparente (que, por tanto, no se vería) se ha escalado la transparencia entre 0.5 y 1.0.

#version 400

in vec3 Position;
in vec3 Normal;
in vec2 TexCoord;

uniform sampler2D BaseTex;
uniform mat4 ViewMatrix;
struct LightInfo {
  vec3 Ldir;
  vec3 La;
  vec3 Ld;
  vec3 Ls;
};
uniform LightInfo Light;

struct MaterialInfo{
  vec3 Ka;
  vec3 Kd;
  vec3 Ks;
  float Shininess;
};
uniform MaterialInfo Material;

out vec4 FragColor;

vec3 ads(vec3 TexColor) {
  vec4 s4 = ViewMatrix*vec4(Light.Ldir, 0.0);
  vec3 n = normalize(Normal);
  vec3 v = normalize(-Position);
  vec3 s = normalize(-vec3(s4));
  vec3 r = reflect(-s, n);
  float dRate = max(dot(s, n), 0.0);
  float sRate = pow(max(dot(r, v), 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)*TexColor + specular;
}

void main()
{
  vec4 TexColor = texture(BaseTex,TexCoord);
  vec3 Color = ads(TexColor.rgb);
  FragColor = vec4(Color, (TexColor.a+1)/2);
}

La clase CGModel también se ha tenido que modificar para poder tratar las transparencias. Para ello simplemente se ha actualizado el método initialize(), activando la opción GL_BLEND y configurando la función de mezcla.

//
// 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_FILL);
  glEnable(GL_BLEND);
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}