Bone animation and skinning

Discuss the development of new homebrew software, tools and libraries.

Moderators: cheriff, TyRaNiD

Post Reply
Criptych
Posts: 64
Joined: Sat Sep 12, 2009 5:18 am

Bone animation and skinning

Post by Criptych »

I'm writing a 3D model viewer for a proprietary PC game format (in hopes of porting the game), and have reverse-engineered enough of the structure to display the models statically. I haven't done a lot of 3D programming - not since the good old "wireframe cube in QBASIC" days, anyway - but after a lot of reading I've gotten this far. Now I need some help figuring out how to use the GU for skinning, and especially how to generate bone matrices from a skeleton. Even looking at the gu/skinning and gu/morphskin samples didn't really help me understand what the code was doing and why. Can anyone point me to a good tutorial on this subject, or have some experience they're willing to share?
a_noob
Posts: 97
Joined: Sun Sep 17, 2006 8:33 am
Location: _start: jr 0xDEADBEEF

Post by a_noob »

The GU is limited to 8 bones, and actually makes skinning slower. Since in a lot of 3D game cases it is not the game logic/cpu slowing it down, but the gpu, so if you also tell it to skin the models, you are just asking for a slowdown. The psp's vfpu should provide more than enough of a speed boost to skin a model quite fast. Skinning a model for the psp differs in no way from skinning a model for openGL, just remember you have tightly packed 16bit aligned interleaved arrays ;) And you should be good to go. Now if you don't know how to skin, well I could sit here and explain, or you could google it, as I am sure there are hundreds of tutorials written for this.

The basic idea is each bone has a matrix and each vertex has a vector of its position relative to the bone matrix, each vertex also then has a weight, which is the percent effected by this bone matrix, now each vertex may be affected/effected by several matrices, each effector has a different weight, and all the weights should add to 1. Hopefully thats a good enough explanation to get you started.

Code: Select all

.øOº'ºOø.
'ºOo.oOº'
Criptych
Posts: 64
Joined: Sat Sep 12, 2009 5:18 am

Post by Criptych »

a_noob wrote:The GU is limited to 8 bones . . . The psp's vfpu should provide more than enough of a speed boost to skin a model quite fast.
Is that eight bones at a time? That is, can I set up eight bones, call sceGuDrawArray, then set up eight more and call it again, etc.? I'm only curious, I'll probably use the VFPU anyway, since I know I need a lot more than eight bones (some of the models I'm using have almost 200, I believe).
a_noob wrote:Skinning a model for the psp differs in no way from skinning a model for openGL, just remember you have tightly packed 16bit aligned interleaved arrays ;) And you should be good to go. Now if you don't know how to skin, well I could sit here and explain, or you could google it, as I am sure there are hundreds of tutorials written for this.

The basic idea is each bone has a matrix and each vertex has a vector of its position relative to the bone matrix, each vertex also then has a weight, which is the percent effected by this bone matrix, now each vertex may be affected/effected by several matrices, each effector has a different weight, and all the weights should add to 1. Hopefully thats a good enough explanation to get you started.
Thanks, that actually made a lot more sense than anything else I was able to find. :) Most of them spent more time discussing how the math worked than how to actually use the technique. :(
One quick question: the model files contain the mesh data and (what I assume are) the matrices defining the bones' "relative" position to the mesh, but I can find nothing on how the bones are connected to each other. Do I need to know the skeleton hierarchy just to transform the vertices, or is that taken care of by how the matrices are set up and "connected" to the vertices?
a_noob
Posts: 97
Joined: Sun Sep 17, 2006 8:33 am
Location: _start: jr 0xDEADBEEF

Post by a_noob »

Yes it would be limited to 8 bones per call to drawArrays. Also, you should just need to know all the effector bones matrices, the bone hierarchy should take care of aligning the bones with one and other, and keep them linked.

Basically you end up with 2 systems, a skeleton system and a mesh system, one controls the bones, and their movement, the other just aligns the mesh to the bone.

I suggest to get skinning working purely on the cpu first, then moving the math to the VFPU if you need more speed, also this will make solving code errors easier, because your math will be in C/C++ and not ASM. Just a tad easier to track.

Code: Select all

.øOº'ºOø.
'ºOo.oOº'
Criptych
Posts: 64
Joined: Sat Sep 12, 2009 5:18 am

Post by Criptych »

a_noob wrote:Also, you should just need to know all the effector bones matrices, the bone hierarchy should take care of aligning the bones with one and other, and keep them linked.

Basically you end up with 2 systems, a skeleton system and a mesh system, one controls the bones, and their movement, the other just aligns the mesh to the bone.
That's very good to hear. I'm still deciphering parts of the format, so I was a little worried I might need more data to get this working. From what I do have, I think all the animations are stored as transformations for each bone, so maybe I won't need to know the hierarchy at all.
a_noob wrote:I suggest to get skinning working purely on the cpu first, then moving the math to the VFPU if you need more speed, also this will make solving code errors easier, because your math will be in C/C++ and not ASM. Just a tad easier to track.
Most likely it's learning the VFPU instructions I'll have trouble with, not the math. :) Thanks for the tip, though.


By the way, this is the same project I was talking about in the FPS thread; I added a counter to find out how many triangles I'm using, and the smallest numbers are over 10,000 (total of several models at once, but the more detailed ones are a few thousand each). Yes, it could do better than 30fps, but it's not bad for totally unoptimized code and models. :P
Criptych
Posts: 64
Joined: Sat Sep 12, 2009 5:18 am

Post by Criptych »

Okay, the good news is that I got animation working, and it only slowed down to 12fps. The bad news is...
Image
I think I'm still missing something, the only reason the model is even recognizable is because I used an animation with very little movement from the base pose. (I tried it with a different one, and got an image that could frighten Cthulhu.) I'm using the algorithm from this page (under "Pseudocode Algorithm"). Any suggestions?

EDIT: Here's the base pose, for reference:
Image

Fixed, turns out I had the transform matrices transposed. Oops.
liberty
Posts: 33
Joined: Wed Sep 16, 2009 11:30 am

Post by liberty »

Criptych wrote:Okay, the good news is that I got animation working, and it only slowed down to 12fps. The bad news is...
Image
I think I'm still missing something, the only reason the model is even recognizable is because I used an animation with very little movement from the base pose. (I tried it with a different one, and got an image that could frighten Cthulhu.) I'm using the algorithm from this page (under "Pseudocode Algorithm"). Any suggestions?

EDIT: Here's the base pose, for reference:
Image

Fixed, turns out I had the transform matrices transposed. Oops.
Great work. Can you share the code?
Criptych
Posts: 64
Joined: Sat Sep 12, 2009 5:18 am

Post by Criptych »

liberty wrote:Great work. Can you share the code?
Certainly. It's not the prettiest, but if you can decipher it, you can have it. :) Unfortunately I couldn't get it to work consistently with lv.q so I went back to ulv.q for now, which might cause problems on a 1000.

Code: Select all

   plpSeqBoneFrame *f = &(seq->bones[frame]);

   ScePspFMatrix4 tfm[numBones];

   int i, j, k;

   ScePspFMatrix4 *a = &(tfm[0]), *b = &(f->bones[0]), *c = &(mdl->bones[0]);

   for &#40;i = 0; i < numBones; ++i&#41;
   &#123;
      __asm__ volatile&#40;
         "ulv.q   C000,  0 + %1\n"
         "ulv.q   C010, 16 + %1\n"
         "ulv.q   C020, 32 + %1\n"
         "ulv.q   C030, 48 + %1\n"
         "ulv.q   C100,  0 + %2\n"
         "ulv.q   C110, 16 + %2\n"
         "ulv.q   C120, 32 + %2\n"
         "ulv.q   C130, 48 + %2\n"
         "vmmul.q E200, M000, M100\n"
         "usv.q   C200,  0 + %0\n"
         "usv.q   C210, 16 + %0\n"
         "usv.q   C220, 32 + %0\n"
         "usv.q   C230, 48 + %0\n"
         &#58; "=m"&#40;*a++&#41;
         &#58; "m"&#40;*b++&#41;, "m"&#40;*c++&#41;&#41;;
   &#125;

   for &#40;i = 0; i < mdl->numObjects; ++i&#41;
   &#123;
      plpObject *obj = mdl->objects&#91;i&#93;;

      plpVertex *vert = &&#40;obj->verts&#91;0&#93;&#41;;
      pgeVertTNV *tvert = &&#40;obj->tverts&#91;0&#93;&#41;;

      for &#40;j = 0; j < obj->numVerts; ++j&#41;
      &#123;
         __asm__ volatile&#40;
            "vzero.t C200\n"           // C200 = new vertex
            "vzero.t C210\n"           // C210 = new normal
            "lv.s    S220,  0 + %0\n"    // C220 = original vertex
            "lv.s    S221,  4 + %0\n"
            "lv.s    S222,  8 + %0\n"
            "vone.s  S223\n"
            "lv.s    S230,  0 + %1\n"    // C230 = original normal
            "lv.s    S231,  4 + %1\n"
            "lv.s    S232,  8 + %1\n"
            "vzero.s S233\n"
            &#58; &#58; "m"&#40;vert->x&#41;, "m"&#40;vert->nx&#41;&#41;;

         for &#40;k = 0; k < 3; ++k&#41;
         &#123;
            if &#40;vert->bones&#91;k&#93; == 0xff&#41; break;

            __asm__ volatile&#40;
               "ulv.q   C000,  0 + %0\n"      // M000 = transform matrix
               "ulv.q   C010, 16 + %0\n"
               "ulv.q   C020, 32 + %0\n"
               "ulv.q   C030, 48 + %0\n"
               "lv.s    S110,  0 + %1\n"     // S110 = weight
               "vtfm4.q C100, M000, C220\n"  // C100 = &#91; transform matrix &#93; &#91; original vertex &#93;
               "vscl.t  C100, C100, S110\n"  // add weighted value to new vertex
               "vadd.t  C200, C200, C100\n"
               "vtfm4.q C100, M000, C230\n"  // C100 = &#91; transform matrix &#93; &#91; original normal &#93;
               "vscl.t  C100, C100, S110\n"  // add weighted value to new normal
               "vadd.t  C210, C210, C100\n"
               &#58; &#58; "m"&#40;tfm&#91;vert->bones&#91;k&#93;&#93;&#41;, "m"&#40;vert->weight&#91;k&#93;&#41;&#41;;
         &#125;

         __asm__ volatile&#40;
            "vdot.t     S000, C210, C210\n"  // normalize new normal
            "vrsq.s     S000, S000\n"
            "vscl.t     C210&#91;-1&#58;1,-1&#58;1,-1&#58;1&#93;, C210, S000\n"
            "sv.s       S200, 0 + %0\n"      // store new vertex
            "sv.s       S201, 4 + %0\n"
            "sv.s       S202, 8 + %0\n"
            "sv.s       S210, 0 + %1\n"      // store new normal
            "sv.s       S211, 4 + %1\n"
            "sv.s       S212, 8 + %1\n"
            &#58; "=m" &#40;tvert->x&#41;, "=m" &#40;tvert->nx&#41;&#41;;

         ++vert; ++tvert;
      &#125;
   &#125;
A "bone" in this case is a 4x4 matrix (ScePspFMatrix4). Each model has a set of bones and set of objects. Each object has a list of original vertices and a list of transformed vertices; the latter is used for rendering. If your models contain vertex data directly, you can remove the extra loop.
Post Reply