Skeletal Animation

From OpenGL.org
Jump to: navigation, search

This document will talk about doing skeletal based animation but it will be brief since the subject is complex and there are also other sources (websites and books and demos at http://developer.nvidia.com in the SDK, the tutorial here, and individual downloads, http://content.gpwiki.org/index.php?title=OpenGL:Tutorials:Basic_Bones_System).
You would need to model the object or human or animal in the software of your choice and export it in the file format of your choice.

Here is a description of one way to do it :
1. Your object will be in a default pose.
2. All the vertices are in their respective places.
3. You have an array of bones.
3. You have an array of offsets.
4. What's a bone? It is a rotation matrix. You only need a 3x3 matrix.
5. What's an offset? It is a XYZ value you use to translate your vertex.
6. A bone and its offset together make a 4x4 matrix.
7. Each vertex can have a certain amount of bones that influence it. 0, 1, 2, ...you name it!
8. For each bone that influences your vertex, you need to decide how much it influences. This is called a weight or blend factor. 1 weight per bone/offset matrix
9. In order to animate your object, you need to change the bones and offset matrix. Normally, you would not change the weights.

Let's assume our entire model will have 10 bones. Let's assume each vertex will have 2 bones.
So your vertex structure would look something like this :

 struct MyVertex
 {
   float x, y, z;      //Vertex
   float nx, ny, nz;   //Normal
   float s0, t0;       //Texcoord
   float index0, index1;     //Index into the bone/offset matrix array (2 bones)
   float weight0, weight1;   //The blend factor for each bone/offset matrix (2 bones)
 };

You would be boning your vertex and normals. The texcoords just pass through.
If you have tangent vectors, you would bone them as well.
Warning : notice that each bone matrix will consume a lot of register space on the GPU. Each matrix takes 4 registers.
Since our example will have 10 bones (mat4), that makes 40 registers (vec4), which isn't really a problem for any GPU but typically, your model may have way more bones, perhaps 200 bones.
Instead of uploading a mat4 for each bone, upload a pair of quaternion and offset. A quat uses a vec4 and offset uses a vec4.
As a result, you'll cut the register need of your shader in half.
Of course, your would have to convert them to a mat4 in your shader which will cost you a few clock cycles.
Here, we'll use a mat4 matrix.
Notice that in the shader example unlike the examples on the other Wiki pages, we aren't using any built in features of GL. We aren't using gl_Vertex, gl_MultiTexCoord0, ftransform(), gl_ProjectionModelViewMatrix, gl_Normal, etc. We are using our own attribute names and our own varying and our own uniforms.

 //[Vertex Shader]
 attribute vec4 Vertex;
 attribute vec3 Normal;
 attribute vec2 TexCoord;
 attribute vec2 Index;
 attribute vec2 Weight;
 uniform mat4 ModelviewMatrix;
 uniform mat4 ProjectionModelviewMatrix;
 uniform mat4 Bone[10];  //Array of bones that you compute (animate) on the CPU and you upload to the shader
 //-----------------------
 varying vec2 TexCoord0;
 varying vec3 EyeNormal;
 //-----------------------
 void main()
 {
   vec4 newVertex;
   vec4 newNormal;
   int index;
   //-----------------------
   index=int(Index.x);    //Cast to int
   newVertex = (Bone[index] * Vertex) * Weight.x;
   newNormal = (Bone[index] * vec4(Normal, 0.0)) * Weight.x;
   index=int(Index.y);    //Cast to int
   newVertex = (Bone[index] * Vertex) * Weight.y + newVertex;
   newNormal = (Bone[index] * vec4(Normal, 0.0)) * Weight.y + newNormal;
   EyeNormal = vec3(ModelviewMatrix * newNormal);
   gl_Position = ProjectionModelviewMatrix * newVertex;
   TexCoord0 = TexCoord;
 }

Now that you have studied the code above, what's the problem?

   gl_Position = ProjectionModelviewMatrix * newVertex;

There is a risk that newVertex.w might not be exactly 1.0. Just to be safe

   gl_Position = ProjectionModelviewMatrix * vec4(newVertex.xyz, 1.0);