Here's a short explanation of vertex weighting/skinning I wrote on the OpenGL developer forum a while back (in the context of "why is the OpenGL vertex weighting extension useless?"):

Basically, you have one local transform matrix per bone. Each vert can then reference some number of bones with a weight per reference. To calculate the "actual" vert position, you transform the vert separately by each affecting bones' matrix, and then blend (interpolate) the separate results based on the weights.

The simple two-weights-per-bone case looks something like this:


struct Vert {
  Pos p;  
  int bone0; float weight0;
  int bone1; float weight1;
};

void SkinVerts( Matrix const * bones, Vert const * inVerts, Pos * outVerts, int count ) {
  for( int ix = 0; ix < count; ++ix ) {
    Pos p0 = bones[ inVerts[ ix ].bone0 ] * inVerts[ ix ].p;
    Pos p1 = bones[ inVerts[ ix ].bone1 ] * inVerts[ ix ].p;
    outVerts[ ix ] = p0 * inVerts[ ix ].weight0 + p1 * inVerts[ ix ].weight1;
  }
}

In the typical case, weight0 + weight1 will always be 1.0, and thus weight1 can be omitted and re-derived from weight0. Also, in very modern games, you'll see more than two bones influencing each vertex.

Here comes the problem with the "vertex weighting" extension: because there can only be two bones TOTAL in all the triangles you draw in a single call, you have to slice your model into a hundred little itsy bitsy pieces, depending on which bones they're weigthed to.

Even worse: because all verts in the same triangle needs to be weighted to the same two bones (only), the "seam" verts between different bone influences need to only be weighted to a single bone, so they can be used in both segments. This looks very bad.