Open Asset Import Library (ASSIMP)

Assimp es una librería que nos servirá para cargar modelos o escenas 3D almacenados en gran variedad de formatos, como: Collada (*.dae; *.xml), Blender (*.blend), 3D Studio Max 3DS (*.3ds), Wavefront Object (*.obj ), y muchos más, Assimp puede cargar información de vértices, coordenadas de textura, normales, materiales, animación, y otros.

Construir Assimp


Descargarnos el correspondiente código fuente, para este ejemplo usaremos la versión Assimp 3.2, usaremos CMake para generar el proyecto que luego construiremos con Visual Studio 2015, opcionalmente podemos instalar las librerías DirectX SDK y la biblioteca Boost que serán usadas para construir Assimp con algunas mejoras de rendimiento.

Abrimos CMake GUI e indicamos el CMakeLists.txt de Assimp y la carpeta de salida, presionamos configurar, debemos recordar cambiar la ruta de instalación CMAKE_INSTALL_PREFIX, presionamos configurar nuevamente, si no tenemos errores, presionamos generar.

assimp cmake opengl
Una vez tenemos el proyecto generado para Visual Studio 2015 abrimos la solución correspondiente, cambiamos a la configuración adecuada, Debug o Release, según lo necesitemos, para generar e instalar la biblioteca Assimp, nos situamos sobre el proyecto llamado, INSTALL y presionamos generar.

Para finalizar debemos agregar la variable de entorno ASSIMP_DIR apuntando a la carpeta de instalación C:/Developer/opengl/ASSIMP/lib/cmake/assimp-3.2 o podemos indicarla manualmente a la hora de usar CMake para generar los ejemplos, requerimos también agregar la ruta C:/Developer/opengl/ASSIMP/bin a la variable PATH.

Model Mesh


Normalmente un modelo o escena 3D está compuesto por varias partes, a cada una de las partes de componen un modelo le llamaremos Mesh, por ejemplo: a la hora de modelar un auto, para facilitarnos el trabajo podemos ir creándolo por partes, las llantas, las puertas, ventanas, etc., cada una de estas partes (Meshes) tendrá sus normales, texturas, vértices, etc., por lo que podemos dibujarlas individualmente, todas en conjunto forman el modelo de un auto.

model mesh tutorial opengl
Usando la aplicación Open 3D Model Viewer podemos observar cómo está compuesto un modelo, en la parte derecha se pueden ver cada uno de los meses que conforman el modelo, en la imagen vemos el mesh 148 resaltado, este es el cuerpo de deadpool, los demás corresponden a sus armas y el traje, veamos como dibujar un mesh con OpenGL Moderno.

Assimp::Importer importer;

// Cragar el archivo indicado, usar caras triangulares (aiProcess_Triangulate)
const aiScene* scene = importer.ReadFile(archivo, aiProcess_Triangulate);

// Si no se ha podido cargar, mustra el error.
if (!scene) {
    std::cout << importer.GetErrorString() << std::endl;
    return;
}

// Obtener el mesh deseado
const aiMesh* mesh = scene->mMeshes[148]; 

// Obtener la posicion de cada vertice
vertex.reserve(mesh->mNumVertices);
for (unsigned int i = 0; i < mesh->mNumVertices; i++) {
    aiVector3D pos = mesh->mVertices[i];
    vertex.push_back(glm::vec3(pos.x, pos.y, pos.z));
}

// Obetener las coordenadas de textura
uv.reserve(mesh->mNumVertices);
for (unsigned int i = 0; i < mesh->mNumVertices; i++) {
    aiVector3D UVW = mesh->mTextureCoords[0][i]; 
    uv.push_back(glm::vec2(UVW.x, UVW.y));
}

// Obtener los vectores normales
normal.reserve(mesh->mNumVertices);
for (unsigned int i = 0; i < mesh->mNumVertices; i++) {
    aiVector3D n = mesh->mNormals[i];
    normal.push_back(glm::vec3(n.x, n.y, n.z));
}

// Obtener los indices 
indices.reserve(3 * mesh->mNumFaces);
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]);
}

Se código muestra el código necesario para dibujar un mesh en particular, el número 148, primero cargamos el archivo, con el método ReadFile, este nos devuelve un objeto aiScene este contiene todos los meshes que componen el modelo, obtenemos el deseado, un objeto aiMesh contiene la información de vértices, normales y texturas que necesitamos para dibujar, obtenemos cada uno y lo almacenamos en los contenedores STL vector.

dibujar model mesh
Este código nos servirá para dibujar modelos que estén formados por un solo mesh obteniendo el primer mesh (const aiMesh* mesh = scene->mMeshes[0]), para dibujar modelos complejos haremos lo siguiente:

Para dibujar una escena completa debemos obtener todos los nodos que componen la escena, una escena tiene un nodo principal, este contiene los meshes que forman el modelo y otros nodos hijos que a su vez contienen esta misma información, para obtener todos los meshes debemos recorrer recursivamente los nodos de la escena.

Creamos la clase Model, esta contiene un conjunto de objetos Mesh que conforman la escena a dibujar, el método processNode será el encargado de recorrer cada uno de los nodos de la escena y obtener cada uno de los objetos Mesh que la conforman.

class Model {
private:
    vector<Mesh> meshes;
    GLuint vao;

    // procesar recusivamente cada nodo de la escena
    void processNode(aiNode* node, const aiScene* scene)
    {
        // obtener los mesh de esta escena
        for (unsigned int i = 0; i < node->mNumMeshes; i++) {
            Mesh mesh(scene->mMeshes[node->mMeshes[i]]);
            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;
        const aiScene* scene = importer.ReadFile(file_name, aiProcess_Triangulate);
        processNode(scene->mRootNode, scene);
    }

    // dibujar la escena completa
    void draw() {
        for (Mesh m : meshes) m.draw();
    }
};

La clase Mesh será la encargada de obtener la información necesaria para dibujar cada mesh, vértices, normales, uv, etc., funciona de la misma manera como lo vimos al principio, solo debemos tener en cuenta que, para cada mesh creamos un VAO, almacenamos los buffer correspondientes en este VAO.

class Mesh {
public:
    // dibujar el mesh
    void draw() {
        glBindVertexArray(vao);
        glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_SHORT, 0);
        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 short> 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
            aiVector3D pos = mesh->mVertices[i];
            vertex.push_back(glm::vec3(pos.x, pos.y, pos.z));

            // Obetener las coordenadas de textura
            aiVector3D UVW = mesh->mTextureCoords[0][i];
            uv.push_back(glm::vec2(UVW.x, UVW.y));

            // Obtener los vectores normales
            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 
        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
        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 short), &indices[0], GL_STATIC_DRAW);

        // desactivar el VAO
        glBindVertexArray(0);
    }
};

Ejecutamos la aplicación y veremos el resultado, podemos observar que ahora si tenemos el modelo dibujado con todas sus componentes.

tutorial opengl moderno
En el próximo tutorial veremos cómo añadir texturas y materiales a nuestra escena o modelo 3D y cómo podemos aplicar animaciones a un modelo.

Este modelo no esta incluido, lo puedes descargar de: Dead Pool By TF3DM

GitHub: Importar Modelos con ASSIMP

Comentarios

Temas relacionados

Entradas populares de este blog

tkinter Grid

tkinter Canvas

Histogramas OpenCV Python

Python Binance API