Loading meshes using Assimp in OpenGL

I have been catching up on OpenGL 4.4 lately and realized that there are few working examples on the newer versions of OpenGL. The following code uses Assimp, GLFW and GLEW to load and render all supported Assimp formats and requires OpenGL 3.0 or above.

You will be required to set up an OpenGL context, I will presume that you have the knowledge to do that (if not, I can recommend this tutorial from opengl-tutorial.org).

To use the Mesh class, this is all that is required:

Mesh *mesh = new Mesh("filename.dae");
while (!glfwWindowShouldClose(window)) {
	mesh->render();
}
delete m;

The combined source and example shaders can be downloaded at the bottom of this page.

Source code

Mesh.h

#pragma once

#include "include/GL/glew.h"
#include "include/GL/glfw3.h"

#include "include\assimp\scene.h"
#include "include\assimp\mesh.h"

#include 

class Mesh
{
public :
	struct MeshEntry {
		static enum BUFFERS {
			VERTEX_BUFFER, TEXCOORD_BUFFER, NORMAL_BUFFER, INDEX_BUFFER
		};
		GLuint vao;
		GLuint vbo[4];

		unsigned int elementCount;

		MeshEntry(aiMesh *mesh);
		~MeshEntry();

		void load(aiMesh *mesh);
		void render();
	};

	std::vector<MeshEntry*>; meshEntries;

public:
	Mesh(const char *filename);
	~Mesh(void);

	void render();
};


Mesh.cpp

#include "Mesh.h"

#include "include\assimp\Importer.hpp"
#include "include\assimp\postprocess.h"


/**
*	Constructor, loading the specified aiMesh
**/
Mesh::MeshEntry::MeshEntry(aiMesh *mesh) {
	vbo[VERTEX_BUFFER] = NULL;
	vbo[TEXCOORD_BUFFER] = NULL;
	vbo[NORMAL_BUFFER] = NULL;
	vbo[INDEX_BUFFER] = NULL;

	glGenVertexArrays(1, &amp;vao);
	glBindVertexArray(vao);

	elementCount = mesh->mNumFaces * 3;

	if(mesh->HasPositions()) {
		float *vertices = new float[mesh->mNumVertices * 3];
		for(int i = 0; i < mesh->mNumVertices; ++i) {
			vertices[i * 3] = mesh->mVertices[i].x;
			vertices[i * 3 + 1] = mesh->mVertices[i].y;
			vertices[i * 3 + 2] = mesh->mVertices[i].z;
		}

		glGenBuffers(1, &amp;vbo[VERTEX_BUFFER]);
		glBindBuffer(GL_ARRAY_BUFFER, vbo[VERTEX_BUFFER]);
		glBufferData(GL_ARRAY_BUFFER, 3 * mesh->mNumVertices * sizeof(GLfloat), vertices, GL_STATIC_DRAW);

		glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, NULL);
		glEnableVertexAttribArray (0);

		delete[] vertices;
	}


	if(mesh->HasTextureCoords(0)) {
		float *texCoords = new float[mesh->mNumVertices * 2];
		for(int i = 0; i < mesh->mNumVertices; ++i) {
			texCoords[i * 2] = mesh->mTextureCoords[0][i].x;
			texCoords[i * 2 + 1] = mesh->mTextureCoords[0][i].y;
		}

		glGenBuffers(1, &amp;vbo[TEXCOORD_BUFFER]);
		glBindBuffer(GL_ARRAY_BUFFER, vbo[TEXCOORD_BUFFER]);
		glBufferData(GL_ARRAY_BUFFER, 2 * mesh->mNumVertices * sizeof(GLfloat), texCoords, GL_STATIC_DRAW);

		glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, NULL);
		glEnableVertexAttribArray (1);

		delete[] texCoords;
	}


	if(mesh->HasNormals()) {
		float *normals = new float[mesh->mNumVertices * 3];
		for(int i = 0; i < mesh->mNumVertices; ++i) {
			normals[i * 3] = mesh->mNormals[i].x;
			normals[i * 3 + 1] = mesh->mNormals[i].y;
			normals[i * 3 + 2] = mesh->mNormals[i].z;
		}

		glGenBuffers(1, &amp;vbo[NORMAL_BUFFER]);
		glBindBuffer(GL_ARRAY_BUFFER, vbo[NORMAL_BUFFER]);
		glBufferData(GL_ARRAY_BUFFER, 3 * mesh->mNumVertices * sizeof(GLfloat), normals, GL_STATIC_DRAW);

		glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 0, NULL);
		glEnableVertexAttribArray (2);

		delete[] normals;
	}
	

	if(mesh->HasFaces()) {
		unsigned int *indices = new unsigned int[mesh->mNumFaces * 3];
		for(int i = 0; i < mesh->mNumFaces; ++i) {
			indices[i * 3] = mesh->mFaces[i].mIndices[0];
			indices[i * 3 + 1] = mesh->mFaces[i].mIndices[1];
			indices[i * 3 + 2] = mesh->mFaces[i].mIndices[2];
		}

		glGenBuffers(1, &amp;vbo[INDEX_BUFFER]);
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo[INDEX_BUFFER]);
		glBufferData(GL_ELEMENT_ARRAY_BUFFER, 3 * mesh->mNumFaces * sizeof(GLuint), indices, GL_STATIC_DRAW);

		glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, 0, NULL);
		glEnableVertexAttribArray (3);

		delete[] indices;
	}
	

	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindVertexArray(0);
}

/**
*	Deletes the allocated OpenGL buffers
**/
Mesh::MeshEntry::~MeshEntry() {
	if(vbo[VERTEX_BUFFER]) {
		glDeleteBuffers(1, &amp;vbo[VERTEX_BUFFER]);
	}

	if(vbo[TEXCOORD_BUFFER]) {
		glDeleteBuffers(1, &amp;vbo[TEXCOORD_BUFFER]);
	}

	if(vbo[NORMAL_BUFFER]) {
		glDeleteBuffers(1, &amp;vbo[NORMAL_BUFFER]);
	}

	if(vbo[INDEX_BUFFER]) {
		glDeleteBuffers(1, &amp;vbo[INDEX_BUFFER]);
	}

	glDeleteVertexArrays(1, &amp;vao);
}

/**
*	Renders this MeshEntry
**/
void Mesh::MeshEntry::render() {
	glBindVertexArray(vao);
	glDrawElements(GL_TRIANGLES, elementCount, GL_UNSIGNED_INT, NULL);
	glBindVertexArray(0);
}

/**
*	Mesh constructor, loads the specified filename if supported by Assimp
**/
Mesh::Mesh(const char *filename)
{
	Assimp::Importer importer;
	const aiScene *scene = importer.ReadFile(filename, NULL);
	if(!scene) {
		printf("Unable to laod mesh: %s\n", importer.GetErrorString());
	}

	for(int i = 0; i < scene->mNumMeshes; ++i) {
		meshEntries.push_back(new Mesh::MeshEntry(scene->mMeshes[i]));
	}
}

/**
*	Clears all loaded MeshEntries
**/
Mesh::~Mesh(void)
{
	for(int i = 0; i < meshEntries.size(); ++i) {
		delete meshEntries.at(i);
	}
	meshEntries.clear();
}

/**
*	Renders all loaded MeshEntries
**/
void Mesh::render() {
	for(int i = 0; i < meshEntries.size(); ++i) { meshEntries.at(i)->render();
	}
}

Download

Mesh.zip

11 Comments

  • Truncator says:

    Very helpful article, thank you!

  • Skirrah says:

    Very helpful code but perhaps someone could help me resolve an error when using with assimp-3.1.-win-binaries.
    Code compiles but gives runtime error; “The program can’t start because assimp.exe is missing from your computer.”
    The assimp.dll file is in the same folder as the program executable.

    • Nex says:

      Greetings Skirrah. What version of Assimp are you using? As I have understood, there is a problem with the 3.1(.1) version of the Windows binaries. Until the issue is fixed, I would recommend that you downgrade your version of Assimp. The bug has been reported to the Github repo, you can keep up with the status of it here: https://github.com/assimp/assimp/issues/302

  • Tomas Mikulica says:

    Hi, first of all I would like to thank you for great article. I have one question. May I use your code in my app (Diploma thesis) when I will put link to this article in the header file?

    • Nex says:

      Greetings Tomas. First of all, thank you! And of course you may! The sole intention for me writing about this stuff is to share the knowledge. Any backlinks are of course much appreciated 😉

      • Tomas Mikulica says:

        Thank you. By the the way I think that the line (in texcoord loading):

        glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, NULL);

        should look more like that:

        glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, NULL);

        Because you are passing to shader only 2 floats not 3. It took me awhile (and lot of weird stuff on my screen 🙂 ) to figure that out 😉

  • pacific00 says:

    Hi Tom ,

    I’m trying to load a model using assimp , in mac using core 4.1 but i’m stuck on “vertex object not bound” error.. tried to bind a vao before vbo still not happening..

    It’d help if you could take a look at the below code(your code 🙂 ) and give some suggestion..

    vbo[VERTEX_BUFFER] = NULL;
    vbo[TEXCOORD_BUFFER] = NULL;
    vbo[NORMAL_BUFFER] = NULL;
    vbo[INDEX_BUFFER] = NULL;

    // this was the only vao that was bound initially with legacy opengl
    //glGenVertexArraysAPPLE(1, &vao);
    //glBindVertexArrayAPPLE(vao);

    elementCount = mesh->mNumFaces * 3;
    if(mesh->HasPositions()) {
    float *vertices = new float[mesh->mNumVertices * 3];
    for(int i = 0; i mNumVertices; ++i) {
    vertices[i * 3] = mesh->mVertices[i].x;
    vertices[i * 3 + 1] = mesh->mVertices[i].y;
    vertices[i * 3 + 2] = mesh->mVertices[i].z;
    }
    // here trying to bind vao and so on before every vbo
    glGenVertexArraysAPPLE(1, &vaoTemp[VERTEX_BUFFER]);
    glBindVertexArrayAPPLE(vaoTemp[VERTEX_BUFFER]);

    glGenBuffers(1, &vbo[VERTEX_BUFFER]);
    glBindBuffer(GL_ARRAY_BUFFER, vbo[VERTEX_BUFFER]);
    glBufferData(GL_ARRAY_BUFFER, 3 * mesh->mNumVertices * sizeof(GLfloat), vertices, GL_STATIC_DRAW);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, NULL);
    glEnableVertexAttribArray (0);
    delete vertices;
    }

  • Praveen says:

    Hi,
    When I am using this code, GlDrawelements doing nothing, I am working in OpenGL 2.1. Thanks

  • George Broughton says:

    MeshEntry constructor, all of your deletes should be delete[] as they are an array, or they will leak memory

  • Mike Hou says:

    Hi, I learned assimp importing from this tutorial, thank you.
    I think the code
    glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, 0, NULL);
    glEnableVertexAttribArray (3);
    should be removed since they are not used, am I correct?

Leave a Reply

Your email address will not be published. Required fields are marked *