tl;dr I wrote a tiny open source skeletal animation and software skinning library for C++: https://github.com/FakeTruth/SkeletalAnimation
Why Skeletal Animations?
Animations can really bring life to projects, that's why for some projects I am working on I want to have skeletal animation. Looking around on the internet I could not really find an open source solution that did what I wanted. I was looking for something really simple.
ozz-animation
On my Google adventure I came across ozz-animation which I thought would do exactly what I was looking for. A complete package for animating skeletons and software skinning. There was a problem though. This library added tools to convert FBX files to skeletons and animations, and one tool to convert FBX files to a skinned mesh. The mesh however does not contain materials or texture coordinates or any other things you might be interested in, just vertices and normals. The source of the converters also seem really complicated so I was not sure how to add materials and texture coordinates to that. Instead I decided to roll out my own library, which may be useful to other people as well.
My own open source solution
As it turned out, it was really easy to write a library that handles skeletal animation and skinning. Following a simple tutorial that actually included skinning in vertex shaders I was able to write a library that does the skinning on the CPU.
Included in my library is also a converter that converts loaded Assimp scenes to models that can be animated, as well as (de)serialization functions to store and load these models on and from disk.
Why software skinning? Just for the sake of simplicity. I am not looking to animate models with millions of vertices, just simple models which can easily be skinned on the CPU. It is also easier to port to different platforms without worrying about shader languages.
It is really easy to load a model from Assimp:
void LoadModel() { Assimp::Importer Importer; const aiScene* pScene = Importer.ReadFile("some_animated_model.fbx", aiProcess_LimitBoneWeights | aiProcess_Triangulate | aiProcess_JoinIdenticalVertices | aiProcess_SortByPType); AssimpConverter::Convert(pScene, g_AnimatedModel); }
Then to make the model animate, just call the Update function:
void Update(float a_Dt) // a_Dt is the elapsed time since last frame in seconds { g_AnimatedModel.Update(a_Dt); }
Rendering the model is very straightforward because you have direct access to the model's vertices and normals. As an example here is one way how you could render the model with OpenGL.
void Render() { for (unsigned int i = 0; i < g_AnimatedModel.GetNumMeshes(); ++i) { const SA::sAnimatedMesh& AnimMesh = g_AnimatedModel.GetMesh(i); glBegin(GL_TRIANGLES); for (unsigned int i = 0; i < AnimMesh.NumIndices; ++i) { unsigned int Index = AnimMesh.pIndices[i]; glm::vec3 n = AnimMesh.pNormals[Index]; glm::vec3 v = AnimMesh.pTransformedVertices[Index]; glColor4f(n.x, n.y, n.z, 1); glVertex3f(v.x, v.y, v.z); } glEnd(); } }
The license I decided to go with is the MIT License, so why not check it out? https://github.com/FakeTruth/SkeletalAnimation
This looks great, it’s weird that there are so few books on skeletal animation so I’ve been looking for all the source code I can find. Do you have any plans to implement blend shapes?
Hi Nicholas. There are no plans to implement blend shapes, feel free to extend the project with your own implementations though.
Hi mate, you project is quite interesting , i was hoping to find a way of implementing new skinning methods based on your code , but the skinning is part of your model handling class. i’m working on a project where i need to implement a certain skinning method and i’m looking for some basecode handeling loading and reading animation and the basic LBS skinning.
Hi Bobmaza. There is definitely no seperation of skinning methods in my implementation, but because the amount of code is quite small maybe you could separate it yourself. The model loading in this project is done by Assimp, maybe you can at least use that part. Good luck!
Hi! I have two questions about this library.
1) how does the timing work for the update command.
2) do you have any sample rendering code that uses a vbo. I just am not sure how to use the dynamic draw to draw the animations to the screen.
Thanks in advance, Rory
Hi Rory, thanks for your reply.
1. The idea for the update command is that you call it once every frame before rendering the model, and pass in the time in seconds elapsed between frames (so 0.0166.. for 60 frames per second).
2. I don’t have sample code that renders by using VBO. It’s been quite a while since I worked on any OpenGL code, but I think on way to update a VBO is by using glBufferData. You could try passing in the transformed vertices to that method every frame.
Do you still have the original model you tested with? I’ve tried several simple, animated .fbx models so far which render okay prior to any update call; however, once update is called, pTransformedVertices comes back with vertex values that aren’t anywhere close to the values in pVertices. I would expect those values to be identical at time 0.0f, unless I’m mistaken? I’m still trying to figure out if the models aren’t any good (though they load and animate in Blender), if the assimp conversion is missing something, or if update is doing something unexpected. I’ve found that just multiplying the inVertex by Weight (without the Transform multiply) gives me almost sane numbers, but they’re still not quite right. Thanks.
Hi Shatteredmoon,
I dug around some of my old projects around that time but unfortunately couldn’t find any .fbx models ?. It doesn’t sound right that update would mess up all the vertex positions. Did you manage to solve this eventually? I saw you also created an issue on GitHub.
It’s possible that the project became incompatible with 3rd party dependencies.
This may seem strange, but I’ve only been able to get this library to work with models exported and animated in blender 2.79 or earlier. Was there an update to the collada standard in that time that you know of?