Keyframe Animation

From OpenGL Wiki
Jump to navigation Jump to search

Keyframe Animation is easier to do than Skeletal Animation. This technique is used when skeletal animation is not appropriate for example, when you want to deform a mesh in some radical way. The downside is that keyframe animation can consume a lot of VBO space.

The concept is the following. You have snapshots of a mesh with different poses. These snapshots are called the keyframes. As time passes, you compute the "current frame" using the old vertex position and newer vertex position. You would also have to compute the normals. Texcoords don't change. If you have tangent vectors, you have to compute these as well. The indices never change between keyframes. The number of triangles never change between keyframes. The number of vertices never change between keyframes.

For simplicity, let's assume our vertex format has vertex, normals and texcoords (aka VNT).

struct MyVertex_VNT
{
    float x, y, z;
    float nx, ny, nz;   // Normals
    float s0, t0;       // Texcoords
};

What is the problem with that? The problem is that you don't have access to the "next keyframe". The solution is to have a special vertex format

struct MyVertex_VNTVN
{
    float x, y, z;       // Vertex for this keyframe
    float nx, ny, nz;    // Normals for this keyframe
    float s0, t0;        // Texcoords
    float x1, y1, z1;    // Vertex for the next keyframe
    float nx1, ny1, nz1; // Normals for the next keyframe
};

So when you allocate your VBO, you would have to generate that vertex format by copying some vertices and normals around. You can store all the keyframes in a single VBO if you want by making a large VBO.

When you run your program, based on the amount of time passed, you have to decide which keyframe will be shown. You also need a tween factor. The tween factor is a value that goes from 0.0 to 1.0.

And finally, the vertex shader. This is in GLSL and it is GL 2.0 compatible but it is easily convertable to GL 3.0. The fragment shader is not important so it isn't listed here.

// Vertex Shader
// ATTRIBUTES
attribute vec3 InVertex;
attribute vec3 InNormal;
attribute vec2 InMultiTexCoord0;
attribute vec3 InVertex1;   // The Next keyframe vertex
attribute vec3 InNormal1;   // The Next keyframe normal
// UNIFORM
uniform vec4 LightPosition0;
uniform mat4 ModelviewMatrix;
uniform float TweenFactor;
uniform mat4 ProjectionModelviewMatrix;
// --------------------
// VARYING
varying vec2 TexCoord0;
varying vec4 LightVector0; // xyz is lightvector and w is light distance from vertex
varying vec3 HalfVector0;
varying vec3 EyeNormal;
// --------------------
void main()
{
    vec3 eyeVertex;
    vec3 lightVector, eyeVector;
    float mysqrtdistance;
    vec4 newVertex;
    vec3 newNormal;
    // --------------------
    newVertex.xyz = mix(InVertex, InVertex1, TweenFactor);
    newVertex.w = 1.0; // Make sure w is exactly 1.0
    // --------------------
    gl_Position = ProjectionModelviewMatrix * newVertex;
    TexCoord0 = InMultiTexCoord0;
    // --------------------
    eyeVertex = vec3(ModelviewMatrix * newVertex);
    eyeVector = normalize(-eyeVertex);
    lightVector = normalize(LightPosition0.xyz - eyeVertex);
    LightVector0.xyz = lightVector;
    mysqrtdistance = sqrt(distance(LightPosition0.xyz, eyeVertex));
    LightVector0.w = mysqrtdistance * sqrt(mysqrtdistance);
    HalfVector0 = lightVector + eyeVector; // No need to normalize the sum
    // --------------------
    newNormal = mix(InNormal, InNormal1, TweenFactor);
    EyeNormal = vec3(ModelviewMatrix * vec4(newNormal, 0.0));
}

So we have the line that says newVertex.xyz = mix(InVertex, InVertex1, TweenFactor) which blends the current keyframe with the next keyframe. mix() does a lerp operation.

newVertex.w = 1.0; because we must always set a vertex w to 1.0

And the other line of interest is this one : newNormal = mix(InNormal, InNormal1, TweenFactor) which compute the normal. mix() does a lerp operation.

The code above doesn't show how to load the keyframe from a file. That is left up to you. Chose whatever file format you want. Create your own if you have to.

The code above also doesn't show the preparation of the VBOs. This is easy enough.