Texturas e Iluminación

En el tutorial OpenGL anterior aprendimos a modificar las propiedades de los materiales para obtener diferentes resultados cuando el objeto es iluminado por una fuente de luz, si deseamos obtener resultados más realistas debemos recurrir al uso de texturas para establecer de manera más precisa el color de cada pixel, veremos cómo utilizar texturas para definir las componentes ambiental, difusa y especular de un objeto.

Diffuse Map


Anteriormente habíamos definido la componente difusa del material como un color RGB, esta vez la componente difusa será establecida por una textura, normalmente llamada diffuse map, lo que nos permite tener mayor precisión y obtener mejores resultados, los conceptos en cuanto al uso de texturas y un diffuse map son los mismos que vimos en el tutorial: Uso de Texturas 2D en OpenGL Moderno.

cyborg_diffuse
En la imagen superior se muestra el diffuse map para el modelo cyborg utilizado en ejemplos anteriores, específicamente: Modelos en Formato OBJ.

Cargar Texturas con ASSIMP


Seguimos utilizando la biblioteca assimp para la carga de los modelos 3D, en este punto requerimos cargar cada una de las texturas utilizadas para establecer las componentes de iluminación, assimp las identifica por el siguiente nombre aiTextureType_NOMBRE donde NOMBRE corresponde a la mapa de textura que deseamos obtener, puede ser AMBIENT, DIFFUSE, SPECULAR, etc., aunque existen muchos otros tipos de texturas de momento veremos estos 3 que son los que conforman las componentes básicas del modelo de iluminación phong.

void load_material(const aiMesh* mesh, aiTextureType ttype, GLuint& texture) {
    if (mesh->mMaterialIndex >= 0) {
        const aiMaterial* material = model->scene->mMaterials[mesh->mMaterialIndex];

        for (unsigned int i = 0; i < material->GetTextureCount(ttype); i++) {
            aiString path;
            if (AI_SUCCESS == material->GetTexture(ttype, i, &path)) {
                const string tex_path = path.C_Str();

                if (model->textures.count(tex_path) == 0) {
                    texture = TextureFromFile(texture_path(path.C_Str()));
                    model->textures.insert({ tex_path, texture });
                }
                else texture = model->textures[tex_path];
            }
        }
    }
}

Este fragmento de código obtiene el material (aiMaterial) asignado al mesh (aiMesh) indicado, luego obtenemos el tipo de textura indicado (aiTextureType), podemos tener una o varias texturas sin embargo nuestra aplicación solo utilizará una, para obtener la textura usamos: material->GetTexture(ttype, i, &path), esto nos devolverá la ruta donde se encuentra el archivo de textura, usamos: texture = TextureFromFile(texture_path(path.C_Str())) para cargar la textura.

Debemos tener en cuenta que varios materiales pueden utilizar una misma textura, por lo que creamos un map<string, GLuint> para guardar las texturas que ya tenemos cargadas, guardamos su nombre y su identificador, antes de cargar una textura verificamos si ya la tenemos en memoria, de ser así solo guardamos su identificador.

GLint TextureFromFile(const std::string& filename)
{
    GLuint textureID = 0;
    glGenTextures(1, &textureID);

    int width, height, comp;
    unsigned char *image = stbi_load(filename.c_str(), &width, &height, &comp, 3);

    glBindTexture(GL_TEXTURE_2D, textureID);
    
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
    
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

    glBindTexture(GL_TEXTURE_2D, 0);

    stbi_image_free(image);

    return textureID;
}

Este método es utilizado para cargar una textura y generar su identificador, usamos la biblioteca STB para cargar el archivo de textura, debemos tener en cuenta que nuestro código solo cargar texturas que estén almacenadas en el mismo directorio donde se encuentra el archivo que contiene la escena 3D que estamos cargando.

Nuestro shader tiene pocas variaciones, solo el tipo de variable del campo diffuse de vec3 a sampler2D para almacenar la textura.

/../
struct Material {
	sampler2D diffuse;
	sampler2D specular;	
	sampler2D ambient;
	vec3 emissive;
	float shininess;
	float shininess_strength;
};
/../
void main()
{
	/../
	float diff = max(dot(N, L), 0);
	float spec = pow(max(dot(N, H), 0), material.shininess) * material.shininess_strength;

	vec3 diffuse  = texture(material.diffuse , UV).rgb * light.diffuse  * diff;

	color = vec4(diffuse, 1.0);
}

En el código de dibujo debemos activar cada una de las texturas antes de utilizarlas, al finalizar desactivamos cada una de ellas.

void draw(GLuint program) {
    activeTextureNum(0, texture_diffuse,  program, "material.diffuse");
    activeTextureNum(1, texture_specular, program, "material.specular");
    activeTextureNum(2, texture_ambient,  program, "material.ambient");

    glUniform1f(glGetUniformLocation(program, "material.shininess"), shininess);
    glUniform1f(glGetUniformLocation(program, "material.shininess_strength"), shininess_strength);

    glBindVertexArray(vao);
    glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, NULL);
    glBindVertexArray(0);

    disableAllTexture();
};

void activeTextureNum(int num, GLuint id, GLuint program, const string& name) {
    glActiveTexture(GL_TEXTURE0 + num);
    glBindTexture(GL_TEXTURE_2D, id);
    glUniform1i(glGetUniformLocation(program, name.c_str()), num);
}

void disableAllTexture() {
    for (size_t i = 0; i < 8; i++) {
        glActiveTexture(GL_TEXTURE0 + i);
        glBindTexture(GL_TEXTURE_2D, 0);
    }
}

El resultado de al ejecutar la aplicación es la siguiente, solamente aplicando el diffuse map.

tutorial opengl diffuse map
El resultado es similar a lo que vimos en el tutorial: cargar modelos .obj, salvo que esta vez la textura se ve afectada por el efecto de iluminación lo que produce un resultado más realista.

Specular Map


El siguiente tipo de textura que aplicaremos, este le brinda al modelo 3D el brillo producido por la fuente de luz, el uso de una textura para este propósito permite indicar el brillo en áreas determinadas por el color blanco como una intensidad máxima y el negro como la mínima.
cyborg_specular
Lo aplicamos de la misma manera que la anterior, solo recordar que debemos multiplicar la textura por la contribución especular calculada, en lugar de la difusa.

vec3 diffuse  = texture(material.diffuse , UV).rgb * light.diffuse  * diff;
vec3 specular = texture(material.specular, UV).rgb * light.specular * spec;

color = vec4(diffuse + specular, 1.0);

El resultado ahora es el siguiente:

textura e iluminacion opengl
Podemos agregar también una textura para definir la componente ambiental, pero, nuestro modelo no la tiene, añadirla es muy fácil, esta textura se multiplica por la componente ambiental de la fuente de luz y luego se suma al color final, algunas veces la textura ambiental es la misma textura difusa aunque el resultado final no varía tanto.

GitHub: OpenGL Texturas e Iluminación

Comentarios

Temas relacionados

Entradas populares de este blog

tkinter Grid

tkinter Canvas

Histogramas OpenCV Python

Python Binance API