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 |

> 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.
Line 21 under Vertex Array Objects has sizeof(float) but the indices are unsigned ints; is that intentional?
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?
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.
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?
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?
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 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?
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);
}
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
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
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
Hi Jose,
The function pushMatrix is part of the source code. Perhaps you’re confusing it with glPushMatrix?
Oh! I was confusing it yes. Thanks for a great tutorial anyway. Keep it up!
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;
The munged includes should be libgen.h and boost/filesystem.hpp
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?
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.
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!
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!
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.
I’ll add a Cmake file as soon as I have some time. Thanks for the suggestion.
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\");
Well, yes and no. They become accessible if you use glewExperimental = true, but by default they are not accessible.
See this previous post.