I'm writing a small game with WebGL to help myself understand some of the core concepts in 3D games programming (Without the use of an existing engine). The project is just something I've got going on in my spare time and if I felt the need, the WebGL version would be a prototype with a native program coming second.
I'm currently at the stage where I'm trying to animate character models, and have been struggling for days trying to figure out how it's supposed to be done. I've found it difficult to find any simple tutorials online that I could go by (Or to even translate from another programming language), so I've come to this site for some guidance. If the problem can be solved here, I'm hoping it'll help others with similar questions on the concept in the future too.
My current solution is this (For each bone in the model):
- Translate to the original position of the bone
- Rotate to the new requested position
- Transform by the (Now calculated) parent bone matrices
- Translate by the inverse of the original position matrix
Here's the relevant code that calculates the matrices for the model so far. I know the code isn't exactly efficient by creating lots of new matrices for each bone every frame, but I've written it this way so that I can get my head around it. Optimisation comes later. The only bone that is instructed to rotate is the one labelled "Bip01 L UpperArm", which is the characters right shoulder.
var temp = 0.0;
ModelInstance.prototype.Tick = function(step, name, hasArmature, bones) {
temp -= 0.0001*step;
if(temp>1.0)
temp -= 2.0;
else if(temp<-1.0)
temp += 2.0;
// Calculate world matrix for overall model
var rotation = quat.fromValues(this.rX, this.rY, this.rZ, 1.0);
quat.calculateW(rotation, rotation);
mat4.fromRotationTranslation(this.worldMatrix, rotation, [this.x, this.y, this.z]);
mat4.scale(this.worldMatrix, this.worldMatrix, [this.sX, this.sY, this.sZ]);
mat4.transpose(this.worldMatrix, this.worldMatrix);
// Calculate bone matrices
if(hasArmature) {
for(var i=0; i<bones.length && i<40; i++) {
// Rotate just the right shoulder for this example
var boneRotate = bones[i].name == "Bip01 L UpperArm" ? quat.fromValues(0.0, 0.0, temp, 1.0) : quat.fromValues(0.0, 0.0, 0.0, 1.0);
quat.calculateW(boneRotate, boneRotate);
// Original Position
// Translate to the position of the bone
var translationMatrix = mat4.create();
mat4.translate(translationMatrix, translationMatrix, [bones[i].pos[0], bones[i].pos[1], bones[i].pos[2]]);
// Rotation Matrix
// Amount to rotate the bone by
var rotationMatrix = mat4.create();
mat4.fromQuat(rotationMatrix, boneRotate);
// New Position
// Rotate the bone around the bones origin by the requested amount
var boneMatrix = mat4.create();
mat4.multiply(boneMatrix, translationMatrix, rotationMatrix);
// Apply parent transformations
if(bones[i].parent>=0) {
var parentID = bones[i].parent;
while(parentID>=0) {
mat4.multiply(boneMatrix, boneMatrix, this.boneMatrices[parentID]);
parentID = bones[parentID].parent;
}
}
// Remove original translation
var inverseMatrix = mat4.create();
mat4.invert(inverseMatrix, translationMatrix);
mat4.multiply(boneMatrix, boneMatrix, inverseMatrix);
// Save matrix
this.boneMatrices[i] = boneMatrix;
}
}
}
Some of the relevant bone data is shown below:
{
"name":"Bip01",
"parent":-1,
"pos":[0.00000000,2.48177004,0.03703270]
},
/*....*/
{
"name":"Bip01 Pelvis",
"parent":0,
"pos":[0.00000000,2.48177004,0.03141975]
},
{
"name":"Bip01 Spine",
"parent":2,
"pos":[0.00000000,2.73970008,0.03122885]
},
{
"name":"Bip01 Spine1",
"parent":3,
"pos":[0.00000000,2.97957993,0.03618195]
},
{
"name":"Bip01 Neck",
"parent":4,
"pos":[0.00000000,3.83638000,0.00980067]
}
/*....*/
{
"name":"Bip01 L Clavicle",
"parent":5,
"pos":[0.07849900,3.83639002,0.00993846]
},
{
"name":"Bip01 L UpperArm",
"parent":10,
"pos":[0.47621104,3.63395000,-0.04814709]
},
{
"name":"Bip01 L Forearm",
"parent":11,
"pos":[0.93108791,3.14579010,-0.13482325]
},
{
"name":"Bip01 L Hand",
"parent":12,
"pos":[1.32678998,2.72037005,-0.01493155]
}
/*....*/
And finally, the relevant part of the vertex shader is shown below:
mat4 skinMatrix;
skinMatrix = uBoneMatrices[int(aBoneIndex.x)] * aBoneWeight.x +
uBoneMatrices[int(aBoneIndex.y)] * aBoneWeight.y +
uBoneMatrices[int(aBoneIndex.z)] * aBoneWeight.z +
uBoneMatrices[int(aBoneIndex.w)] * aBoneWeight.w;
gl_Position = (((vec4(aPosition, 1.0) * skinMatrix) * uWMatrix) * uVMatrix) * uPMatrix;
vLightViewPosition = (((vec4(aPosition, 1.0) * skinMatrix) * uWMatrix) * uLVMatrix) * uLPMatrix;
Here's an image of my current thinking of how it should be solved and what the current result is based on the code above. As you can see, the hand stretches right around to the models feet (Bare in mind only the shoulder is actually being rotated), so I'm assuming there's an issue with the solution I've written.
Any help is appreciated :)
Utils used: glMatrix