Blender, Assimp, OpenGL and animations

Started by
4 comments, last by teutonicus 12 years ago
HI,

Looking for advice in this area. I have a game defined in OpenGL, and static models defined in Blender, exported to OBJ, loaded with Assimp. I have an artist that can create animations in Blender. Problem is, this person doesn't know how to export into a suitable format, and I don't know what format to use (we are both new into the animation business). After spending several hours on Internet as well as testing, I did not come to a conclusion how to best do it.

The first question seems to be whether to use key frames or bones data (in the game engine). I know how to define a bones structure. I think key frames are easier, but less flexible, and would be the best start.

There are many formats to choose from. I am currently using Blender 2.62, but there seems to be problems doing export. For example, the export to X-format will crash with an error in the Python script, and I would prefer not to debug that. Any advice in a format that can be used, and a version of Blender that supports export from it?

Assimp can import directly from blender files, but I don't think that is the correct solution. Blender files contains much more than should go into the final game.
[size=2]Current project: Ephenation.
[size=2]Sharing OpenGL experiences: http://ephenationopengl.blogspot.com/
Advertisement
Hi,

I had the same problem in my game engine 2 weeks ago. My artist is using Blender too. He exports his models as COLLADA-files and my importer class reads them via rapidXml written in C++. The COLLADA-file contains the geometry, materials, physics data, skeleton / bones and animations all in one. There are nice tutorials how to parse this format, for example: http://www.wazim.com..._Tutorial_1.htm
But for reasons of performance you have to use GLSL and implement the skeletal animation in the vertex shader, the calculation on the CPU would be to slowly.
I used to use OBJ as well, but it contains only information about the geometry of the model. COLLADA has many advantages but it is difficult to parse (the source code of my importer class has a size of 42KB).

Here a example, how a hierarchic structure of bones in a skeleton is implemented in my engine:

struct Bone {
Matrix4 staticMat;
unsigned int jointIndex;
std::string name;
std::vector<Bone*> children;
};

struct Skeleton {
Bone* rootBone;
std::map<std::string, Bone*> bones;
};

class Mesh {
public:
GLuint vbo, ibo;
unsigned int elementsCount;
int postions, texcoords, normals, tangents, bitangents, weightJoints;
Texture *diffuse, *normalMap, *effectMap;
Mesh();
~Mesh();
void draw();
};

struct BonePose {
Matrix4 poseMat, dynamicMat;
};

class SkeletonPose {
public:
std::map<std::string, BonePose*> bonePoses;
SkeletonPose();
SkeletonPose(Skeleton* skeleton);
~SkeletonPose();
void calculateBonePose(Bone* bone, Bone* parentBone);
void calculateDisplayMatrix(Bone* bone, Bone* parentBone, Matrix4* mats);
};

class Model {
public:
unsigned int useCounter;
std::vector<Mesh*> meshes;
Skeleton* skeleton;
Model();
~Model();
bool loadCollada(FilePackage* filePackage, const char* filePath);
void draw();
void draw(SkeletonPose* skeletonPose);
};


The model contains static meshes and a static skeleton. The animation is done via SkeletonPose as an argument of the model.draw() call. The SkeletonPose contains the dynamic information about the pose which should be used to render the model. So the animations can be easily implemented:

//Init
Model* humanModel = new Model();
humanModel->loadCollada(NULL, "human.dae");
SkeletonPose* skeletonPose = new SkeletonPose(humanModel->skeleton);

//Draw frame
mainShaderProgram->use();
skeletonPose->bonePoses["Back"]->poseMat.setIdentity();
skeletonPose->bonePoses["Back"]->poseMat.rotateY(0.4);
skeletonPose->bonePoses["Back"]->poseMat.translate(Vector3(0.0, sin(animationTime)*0.5-0.8, 0.0));
float thighRotX = (1.0-sin(animationTime))*0.6,
shinRotX = -0.8*(1.0-sin(animationTime)),
footRotX = 0.4-0.8*(1.0+sin(animationTime));
skeletonPose->bonePoses["Thigh_Right"]->poseMat.setIdentity();
skeletonPose->bonePoses["Thigh_Right"]->poseMat.rotateX(thighRotX);
skeletonPose->bonePoses["Thigh_Left"]->poseMat.setIdentity();
skeletonPose->bonePoses["Thigh_Left"]->poseMat.rotateX(thighRotX);
skeletonPose->bonePoses["Shin_Right"]->poseMat.setIdentity();
skeletonPose->bonePoses["Shin_Right"]->poseMat.rotateX(shinRotX);
skeletonPose->bonePoses["Shin_Left"]->poseMat.setIdentity();
skeletonPose->bonePoses["Shin_Left"]->poseMat.rotateX(shinRotX);
skeletonPose->bonePoses["Foot_Right"]->poseMat.setIdentity();
skeletonPose->bonePoses["Foot_Right"]->poseMat.rotateX(footRotX);
skeletonPose->bonePoses["Foot_Left"]->poseMat.setIdentity();
skeletonPose->bonePoses["Foot_Left"]->poseMat.rotateX(footRotX);
skeletonPose->bonePoses["Fingers1_Right"]->poseMat.setIdentity();
skeletonPose->bonePoses["Fingers1_Right"]->poseMat.rotateZ(-1.3*(cos(animationTime)*0.5+0.5));
skeletonPose->bonePoses["Fingers2_Right"]->poseMat.setIdentity();
skeletonPose->bonePoses["Fingers2_Right"]->poseMat.rotateZ(-1.3*(cos(animationTime)*0.5+0.5));
skeletonPose->calculateBonePose(humanModel->skeleton->rootBone, NULL);
humanModel->draw(skeletonPose);

//At the end of the code
delete skeletonPose;
delete humanModel;


I hope this could help you.
Thanks for your answer, I'll have a look at the proposed data structure!

I eventually managed to read the data using Assimp, although I understand it is not an optimal library (and your proposal may be better). It turned out that bones did not work with Assimp until I turned off the flag aiProcess_PreTransformVertices.

As the next step, I have indeed been thinking about using the vertex shader to compute the animation, but I am not sure. The graphics is the bottle neck in my application, and maybe I can use a separate process for animation computation (using shared memory with OpenGL). Obviously, I have no measurements yet that helps assess the best solution.
[size=2]Current project: Ephenation.
[size=2]Sharing OpenGL experiences: http://ephenationopengl.blogspot.com/

Assimp can import directly from blender files, but I don't think that is the correct solution. Blender files contains much more than should go into the final game.


The ideal is to have a tool in your content pipeline which imports your models via Assimp or similar and then converts them to an optimised binary format that your game can load directly. So the slow model import/conversion part happens offline whenever your assets are rebuilt, rather than every time your game loads a model.

The ideal is to have a tool in your content pipeline which imports your models via Assimp or similar and then converts them to an optimised binary format that your game can load directly. So the slow model import/conversion part happens offline whenever your assets are rebuilt, rather than every time your game loads a model.


I used to do the importing the same way, but I can say from my own experience that inventing a own binary format and converting all files in to this format before they are loaded into the game is a bad idea:
1. There will be many error sources in the file importer and exporter
2. You have to convert your files every time you change the model
3. It's hard to debug because it isn't human readable
4. You would have to write a new converter for each other file format which you use to get the model data
5. Little and big endian can cause problems

Of course this way has some advantages too:
1. The files are smaller, because its binary and no ascii
2. The importer might be much faster

However I decided to use only established file formats and in my opinion COLLADA is one of the best for animated models.





As the next step, I have indeed been thinking about using the vertex shader to compute the animation, but I am not sure. The graphics is the bottle neck in my application, and maybe I can use a separate process for animation computation (using shared memory with OpenGL). Obviously, I have no measurements yet that helps assess the best solution.



If you really want to use a separate process, you might take into consideration to use OpenCL but a animation shader is very easy and needs only a few lines of code. Vertex shader:

attribute vec3 position;
attribute vec2 texCoord;
attribute vec3 normal;
attribute vec3 weights;
attribute vec3 joints;
uniform mat4 jointMats[64];
uniform mat4 viewMat;
varying vec2 vTexCoord;
varying vec3 vNormal;
void main() {
mat4 mat = mat4(0.0);
mat += weights[0]*jointMats[int(joints[0])];
mat += weights[1]*jointMats[int(joints[1])];
mat += weights[2]*jointMats[int(joints[2])];
gl_Position = vec4(position, 1.0) * mat * viewMat;
vTexCoord = texCoord;
vNormal = normalize((vec4(normal, 0.0) * mat).xyz);
}


And fragment shader:

uniform sampler2D sampler0;
varying vec2 vTexCoord;
varying vec3 vNormal;
void main() {
gl_FragColor = texture2D(sampler0, vTexCoord);
gl_FragColor.rgb *= vNormal.z;
}

I used to do the importing the same way, but I can say from my own experience that inventing a own binary format and converting all files in to this format before they are loaded into the game is a bad idea:
1. There will be many error sources in the file importer and exporter
2. You have to convert your files every time you change the model
3. It's hard to debug because it isn't human readable
4. You would have to write a new converter for each other file format which you use to get the model data
5. Little and big endian can cause problems

Of course this way has some advantages too:
1. The files are smaller, because its binary and no ascii
2. The importer might be much faster

However I decided to use only established file formats and in my opinion COLLADA is one of the best for animated models.


1. The errors in your asset pipeline that are actually going to be time consuming to fix (usually broken/malformed source assets due to bad exports from DCC tool) are present regardless of whether you convert them offline or at load time.

2. As compared to converting your files every time you load them in-game. Doing it offline is a win.

3. Your source assets (ie. the things that get converted to your binary format) could still be human readable. You could use COLLADA, ASE, OBJ, etc. as your source asset format. Not that I've ever found this useful.

4. Assimp solves this. Or just use COLLADA, FBX or similar as your source asset format.

5. You pay upfront cost in developing the proper tools for this, but otherwise it should not be a big problem.

You can and should use established file formats. Then, ideally, as part of your asset build process, convert them to optimised binary format which is used by your game.

This topic is closed to new replies.

Advertisement