Animating 3D Polygon Meshes
CS 481 Lecture, Dr. Lawlor
The basic ideas are:
- We're going to transform each part of the mesh using a matrix.
Each matrix is called a 'bone', since there's usually one for each
semi-rigid part of the mesh. It's easier to give the animating
locations of a few dozen bones than all the thousands of vertices in
the mesh, and you can add 'inverse kinematics' to simplify the bone
animation process--drag a hand around, and the elbow and shoulder
positions can update automatically.
- We identify how much of each bone applies to each vertex by
painting vertex weights on, one for each bone. The part of the
mesh right next to the bone will have weight 1.0, and far away parts
will have weight 0.0.
It's actually really a simple idea. But implementing it isn't as easy.
This has changed dramatically in Blender 2.6, so the documentation is
really nearly useless. What finally worked for me was:
- Create a meshed polygon object (see last lecture).
- Create an "Armature" containing "Bones" for each part of the mesh you want to move.
Start by Add -> Armature -> Single Bone, and then enter edit mode
and hit shift-A to make new bones in that armature. Line up the bones with the major parts of the mesh, and add names for the bones.
- Select the mesh in Object mode, and add an Armature modifier to
the mesh. Set the "Object" to the armature you just
created. Bind to Vertex Groups, not Envelope. DON'T
HIT APPLY! This will bake the bone deformations into the
mesh--you want to leave the armature modifier active to be able to move
the bones around and have the mesh follow it.
- Enter Weight Paint mode, and paint the parts of the mesh you want
to move with each bone. This is the proportion of each bone to
use to transform each vertex, and so it ranges from zero (bone does not
affect vertex) to one (bone totally defines location of vertex).
Feather the edges, so the mesh deforms smoothly. Name the vertex groups
(paint colors) the same as each of your bones--Blender matches bones to
vertex groups using the names. (It would probably be better to
use the 'bone envelopes' to initially create the vertex weights, and
then tweak them as needed, but I haven't figured out how to do this
yet.)
- Finally, to move the parts of the meshed object, select the
armature, enter 'Pose Mode', and move the bone around. The mesh
should move with the bones! You can still go back and tweak the
bone weights.
You can export bone weights via Collada format (they come out as a big
"weights" array), and in theory you can then animate the bones and then
apply their transformations to the mesh in C++. This gets fairly
complex, however.
Vertex Buffer Object
Vertex Buffer Objects (VBOs) are a way to store geometry information on
the graphics card, just like Textures let you store raster information
on the graphics card. VBOs are described in the ARB_vertex_buffer_object extension.
A VBO describes a series of glVertex (and optionally glColor, glNormal,
and glTexCoord) calls. The parameters for the calls start in a
CPU array, and get copied into graphics card memory. Depending on the CPU load and OS, VBOs can be a 10x speedup, or no
faster than a bunch of bare glVertex calls--you've just got to try 'em to be
sure!
You create a Vertex Buffer Object with (guess what!) glGenBuffersARB. You then have to glBindBufferARB
the buffer, and then you can then copy data in with
glBufferDataARB. You describe what your data contains
using calls to glVertexPointer (and optionally glColorPointer, glNormalPointer, and glTexCoordPointer), which each take the same four parameters:
- size, the number of floats per vertex (usually 3; it's hardcoded to 3 for glNormalPointer!)
- type, the data type (usually GL_FLOAT, occasionally GL_SHORT to save space, or even GL_BYTE for colors)
- stride, the distance in bytes between each vertex's data. This is usually sizeof(yourVertexStruct).
- pointer, the distance in bytes from the start of the buffer
to the corresponding vertex data. This is zero for the first
element, 12 for the second vec3 (because you have to skip over the
first), 24 for the third vec3, and so on. This is passed as a
"void *", and back in the pre-vertexbuffer days could point directly to
CPU data, but now you pass the buffer data separately.
Here's how you'd create a vertex buffer object to store vertex locations and colors, then render it:
static GLuint vb=0;
if (vb==0) { /* set up the vertex buffer object */
glGenBuffersARB(1,&vb); /* make a buffer */
glBindBufferARB(GL_ARRAY_BUFFER_ARB,vb);
/* Copy our vtx array (on the CPU) into our new GPU buffer */
glBufferDataARB(GL_ARRAY_BUFFER_ARB,sizeof(myVertex)*vtx.size(),
&vtx[0],GL_STATIC_DRAW_ARB);
/* Tell OpenGL how our array is laid out */
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3,GL_FLOAT,sizeof(vtx[0]), (void *)0); /* myVertex.xyz is first thing in struct */
glEnableClientState(GL_COLOR_ARRAY);
glColorPointer (3,GL_FLOAT,sizeof(vtx[0]), (void *)12); /* myVertex.color starts 12 bytes after struct start */
glBindBufferARB(GL_ARRAY_BUFFER_ARB,0); /* back to plain OpenGL */
}
/* Draw all our (GPU) points.
This is way faster than looping over vtx and calling glVertex many times! */
glBindBufferARB(GL_ARRAY_BUFFER_ARB,vb);
glDrawArrays(GL_POINTS,0,vtx.size());
glBindBufferARB(GL_ARRAY_BUFFER_ARB,0);
You can also create an "element buffer" to store vertex indices.
For example, to make a triangle from vertices zero, seven, and
thirteen, you'd put {0,7,13} into an element buffer. Element
buffers allow many triangles to point to the same vertex, which saves
that vertex many trips through your vertex shader. You upload the
index data with glBufferDataARB (just like vertex buffer objects), and
then use glDrawElements to look up your indices into your (already bound) vertex array:
static GLuint eb=0;
if (eb==0) { /* set up the element buffer object */
glGenBuffersARB(1,&eb); /* make a buffer */
glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB,vb);
/* Copy our idx array (on the CPU) into our new GPU buffer */
glBufferDataARB(GL_ARRAY_BUFFER_ARB,sizeof(int)*idx.size(),
&idx[0],GL_STATIC_DRAW_ARB);
glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB,0); /* back to plain OpenGL */
}
glBindBufferARB(GL_ARRAY_BUFFER_ARB,vb); /* vertex data */
glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB,eb); /* index data */
glDrawElements(GL_TRIANGLES,idx.size(),GL_UNSIGNED_INT,0);
glBindBufferARB(GL_ARRAY_BUFFER_ARB,0);
glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB,0);
The advantage of vertex buffer objects is they're a little bit faster
than plain glBegin/glVertex calls. The disadvantage is all this
code!