Let me explain it as simple as possible. It means in the real code the things more complicated!
NOTES!!!
- I oversimplify most things so forgive me for that.
- I only use the “position” attribute in my explanation. We also have rotation and scale, but I’ll omit it.
- I remove some useless code like creating additional skeletons …
We have a character mesh. It contains vertices. Each vertex contains attributes like position, uv etc.
If we have only vPosition(position of vertex), BJS renders it “as is”.
Before render, BJS calculates final vertex position. Because we have “node.position” too, the “unreal code” looks like:
vertexFinalPosition = node.position * vPosition
We don’t need to do it manually, it works under the hood.
Now we want to animate the vertex. We can manually change every vertex’s position like vPosition = some new value But it’s boring… So, we’re starting to use the Skeleton.
The Skeleton is a simple bunch of bones(invisible vertices with positions) which BJS respects in calculation of vertexFinalPosition
Now we need to specify for every vertex which bone and how much we want to apply it(that is the reason of using matricesIndices and matricesWeights attributes for every vertex).
vertexFinalPosition = node.position * vPosition * bonePosition * boneWeight
As you can see, we can manipulate vertex’s position by changing bonePosition. That’s how Skeleton animation works.
The animation is the sequence of position changes(yeah, not only position
). and we apply it to bone! So the bone’s position changes via animation.
BUT! If we have a lot of skeletons and bones, we need to calculate every bone per every animation frame on CPU!
VAT allows us to move calculation from CPU to GPU. The animation texture is a sequence of bone’s position changes(not only position
) in the pixels form.
bonePositionFromVAT = .. get bone position from texture using boneIndex and time!
vertexFinalPosition = node.position * vPosition * bonePositionFromVAT * boneWeight
So we can run this code on the GPU. The only things we need to sync between CPU and GPU are node.position and animation time.
Now how does the code work?
As you can see from example above, we can animate the mesh using the skeleton.
Now we have the character with the skeleton(+ animation).
We want to animate the sword too. So we can apply the skeleton animation to the sword’s mesh.
But we have an issue. Because the sword is a separate object, we need to “fix” the position for every sword’s vertices because we want to see sword in the hand and not between foots. That’s why we have 73 - 75 lines in the code before merging the sword. We “fix” the sword position(and it automatically fixes all vertices positions). I think the best solution is applying such transform in Blender.
122 - 138 lines: re-using VAT and set matricesIndices and matricesWeights attributes for every sword’s vertex;
149 - 161 lines: we need to move sword’s object in the same place as the character’s object + add VAT for every instance;
UPDATE
Sorry. I forgot to explain the difference between “baked” vertex position VS item slot ways.
“baked” vertex position
That’s how character model works. You prepare you model in Blender, attach every vertex to bone and done! It works great for character model, helms, boots, glovers, armor. Because you can manually change the position of each armor for example. Like Dragon Armor should be a little bit higher than Leather Armor. You don’t want to have separate item slot for that or have a lot configurations code like dragonArmorFixYPosition = 0.5. So, you bake all fix fixes into mesh.
item slot
Item slot works great if you have more than one item slot for your item. Like Swords! You can attach a sword to LeftHandSlot or RightHandSlot. So you place your sword in Blender in right place with right rotation(it is a separate topic about content preparation…). But the final position will be calculated via code. (73 - 75 lines in my code). Like finalPosition = meshOriginal * boneOriginalPosition
For Swords, you can also use Baked position “as is” for LeftHand and apply sword.position.x *= -1 to sword for RightHand (flip it horizontally).
UPDATE 2
Use can disable multiMaterial for MergeMeshes (set last parameter to false) to have 3 draw calls instead of 8.
@sebavan @Evgeni_Popov please correct me if I’m totally wrong.