|
Grado en Ingeniería Informática
Animación por Ordenador
Curso 2025/2026
|
Práctica 6
|
|
Color e
iluminación
|
|
Objetivos
|
|
En esta práctica se presenta un modelo de iluminación que permite
añadirle color a los objetos situados en la escena. El modelo
planteado se conoce como modelo de Phong o modelo ADS, ya que
calcula el color de cada punto como la suma de tres componentes:
ambiental, difusa y especular. El resultado final es similar al
presentado en la práctica anterior, pero dotando de color a los
cuerpos representados.

|
|
El modelo de iluminación de Phong
|
|
Vulkan utiliza el formato RGBA para representar el color. Esto permite describir un color por medio de un vector de 4 componentes.
Generalmente cada componente se representa por medio de un valor float normalizado. De esta forma, el color negro se
representa mediante el vector (0.0, 0.0, 0.0, 1.0) y el color blanco mediante el vector (1.0, 1.0, 1.0, 1.0). En las prácticas
anteriores el FragmentShader se limitaba a asignar el color rojo a todos los píxeles del fragmento. Para generar figuras
de colores diferentes una opción sencilla es introducir el color del objeto por medio de una variable uniforme. El problema
es que esto dibuja las figuras con un color plano, lo que resulta muy poco realista. Para generar un coloreado natural es
necesario estudiar la forma en la que la luz ilumina a las diferentes figuras. Es decir, es necesario plantear un modelo de
iluminación.
El modelo de iluminación de Phong fue propuesto por Bui Tuong Phong en su tesis doctoral, presentada en 1973 en la Universidad de Utah.
Este modelo constituye una mejora del modelo de Gouraud, que fue propuesto en 1971. Estos modelos se basan en calcular el color con
el que se ve un objeto como el resultado de la interacción de la luz (de un cierto color) con el material que forma el objeto (descrito
también por un color). Si los colores se expresan en coordenadas normalizadas, el resultado de iluminar un cuerpo de un color M
con una luz de un color L es
Color = M · L = (Mr, Mg, Mb, Ma) · (Lr, Lg, Lb, La)
= (Mr·Lr, Mg·Lg, Mb·Lb, Ma·La)
Esta ecuación permite modificar el color con el que se ve un objeto al modificar el color de la luz. Sin embargo sigue generando el
mismo color para todos los puntos del objeto (es decir, un color plano) lo que resulta antinatural. Para conseguir un coloreado
más natural se considera que el color final es la combinación de tres efectos: luz ambiental, luz difusa y luz especular.
La luz ambiental se considera como una luz que proviene de todas las direcciones y tras incidir en un punto se dispersa en todas las
direcciones. Este es el tipo de luz que ilumina la zona de sombra de un objeto (la zona de sombra nunca es totalmente negra) y su
fórmula corresponde a la descrita anteriormente.
La luz difusa se considera como una luz que proviene de una dirección concreta y tras incidir en un punto se dispersa en todas las
direcciones. El efecto de esta luz tiene en cuenta la dirección de incidencia, de manera que si incide verticalmente sobre el objeto
la iluminación es más intensa mientras que si incide de forma inclinada la intensidad es menor. El factor de incidencia se calcula
como el coseno del ángulo que forma la dirección de la luz (Ldir) con el vector normal a la superficie (N).
Si ambos vectores son unitarios el coseno corresponde al producto
escalar. El
efecto se calcula como:
Color = M · L · Intensidad = (M·L) * (Ldir·N)
La luz especular se considera como una luz que proviene de una dirección concreta y tras incidir en un punto se refleja, dispersándose
principalmente en la dirección reflejada. El efecto de esta luz tiene en cuenta la dirección de incidencia y la normal (para calcular
la dirección reflejada) y la posición del observador. Si la dirección reflejada coincide con la dirección en la que se encuentra el
observador la intensidad de este efecto será máxima mientras que si la dirección reflejada se separa de la dirección del observador
la intensidad disminuye. El factor de intensidad especular se calcula como el coseno del ángulo entre la dirección reflejada
(Reflect) y la
dirección del observador (Pos), elevado a un cierto coeficiente de brillo (Shininess).
Color = M · L · Intensidad = (M·L) * (Reflect·Pos)^Shininess
Reflect = 2· (Ldir·N) · N - Ldir
El color final en el que se dibuja cada punto es la suma de los tres efectos.
Color = Color_ambiental + Color_difuso + Color_especular
|
|
El programa gráfico
|
|
Para desarrollar el modelo de ilumniación de Phong es necesario calcular para cada píxel el valor de la normal a la superficie
y la posición del punto asociado a ese pixel. Estos vectores deben describirse en coordenadas del observador. Para obtener estos
vectores en cada pixel, el Vertex Shader debe generarlos como salidas asociadas a cada vértice de forma que el proceso de interpolación
genere los valores correspondientes a cada punto del fragmento. Por tanto, los atributos asociados a cada vértice deben ser su posición
y su normal. La salida gl_Position contiene la posición en coordenadas homogeneas (multiplicando por la matriz MVP como
vimos en la práctica anterior). Las salidas Position y Normal contienen los vectores de posición y normal en coordenadas
del observador (multiplicando en este caso por la matriz model-view).
#version 450
#extension GL_ARB_separate_shader_objects : enable
layout(set = 0, binding = 0) uniform TransformInfo {
mat4 MVP;
mat4 ViewMatrix;
mat4 ModelViewMatrix;
} Transform;
layout(location = 0) in vec3 inPosition;
layout(location = 1) in vec3 inNormal;
layout(location = 0) out vec3 Position;
layout(location = 1) out vec3 Normal;
void main()
{
vec4 n4 = Transform.ModelViewMatrix*vec4(inNormal, 0.0);
vec4 v4 = Transform.ModelViewMatrix*vec4(inPosition,1.0);
Normal = vec3(n4);
Position = vec3(v4);
gl_Position = Transform.MVP * vec4(inPosition, 1.0);
}
|
El Fragment Shader debe desarrollar los calculos del modelo de
iluminación de Phong a partir de la interpolación de la posición y
la normal (en coordenadas del observador) y de las propiedades de la
luz y del material. Las propiedades de la luz se introducen por
medio de un bloque uniforme (Light) que responde a una
estructura con cuatro campos (Ldir es la dirección de la
luz, La el color de la luz ambiental, Ld es el
color de la luz difusa y Ls es el color de la luz
especular). Las propiedades del material se introducen por medio de
un bloque uniforme (Material) que responde a una
estructura con cuatro campos (Ka es el color del material
frente a la luz ambiental, Kd es el color frente a la luz
difusa, Ks es el color frente a la luz especular y
Shininess es el coeficiente de brillo). La función auxiliar
ads() desarrolla el modelo de Phong calculando los tres
efectos: ambiental, difusa y especular.
#version 450
#extension GL_ARB_separate_shader_objects : enable
layout(location = 0) in vec3 Position;
layout(location = 1) in vec3 Normal;
layout(location = 0) out vec4 outColor;
layout(set=0, binding = 0) uniform TransformInfo {
mat4 MVP;
mat4 ViewMatrix;
mat4 ModelViewMatrix;
} Transform;
layout(set=0, binding = 1) uniform MaterialInfo
{
vec3 Ka;
vec3 Kd;
vec3 Ks;
float Shininess;
} Material;
layout(set=0, binding = 2) uniform LightInfo
{
vec3 Ldir;
vec3 La;
vec3 Ld;
vec3 Ls;
} Light;
vec3 ads()
{
vec4 s4 = Transform.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 + specular;
}
void main()
{
vec3 Color = ads();
outColor = vec4(Color, 1.0);
}
|
|
|
Las estructuras GEVertex, GETransform,
GELight y GEMaterial
|
|
El modelo de iluminación requiere que los vértices almacenen como
atributos no solo la posición sino también el vector normal a la
superficie del objeto en ese vértice. Esto requiere modificar la
estructura GEVertex para incorporar el nuevo atributo.
#pragma once
#include <glm\common.hpp>
typedef struct
{
glm::vec3 pos;
glm::vec3 norm;
} GEVertex;
|
Los shaders diseñados para desarrollar el modelo de iluminación de
Phong utilizan tres bloques de variables uniformes. El bloque
destinado a las matrices de transformación se almacenará en una
estructura GETransform. La estructura ya estaba definida en
proyectos anteriores, pero ahora es necesario modificarla para
añadir las matrices ViewMatrix y ModelViewMatrix.
#pragma once
#include <glm/glm.hpp>
typedef struct
{
alignas(16) glm::mat4 MVP;
alignas(16) glm::mat4 ViewMatrix;
alignas(16) glm::mat4 ModelViewMatrix;
} GETransform;
|
Para describir las propiedades de la luz se va a crear una nueva
estructura denominada GELight.
La estructura cuenta con los campos utilizados en el modelo de Phong. El
campo Ldir almacena la dirección de la luz en coordenadas
del modelo. Los campos La, Ld y Ls
contienen el color de las componentes ambiental, difusa y especular.
#pragma once
#include <glm/glm.hpp>
typedef struct
{
alignas(16) glm::vec3 Ldir; // Light direction
alignas(16) glm::vec3 La; // Ambient intensity
alignas(16) glm::vec3 Ld; // Difusse intensity
alignas(16) glm::vec3 Ls; // Specular intensity
} GELight;
|
Para describir las propiedades de los
materiales se va a crear una nueva estructura denominada GEMaterial.
La estructura cuenta con los campos utilizados en el modelo de Phong. Los campos
Ka, Kd y Ks contienen la reflectividad
del material ante las componentes de luz ambiental, difusa y
especular. El campo Shininess contiene el factor de brillo.
#pragma once
#include <glm/glm.hpp>
typedef struct
{
alignas(16) glm::vec3 Ka; // Ambient reflectivity
alignas(16) glm::vec3 Kd; // Difusse reflectivity
alignas(16) glm::vec3 Ks; // Specular reflectivity
alignas(4) float Shininess; // Specular shininess factor
} GEMaterial;
|
|
|
Modificaciones de la clase GEFigure
|
|
Para considerar los efectos de iluminación es necesario añadir a las figuras la información respecto a las propiedades
de la luz y del material.
Para descibir las propiedades de la luz se ha definido la
estructura GELight. Para describir las propiedades
del material se ha incluido la estructura GEMaterial. Estas
nuevas propiedades requieren almacenarse en buffers vinculados a
conjuntos de descriptores. También es necesario que las figuras almacenen el valor del vector normal (perpendicular) a la
superficie en cada vértice, además de la posición de cada vértice. Esto supone realizar cambios sobre la clase
GEFigure.
#pragma once
#include "GEGraphicsContext.h"
#include "GERenderingContext.h"
#include "GEVertex.h"
#include "GETransform.h"
#include "GEMaterial.h"
#include "GELight.h"
#include "GEVertexBuffer.h"
#include "GEIndexBuffer.h"
#include "GEUniformBuffer.h"
#include "GEDescriptorSet.h"
#include <glm/glm.hpp>
#include <vector>
//
// CLASE: GEFigure
//
// DESCRIPCIÓN: Clase que describe una figura formada por una malla de vértices
//
class GEFigure
{
protected:
std::vector<GEVertex> vertices;
std::vector<uint16_t> indices;
glm::mat4 location;
GEMaterial material;
GELight light;
public:
void initialize(GEGraphicsContext* gc, GERenderingContext* rc);
void destroy(GEGraphicsContext* gc);
void addCommands(VkCommandBuffer commandBuffer, VkPipelineLayout pipelineLayout,
int index);
void update(GEGraphicsContext* gc, uint32_t index, glm::mat4 view,
glm::mat4 projection);
void resetLocation();
void setLocation(glm::mat4 m);
void translate(glm::vec3 t);
void rotate(float angle, glm::vec3 axis);
void setMaterial(GEMaterial m);
void setLight(GELight l);
private:
GEVertexBuffer* vbo;
GEIndexBuffer* ibo;
GEUniformBuffer* transformBuffer;
GEUniformBuffer* materialBuffer;
GEUniformBuffer* lightBuffer;
GEDescriptorSet* dset;
};
|
El método initialize() se modifica para crear los
buffers asociados a la luz y el material y considerarlos al
crear los conjuntos de descriptores. El método destroy()
se modifica para incluir la eliminación de los nuevos buffers. El
método update() se modifica para incluir la
asignación de los valores de las nuevas matrices de transformación,
de la luz y del material. Se han añadido los métodos setLight()
para asignar los valores de la luz y setMaterial()
para asignar las propiedades del material.
#include "GEFigure.h"
#include "GEVertex.h"
#include "GETransform.h"
#include "GEMaterial.h"
#include "GELight.h"
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <iostream>
//
// FUNCIÓN: GEFigure::initialize(GEGraphicsContext* gc, GERenderingContext* rc)
//
// PROPÓSITO: Crea la figura
//
void GEFigure::initialize(GEGraphicsContext* gc, GERenderingContext* rc)
{
size_t vertexSize = sizeof(GEVertex) * vertices.size();
vbo = new GEVertexBuffer(gc, vertexSize, vertices.data());
size_t indexSize = sizeof(indices[0]) * indices.size();
ibo = new GEIndexBuffer(gc, indexSize, indices.data());
size_t transformBufferSize = sizeof(GETransform);
transformBuffer = new GEUniformBuffer(gc, rc->imageCount, transformBufferSize);
size_t materialBufferSize = sizeof(GEMaterial);
materialBuffer = new GEUniformBuffer(gc, rc->imageCount, materialBufferSize);
size_t lightBufferSize = sizeof(GELight);
lightBuffer = new GEUniformBuffer(gc, rc->imageCount, lightBufferSize);
std::vector<GEUniformBuffer*> ubos(3);
ubos[0] = transformBuffer;
ubos[1] = materialBuffer;
ubos[2] = lightBuffer;
dset = new GEDescriptorSet(gc, rc, ubos);
location = glm::mat4(1.0f);
}
//
// FUNCIÓN: GEFigure::destroy(GEGraphicsContext* gc)
//
// PROPÓSITO: Libera los buffers de la figura
//
void GEFigure::destroy(GEGraphicsContext* gc)
{
vbo->destroy(gc);
ibo->destroy(gc);
transformBuffer->destroy(gc);
materialBuffer->destroy(gc);
lightBuffer->destroy(gc);
dset->destroy(gc);
delete vbo;
delete ibo;
delete transformBuffer;
delete materialBuffer;
delete lightBuffer;
delete dset;
}
//
// FUNCIÓN: CAFigure::addCommands(VkCommandBuffer commandBuffer, int index)
//
// PROPÓSITO: Añade los comandos de renderizado al command buffer
//
void GEFigure::addCommands(VkCommandBuffer commandBuffer,
VkPipelineLayout pipelineLayout,
int index)
{
VkDeviceSize offset = 0;
vkCmdBindVertexBuffers(commandBuffer, 0, 1, &(vbo->buffer), &offset);
vkCmdBindIndexBuffer(commandBuffer, ibo->buffer, 0, VK_INDEX_TYPE_UINT16);
vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
pipelineLayout, 0, 1, &(dset->descriptorSets[index]),
0, nullptr);
vkCmdDrawIndexed(commandBuffer, (uint32_t)indices.size(), 1, 0, 0, 0);
}
//
// FUNCIÓN: GEFigure::update(...)
//
// PROPÓSITO: Actualiza las variables uniformes sobre una imagen del swapchain
//
void GEFigure::update(GEGraphicsContext* gc, uint32_t index, glm::mat4 view,
glm::mat4 projection)
{
GETransform transform;
transform.MVP = projection * view * location;
transform.ModelViewMatrix = view * location;
transform.ViewMatrix = view;
transformBuffer->update(gc, index, sizeof(GETransform), &transform);
materialBuffer->update(gc, index, sizeof(GEMaterial), &material);
lightBuffer->update(gc, index, sizeof(GELight), &light);
}
//
// FUNCIÓN: GEFigure::resetLocation()
//
// PROPÓSITO: Resetea la matriz de localización (Model).
//
void GEFigure::resetLocation()
{
location = glm::mat4(1.0f);
}
//
// FUNCIÓN: GEFigure::setLocation()
//
// PROPÓSITO: Resetea la matriz de localización (Model).
//
void GEFigure::setLocation(glm::mat4 m)
{
location = glm::mat4(m);
}
//
// FUNCIÓN: GEFigure::translate(glm::vec3 t)
//
// PROPÓSITO: Añade un desplazamiento la matriz de localización (Model).
//
void GEFigure::translate(glm::vec3 t)
{
location = glm::translate(location, t);
}
//
// FUNCIÓN: GEFigure::rotate(float angle, glm::vec3 axis)
//
// PROPÓSITO: Añade una rotación la matriz de localización (Model).
//
void GEFigure::rotate(float angle, glm::vec3 axis)
{
location = glm::rotate(location, glm::radians(angle), axis);
}
//
// FUNCIÓN: GEFigure::setMaterial(GEMaterial m)
//
// PROPÓSITO: Asigna las propiedades del material del que está formada la figura
//
void GEFigure::setMaterial(GEMaterial m)
{
this->material = m;
}
//
// FUNCIÓN: GEFigure::setLight(GELight l)
//
// PROPÓSITO: Asigna las propiedades de la luz que ilumina la figura
//
void GEFigure::setLight(GELight l)
{
this->light = l;
}
|
|
|
Modificaciones de la clase GEScene
|
|
La escena a representar en el modelo se describe en la clase GEScene. La estructura de esta clase no sufre cambios con respecto a
la versión de la práctica anterior.
#pragma once
#include "GEGraphicsContext.h"
#include "GEDrawingContext.h"
#include "GECommandContext.h"
#include "GERenderingContext.h"
#include "GEFigure.h"
#include "GECamera.h"
#include <vulkan/vulkan.h>
#include <glm/glm.hpp>
#include <vector>
//
// CLASE: GEScene
//
// DESCRIPCIÓN: Clase que describe una escena
//
class GEScene
{
private:
GERenderingContext* rc;
std::vector<GEFigure*> figures;
GECamera* camera;
glm::mat4 projection;
public:
GEScene(GEGraphicsContext* gc, GEDrawingContext* dc, GECommandContext* cc);
void destroy(GEGraphicsContext* gc);
void recreate(GEGraphicsContext* gc, GEDrawingContext* dc, GECommandContext* cc);
void update(GEGraphicsContext* gc, uint32_t index);
void key_action(int key, bool pressed);
void aspect_ratio(double aspect);
private:
void fillCommandBuffers(std::vector<VkCommandBuffer> commandBuffers);
GEPipelineConfig* createPipelineConfig(VkExtent2D extent);
};
|
Con respecto al código, los cambios se producen en el constructor de la clase
y en la configuración del proceso de renderizado.
El constructor, además de
generar las figuras y posicionarlas, debe inicializar los valores de
la fuente de luz y de las propiedades de material de cada figura. El
método createPipelineConfig() debe definir ahora un
pipeline con dos atributos y tres variables uniformes.
//
// FUNCIÓN: GEScene::GEScene(GEGraphicsContext* gc, GEDrawingContext* dc)
//
// PROPÓSITO: Crea la escena
//
GEScene::GEScene(GEGraphicsContext* gc, GEDrawingContext* dc, GECommandContext* cc)
{
VkExtent2D extent = dc->getExtent();
double aspect = (double)extent.width / (double)extent.height;
aspect_ratio(aspect);
GEPipelineConfig* config = createPipelineConfig(dc->getExtent());
rc = new GERenderingContext(gc, dc, config);
this->camera = new GECamera();
this->camera->setPosition(glm::vec3(0.0f, 10.0f, 300.0f));
this->camera->setMoveStep(0.0f);
GELight light = {};
light.Ldir = glm::normalize(glm::vec3(1.0f, -0.8f, -0.7f));
light.La = glm::vec3(0.2f, 0.2f, 0.2f);
light.Ld = glm::vec3(0.8f, 0.8f, 0.8f);
light.Ls = glm::vec3(1.0f, 1.0f, 1.0f);
GEMaterial groundMat = {};
groundMat.Ka = glm::vec3(0.0f, 0.3f, 0.0f);
groundMat.Kd = glm::vec3(0.0f, 0.3f, 0.0f);
groundMat.Ks = glm::vec3(0.8f, 0.8f, 0.8f);
groundMat.Shininess = 16.0f;
GEFigure* ground = new GEGround(50.0f, 50.0f);
ground->initialize(gc, rc);
ground->setMaterial(groundMat);
ground->setLight(light);
GEMaterial fig1Mat = {};
fig1Mat.Ka = glm::vec3(1.0f, 0.0f, 1.0f);
fig1Mat.Kd = glm::vec3(1.0f, 0.0f, 1.0f);
fig1Mat.Ks = glm::vec3(0.8f, 0.8f, 0.8f);
fig1Mat.Shininess = 16.0f;
GEFigure* fig1 = new GECone(5, 20, 5.0f, 5.0f);
fig1->initialize(gc, rc);
fig1->translate(glm::vec3(25.0f, 5.0f, 25.0f));
fig1->rotate(-90.0f, glm::vec3(1.0f, 0.0f, 0.0f));
fig1->setMaterial(fig1Mat);
fig1->setLight(light);
GEMaterial fig2Mat = {};
fig2Mat.Ka = glm::vec3(0.0f, 1.0f, 0.0f);
fig2Mat.Kd = glm::vec3(0.0f, 1.0f, 0.0f);
fig2Mat.Ks = glm::vec3(0.8f, 0.8f, 0.8f);
fig2Mat.Shininess = 16.0f;
GEFigure* fig2 = new GECube(5.0f);
fig2->initialize(gc, rc);
fig2->translate(glm::vec3(-25.0f, 5.0f, 25.0f));
fig2->rotate(-90.0f, glm::vec3(1.0f, 0.0f, 0.0f));
fig2->setMaterial(fig2Mat);
fig2->setLight(light);
GEMaterial fig3Mat = {};
fig3Mat.Ka = glm::vec3(0.0f, 0.0f, 1.0f);
fig3Mat.Kd = glm::vec3(0.0f, 0.0f, 1.0f);
fig3Mat.Ks = glm::vec3(0.8f, 0.8f, 0.8f);
fig3Mat.Shininess = 16.0f;
GEFigure* fig3 = new GECylinder(20, 20, 5.0f, 5.0f);
fig3->initialize(gc, rc);
fig3->translate(glm::vec3(25.0f, 5.0f, 0.0f));
fig3->rotate(90.0f, glm::vec3(1.0f, 0.0f, 0.0f));
fig3->setMaterial(fig3Mat);
fig3->setLight(light);
GEMaterial fig4Mat = {};
fig4Mat.Ka = glm::vec3(1.0f, 0.0f, 0.0f);
fig4Mat.Kd = glm::vec3(1.0f, 0.0f, 0.0f);
fig4Mat.Ks = glm::vec3(0.8f, 0.8f, 0.8f);
fig4Mat.Shininess = 16.0f;
GEFigure* fig4 = new GETorus(20, 40, 3.0f, 5.0f);
fig4->initialize(gc, rc);
fig4->translate(glm::vec3(-25.0f, 8.0f, 0.0f));
fig4->setMaterial(fig4Mat);
fig4->setLight(light);
GEMaterial fig5Mat = {};
fig5Mat.Ka = glm::vec3(1.0f, 1.0f, 0.0f);
fig5Mat.Kd = glm::vec3(1.0f, 1.0f, 0.0f);
fig5Mat.Ks = glm::vec3(0.8f, 0.8f, 0.8f);
fig5Mat.Shininess = 16.0f;
GEFigure* fig5 = new GESphere(20, 40, 8.0f);
fig5->initialize(gc, rc);
fig5->translate(glm::vec3(25.0f, 8.0f, -25.0f));
fig5->setMaterial(fig5Mat);
fig5->setLight(light);
GEMaterial fig6Mat = {};
fig6Mat.Ka = glm::vec3(0.0f, 1.0f, 1.0f);
fig6Mat.Kd = glm::vec3(0.0f, 1.0f, 1.0f);
fig6Mat.Ks = glm::vec3(0.8f, 0.8f, 0.8f);
fig6Mat.Shininess = 16.0f;
GEFigure* fig6 = new GEIcosahedron(5.0f);
fig6->initialize(gc, rc);
fig6->translate(glm::vec3(-25.0f, 8.0f, -25.0f));
fig6->setMaterial(fig6Mat);
fig6->setLight(light);
figures.resize(7);
figures[0] = ground;
figures[1] = fig1;
figures[2] = fig2;
figures[3] = fig3;
figures[4] = fig4;
figures[5] = fig5;
figures[6] = fig6;
fillCommandBuffers(cc->commandBuffers);
}
//
// FUNCIÓN: GEScene::createPipelineConfig()
//
// PROPÓSITO: Obtiene la configuración del pipeline de renderizado
//
GEPipelineConfig* GEScene::createPipelineConfig(VkExtent2D extent)
{
GEPipelineConfig* config = new GEPipelineConfig();
config->vertex_shader = IDR_HTML1;
config->fragment_shader = IDR_HTML2;
config->attrStride = sizeof(GEVertex);
config->attrOffsets.resize(2);
config->attrOffsets[0] = offsetof(GEVertex, pos);
config->attrOffsets[1] = offsetof(GEVertex, norm);
config->attrFormats.resize(2);
config->attrFormats[0] = VK_FORMAT_R32G32B32_SFLOAT;
config->attrFormats[1] = VK_FORMAT_R32G32B32_SFLOAT;
config->descriptorTypes.resize(3);
config->descriptorTypes[0] = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
config->descriptorTypes[1] = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
config->descriptorTypes[2] = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
config->descriptorStages.resize(3);
config->descriptorStages[0] = VK_SHADER_STAGE_ALL_GRAPHICS;
config->descriptorStages[1] = VK_SHADER_STAGE_FRAGMENT_BIT;
config->descriptorStages[2] = VK_SHADER_STAGE_FRAGMENT_BIT;
config->depthTestEnable = VK_TRUE;
config->cullMode = VK_CULL_MODE_BACK_BIT;
config->extent = extent;
return config;
}
|
|
|
Figuras geométricas
|
|
Con respecto a las diferentes figuras geométricas, es necesario
modificar su código para incorporar a los atributos de los vértices
el valor del vector normal en cada vértice.
El código asociado al cubo
es el siguiente.
#include "GECube.h"
GECube::GECube(float s)
{
vertices = {
{{ +s, +s, +s }, {1.0f, 0.0f, 0.0f}}, // A0 // Positive X
{{ +s, -s, +s }, {1.0f, 0.0f, 0.0f}}, // D0
{{ +s, -s, -s }, {1.0f, 0.0f, 0.0f}}, // D1
{{ +s, +s, -s }, {1.0f, 0.0f, 0.0f}}, // A1
{{ -s, +s, +s }, {0.0f, 1.0f, 0.0f}}, // B0 // Positive Y
{{ +s, +s, +s }, {0.0f, 1.0f, 0.0f}}, // A0
{{ +s, +s, -s }, {0.0f, 1.0f, 0.0f}}, // A1
{{ -s, +s, -s }, {0.0f, 1.0f, 0.0f}}, // B1
{{ -s, -s, +s }, {-1.0f, 0.0f, 0.0f}}, // C0 // Negative X
{{ -s, +s, +s }, {-1.0f, 0.0f, 0.0f}}, // B0
{{ -s, +s, -s }, {-1.0f, 0.0f, 0.0f}}, // B1
{{ -s, -s, -s }, {-1.0f, 0.0f, 0.0f}}, // C1
{{ +s, -s, +s }, {0.0f, -1.0f, 0.0f}}, // D0 // Negative Y
{{ -s, -s, +s }, {0.0f, -1.0f, 0.0f}}, // C0
{{ -s, -s, -s }, {0.0f, -1.0f, 0.0f}}, // C1
{{ +s, -s, -s }, {0.0f, -1.0f, 0.0f}}, // D1
{{ +s, +s, +s }, {0.0f, 0.0f, 1.0f}}, // A0 // Positive Z
{{ -s, +s, +s }, {0.0f, 0.0f, 1.0f}}, // B0
{{ -s, -s, +s }, {0.0f, 0.0f, 1.0f}}, // C0
{{ +s, -s, +s }, {0.0f, 0.0f, 1.0f}}, // D0
{{ +s, +s, -s }, {0.0f, 0.0f, -1.0f}}, // A1 // Negative Z
{{ +s, -s, -s }, {0.0f, 0.0f, -1.0f}}, // D1
{{ -s, -s, -s }, {0.0f, 0.0f, -1.0f}}, // C1
{{ -s, +s, -s }, {0.0f, 0.0f, -1.0f}} // B1
};
indices = { // Array of indexes
0, 1, 2,
0, 2, 3,
4, 5, 6,
4, 6, 7,
8, 9, 10,
8, 10, 11,
12, 13, 14,
12, 14, 15,
16, 17, 18,
16, 18, 19,
20, 21, 22,
20, 22, 23
};
}
|
El código asociado a
la pirámide es el siguiente.
#include "GEPyramid.h"
GEPyramid::GEPyramid(float s)
{
glm::vec2 n = glm::normalize(glm::vec2(2.0f, 1.0f));
vertices = {
{{ 0.0f, 0.0f, s }, {n.x, 0.0f, n.y}}, // E // Positive X
{{ s, -s, -s }, {n.x, 0.0f, n.y}}, // B
{{ s, s, -s }, {n.x, 0.0f, n.y}}, // A
{{ 0.0f, 0.0f, s }, {0.0f, n.x, n.y}}, // E // Positive Y
{{ s, s, -s }, {0.0f, n.x, n.y}}, // A
{{ -s, s, -s }, {0.0f, n.x, n.y}}, // D
{{ 0.0f, 0.0f, s }, { -n.x, 0.0f, n.y}}, // E // Negative X
{{ -s, s, -s }, { -n.x, 0.0f, n.y }}, // D
{{ -s, -s, -s }, { -n.x, 0.0f, n.y}}, // C
{{ 0.0f, 0.0f, s }, {0.0f, -n.x, n.y}}, // E // Negative Y
{{ -s, -s, -s }, {0.0f, -n.x, n.y}}, // C
{{ s, -s, -s }, {0.0f, -n.x, n.y}}, // B
{{ s, s, -s }, {0.0f, 0.0f, -1.0f}}, // A // Negative Z
{{ s, -s, -s }, {0.0f, 0.0f, -1.0f}}, // B
{{ -s, -s, -s }, {0.0f, 0.0f, -1.0f}}, // C
{{ -s, s, -s }, {0.0f, 0.0f, -1.0f}} // D
};
indices = { // Array of indexes
0, 1, 2,
3, 4, 5,
6, 7, 8,
9, 10, 11,
12, 13, 14,
12, 14, 15
};
}
|
El código del constructor del cono
es el siguiente.
#include "GECone.h"
GECone::GECone(GLint p, GLint m, GLfloat h, GLfloat r)
{
int numFaces = 2 * p * m; // Number of faces
int numVertices = (p + 1)*m + 2; // Number of vertices
vertices.resize(numVertices);
indices.resize(numFaces * 3);
double module = sqrt(4 * h*h + r * r);
double xyN = (float)(2 * h / module);
double zN = (float)(r / module);
int verticesIndex = 0;
// Centro de la base
vertices[0] = { { 0.0f, 0.0f, -h} , {0.0f, 0.0f,-1.0f} };
verticesIndex++;
// Vértices de la base
for (int i = 0; i < m; i++)
{
float x = (float)cos(glm::radians(360.0f * i / m));
float y = -(float)sin(glm::radians(360.0f * i / m));
vertices[verticesIndex] = { {x * r , y * r, -h}, {0.0f, 0.0f, -1.0f} };
verticesIndex++;
}
// Extremo del cono
vertices[verticesIndex] = { {0.0f, 0.0f, h}, {0.0f, 0.0f, 1.0f} };
verticesIndex++;
// Vértices de los lados
for (int i = 1; i <= p; i++)
{
float xy = i * r / p;
float z = h - 2 * i*h / p;
for (int j = 0; j < m; j++)
{
float xN = (float)cos(glm::radians(360.0f * j / m));
float yN = (float)sin(glm::radians(360.0f * j / m));
float x = (float)(xN*xy);
float y = (float)(yN*xy);
vertices[verticesIndex] = { { x, y, z}, {xN*xyN, yN*xyN, zN} };
verticesIndex++;
}
}
int indicesIndex = 0;
// Base
for (int i = 0; i < m - 1; i++)
{
indices[indicesIndex] = 0;
indices[indicesIndex + 1] = i + 1;
indices[indicesIndex + 2] = i + 2;
indicesIndex += 3;
}
indices[indicesIndex] = 0;
indices[indicesIndex + 1] = m;
indices[indicesIndex + 2] = 1;
indicesIndex += 3;
// Extremo
for (int i = 0; i < m - 1; i++)
{
indices[indicesIndex] = m + 1;
indices[indicesIndex + 1] = m + 2 + i;
indices[indicesIndex + 2] = m + 3 + i;
indicesIndex += 3;
}
indices[indicesIndex] = m + 1;
indices[indicesIndex + 1] = 2 * m + 1;
indices[indicesIndex + 2] = m + 2;
indicesIndex += 3;
// Lados
for (int j = 1; j < p; j++)
{
for (int i = 0; i < m - 1; i++)
{
indices[indicesIndex] = j * m + 2 + i;
indices[indicesIndex + 1] = (j + 1)*m + 2 + i;
indices[indicesIndex + 2] = (j + 1)*m + 3 + i;
indicesIndex += 3;
indices[indicesIndex] = j * m + 2 + i;
indices[indicesIndex + 1] = (j + 1)*m + 3 + i;
indices[indicesIndex + 2] = j * m + 3 + i;
indicesIndex += 3;
}
indices[indicesIndex] = (j + 1)*m + 1;
indices[indicesIndex + 1] = (j + 2)*m + 1;
indices[indicesIndex + 2] = (j + 1)*m + 2;
indicesIndex += 3;
indices[indicesIndex] = (j + 1)*m + 1;
indices[indicesIndex + 1] = (j + 1)*m + 2;
indices[indicesIndex + 2] = j * m + 2;
indicesIndex += 3;
}
}
|
El constructor del cilindro
queda así:
#include "GECylinder.h"
GECylinder::GECylinder(GLint p, GLint m, GLfloat r, GLfloat l)
{
int numFaces = 2 * m * (p + 1); // Number of faces
int numVertices = (m + 1)*(p + 3); // Number of vertices
vertices.resize(numVertices);
indices.resize(numFaces * 3);
int verticesIndex = 0;
int indicesIndex = 0;
/* northern polar cap*/
vertices[0] = { {0.0f, 0.0f, l },{0.0f, 0.0f, 1.0f }};
verticesIndex++;
for (int j = 0; j < m; j++)
{
float mCos = (float)cos(glm::radians(360.0f * j / m));
float mSin = (float)sin(glm::radians(360.0f * j / m));
vertices[verticesIndex] = { { mCos * r, mSin * r, l}, {0.0f, 0.0f, 1.0f} };
verticesIndex++;
indices[indicesIndex] = 0; // center
indices[indicesIndex + 1] = j + 1;
indices[indicesIndex + 2] = (j + 2 > m ? 1 : j + 2);
indicesIndex += 3;
}
/* southern polar cap*/
vertices[verticesIndex] = { { 0.0f, 0.0f, -l},{0.0f, 0.0f, -1.0f }};
verticesIndex++;
for (int j = 0; j < m; j++)
{
float mCos = (float)cos(glm::radians(360.0f * j / m));
float mSin = (float)sin(glm::radians(360.0f * j / m));
vertices[verticesIndex] = { { mCos * r, -mSin * r, -l }, {0.0f, 0.0f, -1.0f} };
verticesIndex++;
indices[indicesIndex] = m + 1; // center
indices[indicesIndex + 1] = j + m + 2;
indices[indicesIndex + 2] = (j + 2 > m ? m + 2 : j + m + 3);
indicesIndex += 3;
}
/* body */
for (int i = 0; i <= p; i++)
{
for (int j = 0; j <= m; j++)
{
float mCos = (float)cos(glm::radians(360.0f * j / m));
float mSin = (float)sin(glm::radians(360.0f * j / m));
vertices[verticesIndex] = { { mCos*r, mSin*r,l -2*l*i/p}, {mCos, mSin, 0.0f}};
verticesIndex++;
}
}
int base = 2 * m + 2;
for (int i = 0; i < p; i++)
{
for (int j = 0; j < m; j++)
{
indices[indicesIndex] = base + (m + 1)*i + j;
indices[indicesIndex + 1] = base + (m + 1)*(i + 1) + j;
indices[indicesIndex + 2] = base + (m + 1)*(i + 1) + j + 1;
indicesIndex += 3;
indices[indicesIndex] = base + (m + 1)*i + j;
indices[indicesIndex + 1] = base + (m + 1)*(i + 1) + j + 1;
indices[indicesIndex + 2] = base + (m + 1)*i + j + 1;
indicesIndex += 3;
}
}
}
|
El constructor de la esfera
sería este:
#include "GESphere.h"
GESphere::GESphere(GLint p, GLint m, GLfloat r)
{
int numFaces = 2 * m*(p - 1); // Number of faces
int numVertices = (m + 1)*(p + 1); // Number of vertices
vertices.resize(numVertices);
indices.resize(numFaces * 3);
int verticesIndex = 0;
int indicesIndex = 0;
/* northern polar cap*/
for (int j = 0; j <= m; j++)
{
vertices[verticesIndex] = { { 0.0f, 0.0f, r }, {0.0f, 0.0f, 1.0f} };
verticesIndex++;
}
for (int i = 1; i < p; i++)
{
for (int j = 0; j <= m; j++)
{
float pCos = (float)cos(glm::radians(180.0f * i / p));
float pSin = (float)sin(glm::radians(180.0f * i / p));
float mCos = (float)cos(glm::radians(360.0f * j / m));
float mSin = (float)sin(glm::radians(360.0f * j / m));
vertices[verticesIndex] = { { pSin*mCos*r, pSin*mSin*r, pCos*r},
{ pSin*mCos, pSin*mSin, pCos} };
verticesIndex++;
}
}
/* southern polar cap*/
for (int j = 0; j <= m; j++)
{
vertices[verticesIndex] = { { 0.0f, 0.0f, -r}, {0.0f, 0.0f, -1.0f} };
verticesIndex++;
}
/* northern polar cap*/
for (int j = 0; j < m; j++)
{
indices[indicesIndex] = j;
indices[indicesIndex + 1] = m + j + 1;
indices[indicesIndex + 2] = m + j + 2;
indicesIndex += 3;
}
for (int i = 1; i < p - 1; i++)
{
for (int j = 0; j < m; j++)
{
indices[indicesIndex] = i * (m + 1) + j;
indices[indicesIndex + 1] = (i + 1)*(m + 1) + j;
indices[indicesIndex + 2] = i * (m + 1) + j + 1;
indices[indicesIndex + 3] = (i + 1)*(m + 1) + j;
indices[indicesIndex + 4] = (i + 1)*(m + 1) + j + 1;
indices[indicesIndex + 5] = i * (m + 1) + j + 1;
indicesIndex += 6;
}
}
for (int j = 0; j < m; j++)
{
indices[indicesIndex] = (p - 1)*(m + 1) + j;
indices[indicesIndex + 1] = p * (m + 1) + j;
indices[indicesIndex + 2] = (p - 1)*(m + 1) + j + 1;
indicesIndex += 3;
}
}
|
El código para construir un toro
es el siguiente.
#include "GETorus.h"
GETorus::GETorus(GLint p, GLint m, GLfloat r0, GLfloat r1)
{
int numFaces = 2 * m * p; // Number of faces
int numVertices = (m + 1)*(p + 1); // Number of vertices
vertices.resize(numVertices);
indices.resize(numFaces * 3);
int verticesIndex = 0;
int indicesIndex = 0;
for (int i = 0; i <= m; i++)
{
for (int j = 0; j <= p; j++)
{
float pCos = (float)cos(glm::radians(360.0f * j / p));
float pSin = (float)sin(glm::radians(360.0f * j / p));
float mCos = (float)cos(glm::radians(360.0f * i / m));
float mSin = (float)sin(glm::radians(360.0f * i / m));
vertices[verticesIndex] = {{(r1 + r0*pCos)*mCos, (r1 + r0*pCos)*mSin, r0*pSin},
{pCos*mCos, pCos*mSin, pSin} };
verticesIndex++;
}
}
for (int i = 0; i < m; i++)
{
for (int j = 0; j < p; j++)
{
indices[indicesIndex] = (p + 1)*i + j;
indices[indicesIndex + 1] = (p + 1)*(i + 1) + j;
indices[indicesIndex + 2] = (p + 1)*(i + 1) + j + 1;
indicesIndex += 3;
indices[indicesIndex] = (p + 1)*i + j;
indices[indicesIndex + 1] = (p + 1)*(i + 1) + j + 1;
indices[indicesIndex + 2] = (p + 1)*i + j + 1;
indicesIndex += 3;
}
}
}
|
El icosahedro
se construye con el siguiente código.
#include "GEIcosahedron.h"
GEIcosahedron::GEIcosahedron(float r)
{
int numFaces = 20; // Number of faces
int numVertices = 60; // Number of vertices
vertices.resize(numVertices);
indices.resize(numFaces * 3);
float phi = (float)((1 + sqrt(5.0)) / 2.0);
float mod = (float)sqrt(6 + 9 * phi);
float p0M = (float)phi / mod;
float p1M = (float)(phi + 1) / mod;
float p2M = (float)(2 * phi + 1) / mod;
glm::vec3 N1 = { -p0M, p2M, 0.0f };
glm::vec3 N2 = { -p1M, p1M, p1M };
glm::vec3 N3 = { 0.0f, p0M, p2M };
glm::vec3 N4 = { p1M, p1M, p1M };
glm::vec3 N5 = { p0M, p2M, 0.0f };
glm::vec3 N6 = { p2M, 0.0f, -p0M };
glm::vec3 N7 = { p2M, 0.0f, p0M };
glm::vec3 N8 = { p1M, -p1M, p1M };
glm::vec3 N9 = { p0M, -p2M, 0.0f };
glm::vec3 N10 = { p1M, -p1M, -p1M };
glm::vec3 N11 = { p1M, p1M, -p1M };
glm::vec3 N12 = { 0.0f, -p0M, p2M };
glm::vec3 N13 = { 0.0f, p0M, -p2M };
glm::vec3 N14 = { -p1M, p1M, -p1M };
glm::vec3 N15 = { -p2M, 0.0f, p0M };
glm::vec3 N16 = { -p1M, -p1M, p1M };
glm::vec3 N17 = { -p2M, 0.0f, -p0M };
glm::vec3 N18 = { 0.0f, -p0M, -p2M };
glm::vec3 N19 = { -p1M, -p1M, -p1M };
glm::vec3 N20 = { -p0M, -p2M, 0.0f };
glm::vec3 A0 = { 0.0f, r*phi, r };
glm::vec3 A1 = { 0.0f, r*phi, -r };
glm::vec3 A2 = { 0.0f, -r * phi, -r };
glm::vec3 A3 = { 0.0f, -r * phi, r };
glm::vec3 B0 = { r*phi, r, 0.0f };
glm::vec3 B1 = { r*phi, -r, 0.0f };
glm::vec3 B2 = { -r * phi, -r, 0.0f };
glm::vec3 B3 = { -r * phi, r, 0.0f };
glm::vec3 C0 = { r, 0.0f, r*phi };
glm::vec3 C1 = { r, 0.0f, -r * phi };
glm::vec3 C2 = { -r, 0.0f, -r * phi };
glm::vec3 C3 = { -r, 0.0f, r*phi };
// face 1
vertices[0] = { A0, N1 };
vertices[1] = { A1, N1 };
vertices[2] = { B3, N1 };
// face 2
vertices[3] = { A0, N2 };
vertices[4] = { B3, N2 };
vertices[5] = { C3, N2 };
// face 3
vertices[6] = { A0, N3 };
vertices[7] = { C3, N3 };
vertices[8] = { C0, N3 };
// face 4
vertices[9] = { A0, N4 };
vertices[10] = { C0, N4 };
vertices[11] = { B0, N4 };
// face 5
vertices[12] = { A0, N5 };
vertices[13] = { B0, N5 };
vertices[14] = { A1, N5 };
// face 6
vertices[15] = { B1, N6 };
vertices[16] = { C1, N6 };
vertices[17] = { B0, N6 };
// face 7
vertices[18] = { B1, N7 };
vertices[19] = { B0, N7 };
vertices[20] = { C0, N7 };
// face 8
vertices[21] = { B1, N8 };
vertices[22] = { C0, N8 };
vertices[23] = { A3, N8 };
// face 9
vertices[24] = { B1, N9 };
vertices[25] = { A3, N9 };
vertices[26] = { A2, N9 };
// face 10
vertices[27] = { B1, N10 };
vertices[28] = { A2, N10 };
vertices[29] = { C1, N10 };
// face 11
vertices[30] = { B0, N11 };
vertices[31] = { C1, N11 };
vertices[32] = { A1, N11 };
// face 12
vertices[33] = { C0, N12 };
vertices[34] = { C3, N12 };
vertices[35] = { A3, N12 };
// face 13
vertices[36] = { A1, N13 };
vertices[37] = { C1, N13 };
vertices[38] = { C2, N13 };
// face 14
vertices[39] = { A1, N14 };
vertices[40] = { C2, N14 };
vertices[41] = { B3, N14 };
// face 15
vertices[42] = { C3, N15 };
vertices[43] = { B3, N15 };
vertices[44] = { B2, N15 };
// face 16
vertices[45] = { C3, N16 };
vertices[46] = { B2, N16 };
vertices[47] = { A3, N16 };
// face 17
vertices[48] = { B3, N17 };
vertices[49] = { C2, N17 };
vertices[50] = { B2, N17 };
// face 18
vertices[51] = { C2, N18 };
vertices[52] = { C1, N18 };
vertices[53] = { A2, N18 };
// face 19
vertices[54] = { C2, N19 };
vertices[55] = { A2, N19 };
vertices[56] = { B2, N19 };
// face 20
vertices[57] = { A2, N20 };
vertices[58] = { A3, N20 };
vertices[59] = { B2, N20 };
for (int i = 0; i < numVertices; i++) indices[i] = i;
}
|
El rectángulo utilizado como suelo se describe así.
#include "GEGround.h"
GEGround::GEGround(float l1, float l2)
{
vertices = {
{{ l1, 0.0f, l2 }, {0.0f, 1.0f, 0.0f}},
{{ l1, 0.0f, -l2 }, {0.0f, 1.0f, 0.0f} },
{{ -l1, 0.0f, -l2 }, {0.0f, 1.0f, 0.0f}},
{{ -l1, 0.0f, l2 }, {0.0f, 1.0f, 0.0f}}
};
indices = {
0, 1, 2,
0, 2, 3
};
}
|
|
|