Help those suffering in the Horn of Africa

Importing 3D Models with Assimp

Prev: OpenGL 3.3 + GLSL 1.5 Sample Next: OpenGL 3.3 + GLSL 1.5 Sample
 

Importing 3D models is easy with Assimp. This sample was based on the Assimp’s demo and extends 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.

The full source code and OBJ model are available in here.

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(!fin.fail()) {
		fin.close();
	}
	else{
		printf("Couldn't open file: %s\n", pFile);
		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());

	struct 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 */
	ilInit(); 

	/* scan scene's materials for textures */
	for (unsigned int m=0; mmNumMaterials; ++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[path.data] = 0;
			// more textures?
			texIndex++;
			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::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 */
		ilEnable(IL_ORIGIN_SET);
		ilOriginFunc(IL_ORIGIN_LOWER_LEFT);
		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]);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, ilGetInteger(IL_IMAGE_WIDTH),
				ilGetInteger(IL_IMAGE_HEIGHT), 0, GL_RGBA, GL_UNSIGNED_BYTE,
				ilGetData());
		}
		else
			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); 

	//Cleanup
	delete [] imageIds;
	delete [] textureIds;

	//return success;
	return true;
}

Vertex Array Objects

void genVAOsAndUniformBuffer(const struct aiScene *sc) {

	struct MyMesh aMesh;
	struct MyMaterial aMat;
	GLuint buffer;

	// For each mesh
	for (unsigned int n = 0; n < sc->mNumMeshes; ++n)
	{
		const struct 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 struct aiFace* face = &mesh->mFaces[t];

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

		// generate Vertex Array for mesh
		glGenVertexArrays(1,&(aMesh.vao));
		glBindVertexArray(aMesh.vao);

		// 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);
			glEnableVertexAttribArray(vertexLoc);
			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);
			glEnableVertexAttribArray(normalLoc);
			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);
			glEnableVertexAttribArray(texCoordLoc);
			glVertexAttribPointer(texCoordLoc, 2, GL_FLOAT, 0, 0, 0);
		}

		// unbind buffers
		glBindVertexArray(0);
		glBindBuffer(GL_ARRAY_BUFFER,0);
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,0);

		// create material uniform buffer
		struct 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[texPath.data];
				aMesh.texIndex = texId;
				aMat.texCount = 1;
			}
		else
			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;

		glGenBuffers(1,&(aMesh.uniformBlockIndex));
		glBindBuffer(GL_UNIFORM_BUFFER,aMesh.uniformBlockIndex);
		glBufferData(GL_UNIFORM_BUFFER, sizeof(aMat), (void *)(&aMat), GL_STATIC_DRAW);

		myMeshes.push_back(aMesh);
	}
}

Model Render

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

	// Get node transformation matrix
	struct aiMatrix4x4 m = nd->mTransformation;
	// OpenGL matrices are column major
	m.Transpose();

	// save model matrix and apply node transformation
	pushMatrix();

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

	// 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
		glBindVertexArray(myMeshes[nd->mMeshes[n]].vao);
		// draw
		glDrawElements(GL_TRIANGLES,myMeshes[nd->mMeshes[n]].numFaces*3,GL_UNSIGNED_INT,0);

	}

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

 

Prev: OpenGL 3.3 + GLSL 1.5 Sample Next: OpenGL 3.3 + GLSL 1.5 Sample
 

25 comments on “Importing 3D Models with Assimp

  1. sehe on said:

    > 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.

  2. Ben on said:

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

  3. awefaweffawefawef on said:

    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?

  4. Mr. Bình on said:

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

    • Asdf on said:

      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.

  5. jose cuervo on said:

    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?

  6. jose cuervo on said:

    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?

  7. Mike on said:

    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.

    • ARF on said:

      Hi Mike,

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

  8. Aditya on said:

    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 0×00000000.

    Any sight will be appreciated

    Aditya

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

    GLuint setupShaders() {

    const char * vertexShaderSource = {
    “#version 130n”
    “in vec3 position;”
    “void main () {”
    ” gl_Position = position;”
    “}”
    };
    const char * fragmentShaderSource = {
    “#version 130n”
    “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";
    printf("n");
    glShaderSource(v, 1, &vertexShaderSource, NULL);
    printf("n");
    glShaderSource(f, 1, &fragmentShaderSource, NULL);
    cout<< "compile shader";
    printf("n");

    glCompileShader(v);
    glCompileShader(f);
    cout<< "finish compiling shader";
    printf("n");

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

    glBindFragDataLocation(program, 0, "output");
    glBindAttribLocation(program,vertexLoc,"position");
    glBindAttribLocation(program,normalLoc,"normal");
    glBindAttribLocation(program,texCoordLoc,"texCoord");
    glLinkProgram(p);
    glValidateProgram(p);
    return(p);
    }

  9. John on said:

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

    its in the line 26 of the Model Render code
    // draw
    glDrawElements(GL_TRIANGLES,myMeshes[nd->mMeshes[n]].numFaces*3,GL_UNSIGNED_INT,0);

    pls help

    • jose cuervo on said:

      The assimp importer is probably going out of scope before that call. The importer object keeps ownership of the data and will destroy it upon destruction.

      Check the api docs:
      http://assimp.sourceforge.net/lib_html/class_assimp_1_1_importer.html#339882c7acb47d5b5110bbd078d870a9

  10. John on said:

    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(
    void
    )
    {
    __debugbreak();
    }

    can u please help

  11. ARF on said:

    Hi Jose,

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

  12. Cory on said:

    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:
    #include
    #include
    namespace fs = boost::filesystem;

  13. fam on said:

    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.

    glBegin(face_mode);
    for(i = 0; i mNumIndices; i++){
    int vertexIndex = face->mIndices[i]; // get group index for current index
    if(mesh->mColors[0] != NULL)
    Color4f(&mesh->mColors[0][vertexIndex]);
    if(mesh->mNormals != NULL)
    if(mesh->HasTextureCoords(0)){
    glTexCoord2f(mesh->mTextureCoords[0][vertexIndex].x, mesh->mTextureCoords[0][vertexIndex].y);
    }
    glNormal3fv(&mesh->mNormals[vertexIndex].x);
    glVertex3fv(&mesh->mVertices[vertexIndex].x);
    }
    glEnd();

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

  14. Eze on said:

    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.

  15. snowball147 on said:

    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!

  16. snowball147 on said:

    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 0×00000000.

    I believe ASSIMP and DevIL has been installed correctly.

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

  17. A2 on said:

    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.

  18. randallr on said:

    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\");

    • ARF on said:

      Well, yes and no. They become accessible if you use glewExperimental = true, but by default they are not accessible.

      See this previous post.

Leave a Reply

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

*

84,370 Spam Comments Blocked so far by Spam Free Wordpress

HTML tags are not allowed.

© 2013 Lighthouse3d.com Suffusion theme by Sayontan Sinha