# Hardware skinning how-to

atm i’m computing animation in software, but i’d like to do it in a vertex shader. a model has an array of joint matrices and every vertex has an id which refers to a position in that array.

i want to upload the matrices as an array of uniform mat4 to the vertex shader and pass the joint id as a vertex attribute:

``````attribute int a_JointId;
uniform mat4 u_JointMatrices[32];
``````

``````'attribute' : cannot be bool or int
``````

mpfh. so what can i do? thanks!

you have to use floats and convert them into ints in the shader (not sure whether it is still true on G80 GPUs).

usually you use 4 bones/vertex for skinning so you send attributes like this:

attribute vec4 weight;
attribute vec4 indice;

where indice stores ids of your bones (matrices) and the weight indicates, how much is the vertex affected by the given matrix

``````attribute float a_JointId;
uniform mat4 u_JointMatrices[32];

int Id = (int)a_JointId;
vec4 Vertex = Transform( gl_Vertex, u_JointMatrices[Id] );
``````

so this should work? well, i’ll give it a try. thank you!

Here is one of my boneshader:

``````attribute vec4 Bones; //bone depencys + index
attribute vec3 Tangent;
uniform vec4 pose[32];

varying vec3 T,B,N;

void main(void){
vec4 temp1 = vec4(0.0, 0.0, 0.0, 0.0);
vec4 temp2 = vec4(0.0, 0.0, 0.0, 0.0);
for (int i = 0; i<4; i++){
temp1 += fract(Bones[i]) * pose[int(Bones[i]) * 2];
temp2 += fract(Bones[i]) * pose[int(Bones[i]) * 2 + 1];
}
//Matrix decompression
mat3 mat;
mat[0] = temp1.xyz;
mat[1] = vec3(-temp1.y, temp1.w, temp2.w);
mat[2] = cross (mat[0].xyz,mat[1].xyz);

gl_Position = gl_ModelViewProjectionMatrix * vec4(mat * gl_Vertex.xyz + temp2.xyz,1.0);

N = normalize( gl_NormalMatrix * (mat * gl_Normal));
T = normalize( gl_NormalMatrix * (mat * Tangent));
B = cross (T,N);

gl_TexCoord[0] = gl_MultiTexCoord0;
gl_TexCoord[1] = gl_MultiTexCoord1;
}
``````

The (transposed) 3x4 matrices are packed into 2 vec4 vectors. The decompression cost the instructions that are saved at mixing. The bone depencys are packet too. The fractional part is the weight and intger part is used as index. (the weight shouldn’t be bigger than 0.999)

great, thanks!
this is what i have so far. the code to transform the vertices is simply copy pasted from my software solution. some vertices are transformed correctly, the others are not the model has less than 32 joints.

``````varying vec2 v_Coordinates;

attribute float a_JointId;
attribute vec2 a_Coordinates;

uniform mat4 u_ViewMatrix;
uniform mat4 u_ModelMatrix;
uniform mat4 u_ProjectionMatrix;
uniform mat4 u_JointMatrices[32];

vec3 Transform( vec3 vector, mat4 matrix )
{
float _x, _y, _z;

_x = vector.x*matrix[0][0]+vector.y*matrix[1][0]+vector.z*matrix[2][0]+matrix[3][0];
_y = vector.x*matrix[0][1]+vector.y*matrix[1][1]+vector.z*matrix[2][1]+matrix[3][1];
_z = vector.x*matrix[0][2]+vector.y*matrix[1][2]+vector.z*matrix[2][2]+matrix[3][2];

return vec3( _x, _y, _z );
}

void main()
{
vec3 Vertex = gl_Vertex.xyz;

if( int(a_JointId) < 32 )
{
Vertex = Transform( gl_Vertex.xyz, u_JointMatrices[int(a_JointId)] );
}

// pass on texture coordinates
v_Coordinates = a_Coordinates;

// transform incoming vertex by view & model matrix
vec4 FinalVertex = u_ViewMatrix*u_ModelMatrix*vec4( Vertex, 1.0 );

// project to screen
gl_Position = u_ProjectionMatrix*FinalVertex;
}
``````

If you using soft skinning (with max 4 influences per vertex) take look this discussion:

http://www.opengl.org/discussion_boards/ubb/ultimatebb.php?ubb=get_topic;f=11;t=000459;p=1#000005

i import my animatins from ms3d, which does not support weights. so there is only one joint matrix influencing each vertex. exactly the same function is working nicely in software, so i do not understand why it does not in a vertex shader. what confuses me the most is that some vertices are transformed correctly, while some are not…

It’s possible that you’re getting a matrix transposed - I notice that your code assumes post-multiplied matrices (v * M) while OpenGL conventionally uses pre-multiplied matrices (M x v).

If you want to get good performance out of the shader (once you’ve made it work), you should let GLSL handle the matrix-vector multiplication instead of breaking it up into scalars; that will allow it to use vector instructions.

everything is working fine now one question remains, though: i have to reupload all matrices everytime i switch shaders. that sounds like a lot of overhead. is there a way to permanantly store the matrices on the gpu instead? the bindable uniform extension sounds interesting, but i guess it’s gf8+ only, right? or could i use the new render to texture feature instead?

thanks!

The Uniformvars are stored “permanent” but a changed pose needs new transformation matrices.

Render2Texture won’t help, and new GF8 extensions could help in future, but they aren’t necessary for simple boneanimations.

ok, next issue
i want to compress my bone matrices. according to this document, i can compress them from 16 to 8 floats: http://wiki.delphigl.com/index.php/Boneanimation_per_Vertexshader (german)

to see if it works i compress my input matrices, decompress them again and use these new matrices instead of the originals:

``````...

uniform mat4 u_FinalMatrices[MAX];

void main()
{
vec4 Vertex = gl_Vertex;

int Id = int( a_JointId );

// compress input matrix

vec4 temp1;
temp1.x = u_FinalMatrices[Id][0][0];
temp1.y = u_FinalMatrices[Id][0][1];
temp1.z = u_FinalMatrices[Id][0][2];
temp1.w = u_FinalMatrices[Id][1][1];

vec4 temp2;
temp2.x = u_FinalMatrices[Id][3][0];
temp2.y = u_FinalMatrices[Id][3][1];
temp2.z = u_FinalMatrices[Id][3][2];
temp2.w = u_FinalMatrices[Id][1][2];

// decompress matrix again

mat3 mat;
mat[0] = temp1.xyz;
mat[1] = vec3( -temp1.y, temp1.w, temp2.w );
mat[2] = cross( mat[0].xyz, mat[1].xyz );

//Vertex = u_FinalMatrices[Id]*Vertex; <- using the uncompressed matrix works fine

Vertex = vec4( mat*Vertex.xyz+temp2.xyz, 1.0 ); // <- using the decompressed matrix doesn't

...
``````

however, the mesh is deformed along the x-axis. can you see what’s wrong?

This topic was automatically closed 183 days after the last reply. New replies are no longer allowed.