SourceForge.net Logo
Home
Documentation
Tutorials
    Beginning
    Gears
    Noise
    Bumpmapping
    Point sprites
    Hello World
    Armature
    Geometryshader
    Toolscripting
    Keyframe animation
    Instancing
    Bezier Surface
    Deferred shading
    Depth peeling
    Particle System
Links
Support This Project
OpenGL
Armature

This tutorial describes how to use the new Armature object to support bone systems.


1. Export a model from Blender
For this step the Blenderexporter should be copied into the Blenders script
directory,also a rigged and skinned model is needed. Here are some links:

Cute
Bear
(the skinning is not good for quaternions)
Katorlegaz
Tux
(works without reskinning)

Load the model and select one or more mesh objects that should be exported. Select the
Lumina exporter from File->Export->Lumina and enter a filename with *.lum extension.

2. Load the exported model in Lumina
Open the exported File wit Lumina. A node with some objects should be shown in the tree.
If changes on the model are needed, reexport the model from blender and open it in
Lumina, the old tree contend and the new from the file will be merged in the tree. Stop
all script that access the old objects. Delete the old objects and drag the new on
theire place. Just remove unneeded nodes and save it as a new file.

3. Write a render script and vertex shader

Luminas Armature support two different types of bone system interfaces. One is based on
a array of 4x4 matrices that works with normal meshes. The 4x4 matrices supports
rotation, translation, scale and shear. (Scale and shear makes trouble, because they
deform the normal matrix)

First the script:

shader = gl.Shader(Vertexshader,Fragmentshader);

tan = shader.Loc("Tangent");
bones = shader.Loc("Bones");

function render(){
Armature.Matrices(shader,"Pose");

shader.Bind();
shader.Uniform("col",0,0,0,0);

Tux.UvCoords.Bind();
Tux.Tangent.Bind(tan);
Tux.Bones.Bind(bones);
Tux.Vertex.Bind();
Tux.Normal.Bind();

Tux.Index.Draw();
shader.Uniform("col",1,1,1,1); //draw white dots
Tux.Draw(gl.POINTS);
}


The vertex shader:

attribute vec4 Bones;
attribute vec3 Tangent;
uniform mat4 Pose[32];
varying vec3 T,B,N;

void main(void){
mat4 mat = 0.0;

for ( int i = 0; i < 4; i++){
mat += Pose[Bones[i]] * fract(Bones[i]);
}

gl_Position = gl_ModelViewProjectionMatrix * (mat *gl_Vertex);

N = gl_NormalMatrix * (mat3(mat[0].xyz,mat[1].xyz,mat[2].xyz) * gl_Normal);
//T = gl_NormalMatrix * (mat3(mat) * Tangent);
//B = cross (T,N);
gl_TexCoord[0] = gl_MultiTexCoord0;
}

That is simple to understand: Up to 4 matrices from Pose will be weighted. The fraction
value of Bones[i] the weight and the integer part the index. (On ATI cards could it be
that a int() cast is needed)

All positions, normals and tangents must be multiplied with this matrix to applie the
Bone deformations. Don't forged the brackets to avoid expensive matrix * matrix
multiplictions. The position needs to be multiplied with the full 4x4 matrix. Normal and
Tangent only with the upper left 3x3 part. (The mat3 cast could make problems on ATI
cards)

Update: It is recommend to use two different attributes for the Bone. The first should
be a 4 dimensinal unsigned byte vector for the bone ID (use short if more than 255 bones
are required) and the second should be 4 dimensional normalized unsigned byte vector for
the weight. In that case use this code:

attribute vec4 BoneWeight;
attribute vec4 BoneID;
attribute vec3 Tangent;
uniform mat4 Pose[32]; // up to 64 should be fine
varying vec3 T,B,N;

void main(void){
mat4 mat = 0.0;
for ( int i = 0; i < 4; i++){
mat += Pose[int(BoneID[i])] * BoneWeight[i];
}

gl_Position = gl_ModelViewProjectionMatrix * (mat *gl_Vertex);
N = gl_NormalMatrix * (mat3(mat[0].xyz,mat[1].xyz,mat[2].xyz) * gl_Normal);
//T = gl_NormalMatrix * (mat3(mat) * Tangent);
//B = cross (T,N);
gl_TexCoord[0] = gl_MultiTexCoord0;
}


A small fragment shader displays the normal vector and white dots for the vertices:

varying vec3 T,B,N;
uniform vec4 col;

void main(void){
gl_FragColor = vec4(normalize(N)/2.0 + 0.5,1.0) + col;
}


The second one is quaternion and joint based, that needs a special mesh, where the
vertex position is relative to joints encoded. (The Blender exporter creates that stream
with name "RelToJoint") The advantage are that the vertex interpolation is not linear
like the linear like the matrix variant, the position could be calculated on a circular
curve. So knees and elbows didn't get unnatural deformed on sharp angles. The LERP
(SLERP isn't needed in vertexshaders) interpolation is very fast because it needs only a
few MADDs and a normalization.
The disadvantages are that the handling of quaternions is more complex than matrices,
but there are well known optimized functions, for multiply two quaternions and rotate a
vector.

To use a quaternion based transformation replace the Matrices function in the script by
the joint and quaternion function and the "Vertex" by "RelToJoint":


shader = gl.Shader(Vertexshader,Fragmentshader);

tan = shader.Loc("Tangent");
bones = shader.Loc("Bones");

function render(){
Armature.Joints(shader,"Joints");
Armature.Quaternions(shader,"Quaternions");

shader.Bind();
shader.Uniform("col",0,0,0,0);

Tux.UvCoords.Bind();
Tux.Tangent.Bind(tan);
Tux.Bones.Bind(bones);
Tux.RelToJoint.Bind();
Tux.Normal.Bind();

Tux.Index.Draw();
shader.Uniform("col",1,1,1,1); //draw white dots
Tux.Draw(gl.POINTS);
}


The vertex shader is a little bit more complex, but faster than the matrix version:

attribute vec4 Bones;
attribute vec3 Tangent;

uniform vec3 Joints[32];
uniform vec4 Quaternions[32];
varying vec3 T,B,N;

vec3 qrot( vec4 q, vec3 v ){
return v + 2.0*cross(q.xyz, cross(q.xyz ,v) + q.w*v);
}

void main(void){
vec4 quaternion = vec4(0.0, 0.0, 0.0, 0.0);

for ( int i = 0; i < 4; i++){
quaternion += Quaternions[Bones[i]] * fract(Bones[i]);
}

normalize(quaternion);

vec4 vert = vec4(qrot(quaternion,gl_Vertex.xyz) + Joints[gl_Vertex.w] ,1.0);
gl_Position = gl_ModelViewProjectionMatrix * vert;

N = gl_NormalMatrix * qrot(quaternion, gl_Normal);
//T = gl_NormalMatrix * qrot(quaternion, Tangent);
//B = cross (T,N);

gl_TexCoord[0] = gl_MultiTexCoord0;
}

First up to 4 quaternions will be weighted like the matrices, but that is 4 times
faster, because for each quaternion is only a single MADD instruction needed. The
normalization is needed for a correct rotation.
The relative to the joint position Vertex will be rotated around that joint, by the
weighted quaternion and the joint position will be added to get transformed position in
post bone space. This vertex can be multiplied with the gl_ModelViewProjectionMatrix
like every other vertex position.
The normal and Tangent vectors must be rotated by the quaternion too. This is a little
bit inefficient, but in later tutorial I will demonstrate how it's possible to replace
the gl_NormalMatrix, gl_Normal, Tangent, Bitangend and the normalmaps by quaternions.

4. Animation

To animate the model, the first step is to rename all bones with illegal chars like
whitespaces and dots in their names.
Each Bone has some methods:

Rotate(x, y, z, w);

Rotate a bone with quaternion (x;y;z;w) around the joint. (x;y;z) are the Vector part
and w the Scalar part.

Rotation(x, y, z, w);

Set the rotation to quaternion (x;y;z;w). Example: Rotation(0, 0, 0, 1) rotates a bone
into initial position.

EulerRotate(a, b, c);

This function rotates a bone by Euler angles in Radians.

EulerRotation(a, b, c);

This function set the rotation of a bone to Euler angles in Radians.

To reset all Bones to initial position call the armatures Reset() function.


With this functions it should be possible to write a script that could interpret simple
mocap data files


The right leg is rotated by a (-SQRT(2),0,0,SQRT(2)) quaternion
EDIT