Materiales e Iluminación

Los materiales son usados para definir el aspecto de los objetos en escena 3D, estos describen las propiedades físicas de los objetos con respecto a cómo los mismos reflejan la luz que incide sobre ellos, anteriormente habíamos definido las propiedades de la fuente de luz por lo que toda la escena es iluminada del mismo modo, definiendo por separado las propiedades de cada objeto logramos que cada una de ellos responda de manera diferente a la iluminación.

Para el uso de materiales definimos 5 componentes: ambiental, difusa, especular, usamos estas componentes para definir la fuente de luz como lo vimos en el tutorial: modelo de iluminación phong, ahora nos sirven también para definir como los objetos reflejan dichas componentes, indicamos también el coeficiente de brillo (shininess) y el coeficiente de emisión, este último define el color de luz que emite el objeto.

GLSL nos permite utilizar estructuras para organizar los datos, crearemos dos estructuras una para los componentes de los materiales y otra para los componentes de la fuente de luz, las estructuras en GLSL funcionas de manera similar que en el lenguaje C.

struct Light {
	vec3 direction;
	vec3 ambient;
	vec3 diffuse;
	vec3 specular;
};

struct Material {
	vec3 ambient;
	vec3 diffuse;
	vec3 emissive;
	vec3 specular;
	float shininess;
	float shininess_strength;
};

uniform Light light;
uniform Material material;

Antes de dibujar debemos establecer cada una de las componentes del material que componen el objeto que deseamos dibujar, utilizaremos assimp para obtener cada una de las componentes del material que se aplica a cada mesh.

if (mesh->mMaterialIndex >= 0) {
    // obtener el material correspondiente a este Mesh
    const aiMaterial* material = model->scene->mMaterials[mesh->mMaterialIndex];

    if (material->Get(AI_MATKEY_SHININESS, shininess) != AI_SUCCESS) shininess = 128.0;
    if (material->Get(AI_MATKEY_SHININESS_STRENGTH, shininess_strength) != AI_SUCCESS) shininess_strength = 1.0;

    aiColor4D diffuse, ambient, specular, emisive;

    if (aiGetMaterialColor(material, AI_MATKEY_COLOR_DIFFUSE, &diffuse) == AI_SUCCESS) {
        aiColorToFloat(diffuse, color_diffuse);
    }

    if (aiGetMaterialColor(material, AI_MATKEY_COLOR_SPECULAR, &specular) == AI_SUCCESS) {
        aiColorToFloat(specular, color_specular);
    }

    if (aiGetMaterialColor(material, AI_MATKEY_COLOR_AMBIENT, &ambient) == AI_SUCCESS) {
        aiColorToFloat(ambient, color_ambient);
    }

    if (aiGetMaterialColor(material, AI_MATKEY_COLOR_EMISSIVE, &emisive) == AI_SUCCESS) {
        aiColorToFloat(emisive, color_emissive);
    }
}

A la hora de dibujar establecemos cada uno de los valores de los componentes del material obtenidos previamente, para obtener la ubicación de un uniform tipo struct en los shaders accedemos a ello mediante el nombre, punto “.” seguido del nombre de la variable que deseamos establecer, ejm: glGetUniformLocation(program, "material.ambient"); para obtener la ubicación de la componente ambiental del material.

glBindVertexArray(vao);

glUniform3fv(glGetUniformLocation(program, "material.ambient"), 1, color_ambient);
glUniform3fv(glGetUniformLocation(program, "material.diffuse"), 1, color_diffuse);
glUniform3fv(glGetUniformLocation(program, "material.specular"), 1, color_specular);
glUniform3fv(glGetUniformLocation(program, "material.emissive"), 1, color_emissive);
glUniform1f(glGetUniformLocation(program, "material.shininess"), shininess);
glUniform1f(glGetUniformLocation(program, "material.shininess_strength"), shininess_strength);

glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, NULL);

glBindVertexArray(0);

Iluminación Blinn-Phong


El modelo de iluminación Blinn-Phong es una extensión del modelo Phong, se implementa prácticamente de la misma manera solo que remplazamos el vector reflejado R por el vector H que es un vector intermedio entre la dirección de la luz y el vector de la vista o cámara.

blinn-phong
N (vector normal), V (vector de la vista), L (vector de la luz).

vec3 N = normalize(N1);
vec3 L = normalize(light.direction);
vec3 V = normalize(V1);
vec3 H = normalize(L + V);	

float diff = max(dot(N, L), 0);
float spec = pow(max(dot(N, H), 0), material.shininess) * material.shininess_strength;

vec3 emissive = material.emissive;
vec3 ambient  = material.ambient  * light.ambient;
vec3 diffuse  = material.diffuse  * light.diffuse  * diff;
vec3 specular = material.specular * light.specular * spec;

color = vec4(emissive + ambient + diffuse + specular, 1.0);

Ahora podemos cargar un modelo soportado por assimp que incluya materiales, utilizaremos una fuente de luz direccional, al ejecutar la aplicación veremos algo similar a esto:

opengl moderno tutorial matriales
En el tutorial: cargar modelos con assimp aprendimos a cargar modelos almacenados en diversos formatos, se han creado las clases Model y Mesh para encapsular las funcionalidades de la biblioteca assimp, ambas clases se encuentran en el archivo OpenGLModel.hpp.

Clases Model y Mesh


La clase Mesh nos servirá para almacenar cada una de las partes que componen el modelo, la clase Model es el contenedor principal que maneja cada uno de los meshes que conforman en modelo o escena, aplicando lo que aprendimos en el tutorial anterior nuestras dos clases se ven de este modo:

class Mesh {
    private:
        Model* model;

    public:
        Mesh(const aiMesh* mesh, Model* model) {
            this->model = model;
            load(mesh);
            create();
        }

        ~Mesh() {
            glDeleteBuffers(4, buffer);
            glDeleteVertexArrays(1, &vao);
        }

        // dibujar el mesh
        void draw() {
            glBindVertexArray(vao);
            glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, NULL);
            glBindVertexArray(0);
        };

        // inicializar el mesh
        void init(const aiMesh* mesh) {
            load(mesh);
            create();
        };

    private:
        vector<glm::vec3> vertex;
        vector<glm::vec3> normal;
        vector<glm::vec2> uv;
        vector<unsigned int> indices;

        GLuint buffer[4];
        GLuint vao;

        // obtener los datos de cada mesh
        void load(const aiMesh* mesh) {

            vertex.reserve(mesh->mNumVertices);
            uv.reserve(mesh->mNumVertices);
            normal.reserve(mesh->mNumVertices);
            indices.reserve(3 * mesh->mNumFaces);

            for (unsigned int i = 0; i < mesh->mNumVertices; i++) {

                // Obtener la posicion de cada vertice
                const aiVector3D* pos = &(mesh->mVertices[i]);
                vertex.push_back(glm::vec3(pos->x, pos->y, pos->z));

                // Obtener las coordenadas de textura
                if (mesh->HasTextureCoords(0)) {
                    const aiVector3D* UVW = &(mesh->mTextureCoords[0][i]);
                    uv.push_back(glm::vec2(UVW->x, UVW->y));
                }

                // Obtener los vectores normales
                if (mesh->HasNormals()) {
                    const aiVector3D* n = &(mesh->mNormals[i]);
                    normal.push_back(glm::vec3(n->x, n->y, n->z));
                }
            }

            // Obtener los indices 
            for (unsigned int i = 0; i < mesh->mNumFaces; i++) {
                indices.push_back(mesh->mFaces[i].mIndices[0]);
                indices.push_back(mesh->mFaces[i].mIndices[1]);
                indices.push_back(mesh->mFaces[i].mIndices[2]);
            }
        }

        void create() {
            // generar y activar el VAO
            glGenVertexArrays(1, &vao);
            glBindVertexArray(vao);

            // generar dos ids para los buffer
            glGenBuffers(4, buffer);

            // buffer de vertices
            glBindBuffer(GL_ARRAY_BUFFER, buffer[0]);
            glBufferData(GL_ARRAY_BUFFER, vertex.size() * sizeof(glm::vec3), &vertex[0], GL_STATIC_DRAW);
            glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, NULL);
            glEnableVertexAttribArray(0);

            // buffer de textura 
            if (!uv.empty()) {
                glBindBuffer(GL_ARRAY_BUFFER, buffer[1]);
                glBufferData(GL_ARRAY_BUFFER, uv.size() * sizeof(glm::vec2), &uv[0], GL_STATIC_DRAW);
                glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, NULL);
                glEnableVertexAttribArray(1);
            }

            // buffer de normales
            if (!normal.empty()) {
                glBindBuffer(GL_ARRAY_BUFFER, buffer[2]);
                glBufferData(GL_ARRAY_BUFFER, normal.size() * sizeof(glm::vec3), &normal[0], GL_STATIC_DRAW);
                glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 0, NULL);
                glEnableVertexAttribArray(2);
            }

            // buffer de indices
            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer[3]);
            glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), &indices[0], GL_STATIC_DRAW);

            // desactivar el VAO
            glBindVertexArray(0);
        }
};
class Model {
public:
    map<string, GLuint> textures;
    const aiScene* scene;

private:
    vector<shared_ptr<Mesh>> meshes;

    // procesar recusivamente cada nodo de la escena
    void processNode(const aiNode* node, const aiScene* scene)
    {
        // obtener los mesh de esta escena
        for (unsigned int i = 0; i < node->mNumMeshes; i++) {
            shared_ptr<Mesh> mesh(new Mesh(scene->mMeshes[node->mMeshes[i]], this));
            meshes.push_back(mesh);
        }

        // procesar los hijos del nodo
        for (unsigned int i = 0; i < node->mNumChildren; i++)
            this->processNode(node->mChildren[i], scene);
    }

public:
    // cargar el archivo deseado
    void init(const std::string& file_name) {
        Assimp::Importer importer;
        scene = importer.ReadFile(file_name, aiProcess_Triangulate);

        if (scene && scene->mRootNode)
            processNode(scene->mRootNode, scene);
        else cout << importer.GetErrorString() << endl;
   }

    // dibujar la escena completa
    void draw() {
        for (auto m : meshes) m->draw();
    }
};

Para inicializar o cargar el modelo usaremos el método model.init("model/deadpool/dead.obj"); solo debemos indicar la ruta donde se encuentra el archivo que deseamos cargar, para dibujar el modelo usaremos model.draw();, model es un objeto de la clase Model.

GitHub: Materiales en OpenGL con Assimp

Comentarios

  1. cómo funciona aiColorToFloat? estoy intentando sacar del modelo Assimp sus constantes ambiental, difusa, especular y emisiva pero luego nose cómo asignarlas a glm::vec3
    Me podrías ayudar?

    Muchas gracias

    ResponderEliminar
    Respuestas
    1. La función aiColorToFloat se encarga de convertir la estructura aiColor4D usada por ASSIMP para almacenar el color en un arreglo float[] que necesita OpenGL, este arreglo debe contener las componentes de color RGBA.

      Eliminar

Publicar un comentario

Entradas populares de este blog

Conectar SQL Server con Java

Entrenar OpenCV en Detección de Objetos

Detección de figuras geométricas