2011-04-07

WebGL - TED DXML importer bones work; needs animations

I got the bones working using some prior work from xcore/mingl.
Unfortunately, it's difficult to show this without a logically consistent animation; However, if you play with the matrix values you can see it works perfectly.

Current Test:
http://www.gocaco.com/webgl/test1/Test.zip

Current Exporter:
http://www.gocaco.com/webgl/igtl_export_ted.py

You probably have wondered how skinning works; Skinning is a procedure where static mesh data is deformed by adjusting the position, rotation, and scale of a transformation matrix. Usually these transform matrices are 4x4 matrices, but 3x4 work just as well.
To achieve a less blocky look, you can apply two or more different matrices to the same vertex; which results in a blending of the motion, controlling the weight of each blend is an art in and of itself. It can take many hours of time to get weights that work for a particular task, elbows and other sharply bending joints being key offenders.

Here is the current unoptimized method I use for matrix palette skinning:



//This procedure works for any input matrix, and converts it for shader use.
var stupid = mat4.create(); //Replace stupid with your armature-space bone matrix.

//Normally, stupid has translate * scale and rotation applied, and scale is applied last.
//You can pass in any matrix you like, just be aware how your modeling program transforms.

//Create some space
sseB = new Float32Array(16);
sseM = new Float32Array(16);
resM = new Float32Array(16);

//Local armaturespace matrix transposed:
sseB[0] = stupid[0];
sseB[1] = stupid[4];
sseB[2] = stupid[8];
sseB[3] = stupid[1];
sseB[4] = stupid[5];
sseB[5] = stupid[9];
sseB[6] = stupid[2];
sseB[7] = stupid[6];
sseB[8] = stupid[10];

//Original armaturespace matrix transposed:
sseM[0] = BAOrigMatrix[0];
sseM[1] = BAOrigMatrix[3];
sseM[2] = BAOrigMatrix[6];
sseM[3] = BAOrigMatrix[1];
sseM[4] = BAOrigMatrix[4];
sseM[5] = BAOrigMatrix[7];
sseM[6] = BAOrigMatrix[2];
sseM[7] = BAOrigMatrix[5];
sseM[8] = BAOrigMatrix[8];

//Multiplication; Transpose result //012 345 678 => 036 147 258
resM[0] = sseB[0]*sseM[0] + sseB[1]*sseM[1] + sseB[2]*sseM[2];
resM[3] = sseB[0]*sseM[3] + sseB[1]*sseM[4] + sseB[2]*sseM[5];
resM[6] = sseB[0]*sseM[6] + sseB[1]*sseM[7] + sseB[2]*sseM[8];

resM[1] = sseB[3]*sseM[0] + sseB[4]*sseM[1] + sseB[5]*sseM[2];
resM[4] = sseB[3]*sseM[3] + sseB[4]*sseM[4] + sseB[5]*sseM[5];
resM[7] = sseB[3]*sseM[6] + sseB[4]*sseM[7] + sseB[5]*sseM[8];

resM[2] = sseB[6]*sseM[0] + sseB[7]*sseM[1] + sseB[8]*sseM[2];
resM[5] = sseB[6]*sseM[3] + sseB[7]*sseM[4] + sseB[8]*sseM[5];
resM[8] = sseB[6]*sseM[6] + sseB[7]*sseM[7] + sseB[8]*sseM[8];

//Load and transpose it, then multiply by inverse original position
resM[12] = stupid[12] - (resM[0]*BAOrigPosition[0] + resM[3]*BAOrigPosition[1] + resM[6]*BAOrigPosition[2]);
resM[13] = stupid[13] - (resM[1]*BAOrigPosition[0] + resM[4]*BAOrigPosition[1] + resM[7]*BAOrigPosition[2]);
resM[14] = stupid[14] - (resM[2]*BAOrigPosition[0] + resM[5]*BAOrigPosition[1] + resM[8]*BAOrigPosition[2]);

//Replace myPaletteIndex with the bone index (for the matrix palette) you want to upload to.
//Remember that armature bones are mapped to the matrix palette, which is often small (28 max bones).
var locpos = 3*myPaletteIndex;

//Get the location of your uniforms
var uploc0 = myshader.pMatrixPaletteUniforms[ locpos ];
var uploc1 = myshader.pMatrixPaletteUniforms[ locpos + 1 ];
var uploc2 = myshader.pMatrixPaletteUniforms[ locpos + 2 ];

//Upload your converted matrix vec4's:
gl.uniform4fv( uploc0, [resM[0], resM[3], resM[6], resM[12]] );
gl.uniform4fv( uploc1, [resM[1], resM[4], resM[7], resM[13]] );
gl.uniform4fv( uploc2, [resM[2], resM[5], resM[8], resM[14]] );


...in your shader, you can now:


//Input vertex position (model space/armature space initial undeformed)
inpos = vec4( vattribPosition.xyz, 1.0 );

if( vattribWeights.x > 0.0 ){
//Use index cumulation (because we cannot upload integers); ie:
//
// w0,w1,w2 => (w0 + 64*w1 + 64*64*w2); floats have perfect 23 bit accuracy for ints.
//
int idex0 = 3*int(mod( vattribWeightIndex.z, 64.0 ));
//Matrix (used?)
rescur.x = vattribWeights.x * dot( inpos, MatrixPalette[ idex0 ] );
rescur.y = vattribWeights.x * dot( inpos, MatrixPalette[ idex0 + 1 ] );
rescur.z = vattribWeights.x * dot( inpos, MatrixPalette[ idex0 + 2 ] );
poscur = rescur;

if( vattribWeights.y > 0.0 ){

int idex1 = 3*int(mod( vattribWeightIndex.z/64.0, 64.0 ));
rescur.x = vattribWeights.y * dot( inpos, MatrixPalette[idex1] );
rescur.y = vattribWeights.y * dot( inpos, MatrixPalette[idex1 + 1] );
rescur.z = vattribWeights.y * dot( inpos, MatrixPalette[idex1 + 2] );
poscur += rescur;

if( vattribWeights.z > 0.0 ){

int idex2 = 3*int(mod( vattribWeightIndex.z/(4096.0), 64.0 ));
rescur.x = vattribWeights.z * dot( inpos, MatrixPalette[idex2] );
rescur.y = vattribWeights.z * dot( inpos, MatrixPalette[idex2 + 1] );
rescur.z = vattribWeights.z * dot( inpos, MatrixPalette[idex2 + 2] );
poscur += rescur;

...ect
}
}
}

//Apply object transform to final position:
gl_Position = uPMatrix * uMVMatrix * vec4(poscur.xyz, 1.0);


Here's a picture of Lara, imported.

Here's the same Gigginox from before, with slight wobble applied via matrix palette. Note this is the exact same file, I am just changing the import into javascript. TED files contain a lot of information.


Animation data is next.

-Z

No comments: