Help end child hunger

Importing 3D Models with Assimp

Prev: Loading an Image File and Creating a Texture Next: OpenGL 3.3 + GLSL 1.5 Sample

Importing 3D models is easy with Assimp. This sample works with Assimp 3.0 and was based on the Assimp’s demo extending it to use core OpenGL 3.3 together with GLSL 3.3.

The sample also uses DevIL, an image loading library to provide the sample the ability to display textured models.

Freeglut is the window toolkit used in this sample. A 3.3 OpenGL context is used, together with multisampling and wheel mouse for zooming on the model.

GLEW is also being used to provide access to the new OpenGL functions.

A simple untextured model in OBJ format is provided to try this code. To test it with other models just replace the name of the model in the source code below.

A VS2010 project with the full source code and OBJ model are available in L3DAssimpModelImport Demo.

Some parts of the code:

Vertex Shader

#version 330

layout (std140) uniform Matrices {

	mat4 projMatrix;
	mat4 viewMatrix;
	mat4 modelMatrix;

in vec3 position;
in vec3 normal;
in vec2 texCoord;

out vec4 vertexPos;
out vec2 TexCoord;
out vec3 Normal;

void main()
	Normal = normalize(vec3(viewMatrix * modelMatrix * vec4(normal,0.0)));
	TexCoord = vec2(texCoord);
	gl_Position = projMatrix * viewMatrix * modelMatrix * vec4(position,1.0);

Fragment Shader

#version 330

layout (std140) uniform Material {
	vec4 diffuse;
	vec4 ambient;
	vec4 specular;
	vec4 emissive;
	float shininess;
	int texCount;

uniform	sampler2D texUnit;

in vec3 Normal;
in vec2 TexCoord;
out vec4 output;

void main()
	vec4 color;
	vec4 amb;
	float intensity;
	vec3 lightDir;
	vec3 n;

	lightDir = normalize(vec3(1.0,1.0,1.0));
	n = normalize(Normal);
	intensity = max(dot(lightDir,n),0.0);

	if (texCount == 0) {
		color = diffuse;
		amb = ambient;
	else {
		color = texture2D(texUnit, TexCoord);
		amb = color * 0.33;
	output = (color * intensity) + amb;

Importing the model with Assimp

bool Import3DFromFile( const std::string& pFile)

	//check if file exists
	std::ifstream fin(pFile.c_str());
	if(! {
		printf("Couldn't open file: %s\n", pFile.c_str());
		printf("%s\n", importer.GetErrorString());
		return false;

	scene = importer.ReadFile( pFile, aiProcessPreset_TargetRealtime_Quality);

	// If the import failed, report it
	if( !scene)
		printf("%s\n", importer.GetErrorString());
		return false;

	// Now we can access the file's contents.
	printf("Import of scene %s succeeded.",pFile.c_str());

	aiVector3D scene_min, scene_max, scene_center;
	get_bounding_box(&scene_min, &scene_max);
	float tmp;
	tmp = scene_max.x-scene_min.x;
	tmp = scene_max.y - scene_min.y > tmp?scene_max.y - scene_min.y:tmp;
	tmp = scene_max.z - scene_min.z > tmp?scene_max.z - scene_min.z:tmp;
	scaleFactor = 1.f / tmp;

	// We're done. Everything will be cleaned up by the importer destructor
	return true;

Loading images with DevIL and creating textures

int LoadGLTextures(const aiScene* scene)
	ILboolean success;

	/* initialization of DevIL */

	/* scan scene's materials for textures */
	for (unsigned int m=0; m<scene->mNumMaterials; ++m)
		int texIndex = 0;
		aiString path;	// filename

		aiReturn texFound = scene->mMaterials[m]->GetTexture(aiTextureType_DIFFUSE, texIndex, &path);
		while (texFound == AI_SUCCESS) {
			//fill map with textures, OpenGL image ids set to 0
			textureIdMap[] = 0; 
			// more textures?
			texFound = scene->mMaterials[m]->GetTexture(aiTextureType_DIFFUSE, texIndex, &path);

	int numTextures = textureIdMap.size();

	/* create and fill array with DevIL texture ids */
	ILuint* imageIds = new ILuint[numTextures];
	ilGenImages(numTextures, imageIds); 

	/* create and fill array with GL texture ids */
	GLuint* textureIds = new GLuint[numTextures];
	glGenTextures(numTextures, textureIds); /* Texture name generation */

	/* get iterator */
	std::map<std::string, GLuint>::iterator itr = textureIdMap.begin();
	int i=0;
	for (; itr != textureIdMap.end(); ++i, ++itr)
		//save IL image ID
		std::string filename = (*itr).first;  // get filename
		(*itr).second = textureIds[i];	  // save texture id for filename in map

		ilBindImage(imageIds[i]); /* Binding of DevIL image name */
		success = ilLoadImage((ILstring)filename.c_str());

		if (success) {
			/* Convert image to RGBA */
			ilConvertImage(IL_RGBA, IL_UNSIGNED_BYTE); 

			/* Create and load textures to OpenGL */
			glBindTexture(GL_TEXTURE_2D, textureIds[i]); 
			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, ilGetInteger(IL_IMAGE_WIDTH),
			printf("Couldn't load Image: %s\n", filename.c_str());
	/* Because we have already copied image data into texture data
	we can release memory used by image. */
	ilDeleteImages(numTextures, imageIds); 

	delete [] imageIds;
	delete [] textureIds;

	//return success;
	return true;

Vertex Array Objects

void genVAOsAndUniformBuffer(const aiScene *sc) {

	struct MyMesh aMesh;
	struct MyMaterial aMat; 
	GLuint buffer;

	// For each mesh
	for (unsigned int n = 0; n < sc->mNumMeshes; ++n)
		const aiMesh* mesh = sc->mMeshes[n];

		// create array with faces
		// have to convert from Assimp format to array
		unsigned int *faceArray;
		faceArray = (unsigned int *)malloc(sizeof(unsigned int) * mesh->mNumFaces * 3);
		unsigned int faceIndex = 0;

		for (unsigned int t = 0; t < mesh->mNumFaces; ++t) {
			const aiFace* face = &mesh->mFaces[t];

			memcpy(&faceArray[faceIndex], face->mIndices,3 * sizeof(unsigned int));
			faceIndex += 3;
		aMesh.numFaces = sc->mMeshes[n]->mNumFaces;

		// generate Vertex Array for mesh

		// buffer for faces
		glGenBuffers(1, &buffer);
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer);
		glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned int) * mesh->mNumFaces * 3, faceArray, GL_STATIC_DRAW);

		// buffer for vertex positions
		if (mesh->HasPositions()) {
			glGenBuffers(1, &buffer);
			glBindBuffer(GL_ARRAY_BUFFER, buffer);
			glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*mesh->mNumVertices, mesh->mVertices, GL_STATIC_DRAW);
			glVertexAttribPointer(vertexLoc, 3, GL_FLOAT, 0, 0, 0);

		// buffer for vertex normals
		if (mesh->HasNormals()) {
			glGenBuffers(1, &buffer);
			glBindBuffer(GL_ARRAY_BUFFER, buffer);
			glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*mesh->mNumVertices, mesh->mNormals, GL_STATIC_DRAW);
			glVertexAttribPointer(normalLoc, 3, GL_FLOAT, 0, 0, 0);

		// buffer for vertex texture coordinates
		if (mesh->HasTextureCoords(0)) {
			float *texCoords = (float *)malloc(sizeof(float)*2*mesh->mNumVertices);
			for (unsigned int k = 0; k < mesh->mNumVertices; ++k) {

				texCoords[k*2]   = mesh->mTextureCoords[0][k].x;
				texCoords[k*2+1] = mesh->mTextureCoords[0][k].y; 

			glGenBuffers(1, &buffer);
			glBindBuffer(GL_ARRAY_BUFFER, buffer);
			glBufferData(GL_ARRAY_BUFFER, sizeof(float)*2*mesh->mNumVertices, texCoords, GL_STATIC_DRAW);
			glVertexAttribPointer(texCoordLoc, 2, GL_FLOAT, 0, 0, 0);

		// unbind buffers

		// create material uniform buffer
		aiMaterial *mtl = sc->mMaterials[mesh->mMaterialIndex];

		aiString texPath;	//contains filename of texture
		if(AI_SUCCESS == mtl->GetTexture(aiTextureType_DIFFUSE, 0, &texPath)){
				//bind texture
				unsigned int texId = textureIdMap[];
				aMesh.texIndex = texId;
				aMat.texCount = 1;
			aMat.texCount = 0;

		float c[4];
		set_float4(c, 0.8f, 0.8f, 0.8f, 1.0f);
		aiColor4D diffuse;
		if(AI_SUCCESS == aiGetMaterialColor(mtl, AI_MATKEY_COLOR_DIFFUSE, &diffuse))
			color4_to_float4(&diffuse, c);
		memcpy(aMat.diffuse, c, sizeof(c));

		set_float4(c, 0.2f, 0.2f, 0.2f, 1.0f);
		aiColor4D ambient;
		if(AI_SUCCESS == aiGetMaterialColor(mtl, AI_MATKEY_COLOR_AMBIENT, &ambient))
			color4_to_float4(&ambient, c);
		memcpy(aMat.ambient, c, sizeof(c));

		set_float4(c, 0.0f, 0.0f, 0.0f, 1.0f);
		aiColor4D specular;
		if(AI_SUCCESS == aiGetMaterialColor(mtl, AI_MATKEY_COLOR_SPECULAR, &specular))
			color4_to_float4(&specular, c);
		memcpy(aMat.specular, c, sizeof(c));

		set_float4(c, 0.0f, 0.0f, 0.0f, 1.0f);
		aiColor4D emission;
		if(AI_SUCCESS == aiGetMaterialColor(mtl, AI_MATKEY_COLOR_EMISSIVE, &emission))
			color4_to_float4(&emission, c);
		memcpy(aMat.emissive, c, sizeof(c));

		float shininess = 0.0;
		unsigned int max;
		aiGetMaterialFloatArray(mtl, AI_MATKEY_SHININESS, &shininess, &max);
		aMat.shininess = shininess;

		glBufferData(GL_UNIFORM_BUFFER, sizeof(aMat), (void *)(&aMat), GL_STATIC_DRAW);


Model Render

void recursive_render (const aiScene *sc, const aiNode* nd)

	// Get node transformation matrix
	aiMatrix4x4 m = nd->mTransformation;
	// OpenGL matrices are column major

	// save model matrix and apply node transformation

	float aux[16];
	memcpy(aux,&m,sizeof(float) * 16);
	multMatrix(modelMatrix, aux);

	// draw all meshes assigned to this node
	for (unsigned int n=0; n < nd->mNumMeshes; ++n){
		// bind material uniform
		glBindBufferRange(GL_UNIFORM_BUFFER, materialUniLoc, myMeshes[nd->mMeshes[n]].uniformBlockIndex, 0, sizeof(struct MyMaterial));	
		// bind texture
		glBindTexture(GL_TEXTURE_2D, myMeshes[nd->mMeshes[n]].texIndex);
		// bind VAO
		// draw


	// draw all children
	for (unsigned int n=0; n < nd->mNumChildren; ++n){
		recursive_render(sc, nd->mChildren[n]);


Prev: Loading an Image File and Creating a Texture Next: OpenGL 3.3 + GLSL 1.5 Sample

52 Responses to “Importing 3D Models with Assimp”

  1. AssimpModelImport Demo.exe’ (Win32): Loaded ‘C:\Windows\SysWOW64\ntdll.dll’. Symbols loaded.
    ‘AssimpModelImport Demo.exe’ (Win32): Loaded ‘C:\Windows\SysWOW64\kernel32.dll’. Symbols loaded.
    ‘AssimpModelImport Demo.exe’ (Win32): Loaded ‘C:\Windows\SysWOW64\KernelBase.dll’. Symbols loaded.
    The program ‘[20724] AssimpModelImport Demo.exe’ has exited with code -1073741701 (0xc000007b).

  2. Hi !

    In your Import3DFromFile function, you have a magic function called  get_bounding_box(&scene_min, &scene_max) which interests me.

    Is it a function you developped on your own ? I don’t see this function in the Assimp documentation =/

  3. How would the procedure change if the input file is type .step (popular in PCB 3d modeling)?

    Also any thoughts on using assimp with pyqt-opengl? I understand its a good platform to do quick GUI applications.


    • Hi Ben,

      As far as I know Assimp does not support the step format. The only short term solution, until Assimp adds STEP as an aceepted format, would be to convert the model to an accepted format by Assimp.

      Regarding pyqt-opengl I’m afraid I can be of much assistance.

      Best Regards,


  4. If it isn’t too much trouble, could you explain a little about how the texturing process works? My model shows up untextured. It’s an exported .obj from Blender, the texture was usually loaded from an associated png.

    • Hi,

      In the mtl file there should be a path to the texture file. Is the texture placed in the right folder? PNGs are OK for DevIL.

      Hope this helps

  5. From the bottom of my heart, thank you. I’ve spent over a week looking for a single working example for assimp, and yours is the only one I’ve found.

    You should update the tutorial package with the change from “output” in the fragment shader so that it works out of the box.

  6. This is the best written and most effective assimp to opengl loader out there.
    Thank you so much for this. May the beautiful girls bless you.

  7. I have a similar error to Jeffrey but not with texture2D, it’s something indicated as ‘output’ although I’ve searched the whole solution and barely see where it is defined

    Ready for OpenGL 3.3
    Import of scene bench.obj succeeded. WARNING: 0:? : ” : Version number deprecated in OGL 3.0 forward compatible context driver

    No errors.

    WARNING: 0:? : ” : Version number deprecated in OGL 3.0 forward compatible context driver
    ERROR: 0:16: ‘output’ : Reserved word.
    ERROR: 0:16: ‘output’ : syntax error syntax error

    Vendor: Intel
    Renderer: Intel HD Graphics 4000
    Version: 3.3.0 – Build
    GLSL: 3.30 – Build

    How should I fix it?

    • Hi,

      Thansk for the bug report. To fix it, go to the fragment shader and replace the out vec4 named output with outputColor (for example). It happens that, although not used, “output” is a reserved keyword in GLSL.

  8. hi guys! Anybody know to convert to opengl view matrix from dx view matrix?

  9. I run the demo and got the following output:
    Ready for OpenGL 3.3
    Import of scene bench.obj succeeded.WARNING: 0:? : ” : Version number deprecate
    d in OGL 3.0 forward compatible context driver

    No errors.

    WARNING: 0:? : ” : Version number deprecated in OGL 3.0 forward compatible cont
    ext driver
    ERROR: 0:35: ‘texture2D’ : function is removed in Forward Compatibile context
    ERROR: 0:35: ‘texture2D’ : no matching overloaded function found (using implicit
    ERROR: 0:35: ‘assign’ : cannot convert from ‘const float’ to ‘4-component vecto
    r of float’

    Vendor: Intel
    Renderer: Intel(R) HD Graphics 2500
    Version: 3.3.0 – Build
    GLSL: 3.30 – Build

    It seens that the code color = texture2D(texUnit, TexCoord); in shader dirlightdiffambpix.frag not working.

    • Hi,

      “texture2D” is in fact deprecated, although NVIDIA drivers don’t complain. You could replace it with “texture”. That should remove the problems in the shader.

      P.S: The demo has been updated

  10. Is it possible to share a full source file. Because I can’t use it without errors.
    Will this code work with Assimp 3.0.1270?

  11. I realize this is a little old now, so it doesn’t compile straight out of the box. Assimp headers need to change for the latest version (below) and most of the “struct” keywords needs to be removed (at least VS2012 doesn’t like them). Also, all the libraries need to be compiled and attached. Other than that it works.


  12. This tutorial has too many missing parts to it

  13. > void main()
    – I’d seen enough.

    And all the ‘struct aiXXXX’ instances. The includes relying on case-insensitivity. All the manual memory management. And then passing a std::string as a format parameter to printf. Unused variables. Mutable `char*` for literals…

    Based on the sheer amount of trouble I wouldn’t exactly recommend this code to people learning assimp/devil.

  14. Line 21 under Vertex Array Objects has sizeof(float) but the indices are unsigned ints; is that intentional?

  15. I am having problems with struct aiVector3D scene_min… I am using C++, and the problem is “incomplete type”, if I change to aiVector3D* scene_min, the problem is when I try to use scene_min-> (then, it says pointer to incomplete class type)…

    What can I do?

  16. Why there are recusive render but not use for(int i=0; imNumMesh; ++i) {render scene->mMeshes[i]; }??

    • I think it’s because of how assimp “scene graph” is made. Each object may have subobjects and they might have their own subobjects and … :)
      Which is why it kind of “requires” recursive rendering.

  17. You also say that you’re creating a material uniform buffer but that doesn’t seem to be the case:

    // create material uniform buffer
    struct aiMaterial *mtl = sc->mMaterials[mesh->mMaterialIndex];

    Why aren’t you creating the material uniform buffer like the matrices ubo?

  18. In this tutorial, the recursive render function tries to access the root Node but when I implemented this, by the time the program had reached the render function, the importer had gone out of scope and as a result the data in the scene object had been destroyed and so the data being pointed to was invalid. Didn’t anyone else run into this problem?

  19. I got this code working perfect with the model that comes with it (bench.obj and bench.mtl).
    However, any other models that either created by myself using 3dsMax or downloaded from Internet doesn’t work as expected. They are either loaded without texture or are not loaded at all. When not loaded, I usually get error: vector::_M_range_check.
    Is there anything special with the way you export bench.obj and bench.mtl?
    Anyway, thanks a lot for the code.

    • Hi Mike,

      No, the bench model has nothing special. Can you send me one of the models that cause an error? What file types are you trying to load?

  20. Sir

    I am starting the whole project from scratch

    Some how i am not able to create shader

    if you see the snippet it never pass the after the variable declared check point

    Some how it is not able to execute these command v = glCreateShader(GL_VERTEX_SHADER); f = glCreateShader(GL_FRAGMENT_SHADER);

    On debugging

    Unhandled exception at 0x773a15de in SimpleOpenGL_Debug.exe: 0xC0000005: Access violation reading location 0x00000000.

    Any sight will be appreciated


    ——————————————————————————–Snipper for setupShader————————————————

    GLuint setupShaders() {

    const char * vertexShaderSource = {
    “#version 130\n”
    “in vec3 position;”
    “void main () {”
    ” gl_Position = position;”
    const char * fragmentShaderSource = {
    “#version 130\n”
    “out vec4 fragColor;”
    “void main() {”
    ” fragColor = vec4(0.5, 0.5, 1.0, 1.0);”

    GLuint p,v,f;
    cout<< "variables declared check point";
    v = glCreateShader(GL_VERTEX_SHADER);
    f = glCreateShader(GL_FRAGMENT_SHADER);
    cout<< "create shader";
    glShaderSource(v, 1, &vertexShaderSource, NULL);
    glShaderSource(f, 1, &fragmentShaderSource, NULL);
    cout<< "compile shader";

    cout<< "finish compiling shader";

    program = glCreateProgram();
    glAttachShader(p, v);
    glAttachShader(p, f);

    glBindFragDataLocation(program, 0, "output");

  21. i am getting an unhandled exception
    Unhandled exception at 0x01e6e620 in Adv_Graphics.exe: 0xC0000005: Access violation reading location 0x00000000.

    its in the line 26 of the Model Render code
    // draw

    pls help

  22. I was trying to run the program. but i got a breakpoint

    #undef _CrtDbgBreak

    /* These methods don’t need a separate
    wchar version. Hence they need to be compiled only once from
    the original file */

    _CRTIMP void _cdecl _CrtDbgBreak(

    can u please help

  23. Hi Jose,

    The function pushMatrix is part of the source code. Perhaps you’re confusing it with glPushMatrix?

  24. Thanks! Just what I was looking for after I went down several false paths.

    I did run into an issue loading dae (Collada) files exported from Sketchup, because it drops the textures in a directory below the model file. I got around this by passing the path to the model file to LoadGLTextures and trying to load relative to that path if the first load failed (too lazy to test the paths first – not for production code). Relevant hacks:

    in init:
    LoadGLTextures(scene, dirname(strdup(modelname.c_str())));

    in LoadGLTextures:
    success = ilLoadImage((ILstring)filename.c_str());
    // if it doesn't load to start with, look for it relative to the
    // source file
    if (!success) {
    fs::path temp_basepath (basepath);
    fs::path temp_filename (filename.c_str());
    fs::path full_path = temp_basepath / temp_filename;
    success = ilLoadImage((ILstring)full_path.string().c_str());

    if (success) {

    in header:
    namespace fs = boost::filesystem;

  25. I tried to port the code provided in ASSIMP’s sample code to glut & OpenGL. For some reasons, I avoid using GLSL & OpenGL 3.x context. I found some models loaded incorrectly, and I think there is something wrong with how the code access the normal and vertex positions from the mesh.

    for(i = 0; i mNumIndices; i++){
    int vertexIndex = face->mIndices[i]; // get group index for current index
    if(mesh->mColors[0] != NULL)
    if(mesh->mNormals != NULL)
    glTexCoord2f(mesh->mTextureCoords[0][vertexIndex].x, mesh->mTextureCoords[0][vertexIndex].y);

    Do you have any suggestion on how to fix this problem?

  26. Hello!
    I’m trying to get this example to work but I can’t seem to get all the linkings properly, I’m using CodeBlocks, can you tell me exactly what files do I need to link?

    Thanks in advance.

  27. The code has been compiled successfully. However, VS place the executable file inside ‘Debug’ or ‘Release’ folder. Therefore, the executable can not recognize the shaders and obj file. This is resulting Access Violation error. When we run the executable manually with shaders and obj file placed at the same directory, the executable runs smoothly.

    Please ignore my not-yet-moderated comment. :) . Indeed, i think it is important to include the cmakelists.txt file.

    Thank you. Your site is amazing!

  28. I tried to compile the source using VS2008, unfortunately, I got access violation error: Unhandled exception at 0x69a59436 in modelimport.exe: 0xC0000005: Access violation reading location 0x00000000.

    I believe ASSIMP and DevIL has been installed correctly.

    Would you mind to provide the project file or instruction to compile this? Thanks! :)

  29. Do you have any zipped archive of your sample? one containing .cpp and .vcproj? Please insert it if you have! we are kind of new to ASSIMP and not so familiar with loading meshes.

  30. what’s with the redefinition of some GL functions in the source code, aren’t these already defined in GLEW?

    glGetUniformBlockIndex = (PFNGLGETUNIFORMBLOCKINDEXPROC) glutGetProcAddress("glGetUniformBlockIndex");
    glUniformBlockBinding = (PFNGLUNIFORMBLOCKBINDINGPROC) glutGetProcAddress("glUniformBlockBinding");
    glGenVertexArrays = (PFNGLGENVERTEXARRAYSPROC) glutGetProcAddress("glGenVertexArrays");
    glBindVertexArray = (PFNGLBINDVERTEXARRAYPROC)glutGetProcAddress("glBindVertexArray");
    glBindBufferRange = (PFNGLBINDBUFFERRANGEPROC) glutGetProcAddress("glBindBufferRange");
    glDeleteVertexArrays = (PFNGLDELETEVERTEXARRAYSPROC) glutGetProcAddress("glDeleteVertexArrays");

© 2014 Suffusion theme by Sayontan Sinha