3

I have *.DAE files for characters each has 45-70 bones, I want to have about 100 animated characters on the screen.

However when I have ~60 Characters the animations takes ~13ms of my update loop which is very costly, and leaves me almost no room for other tasks.

I am setting the animations "CAAnimationGroup" to the Mesh SCNNode when I want to swap animations I am removing the previous animations with fadeOut set to 0.2 and adding the new Animation with FadeIn set to 0.2 as well. -> Is it bad ? Should I just pause a previous animation and play a new one ? or is it even worse?

Is there better ways to animate rigged characters in SceneKit maybe using GPU or something ?

Please get me started to the right direction to decrease the animations overhead in my update loop.

Update After Contacting Apple via Bug radar I received this issue via E-Mail:

This issue is being worked on to be fixed in a future update, we will let you know as soon as we have a beta build you can test and verify this issue.

Thank you for your patience.

So lets wait and see how far Apple's Engineers will enhance it :).

Coldsteel48
  • 3,482
  • 4
  • 26
  • 43

1 Answers1

4

SceneKit does the skeletal animation on the GPU if your vertices have less than 4 influences. From the docs, reproduced below:


SceneKit performs skeletal animation on the GPU only if the componentsPerVector count in this geometry source is 4 or less. Larger vectors result in CPU-based animation and drastically reduced rendering performance.


I have used the following code to detect if the animation is done on the GPU:

- (void)checkGPUSkinningForInScene:(SCNScene*)character
                          forNodes:(NSArray*)skinnedNodes {
  for (NSString* nodeName in skinnedNodes) {
    SCNNode* skinnedNode =
        [character.rootNode childNodeWithName:nodeName recursively:YES];
    SCNSkinner* skinner = skinnedNode.skinner;
    NSLog(@"******** Skinner for node %@ is %@ with skeleton: %@",
          skinnedNode.name, skinner, skinner.skeleton);
    if (skinner) {
      SCNGeometrySource* boneIndices = skinner.boneIndices;
      SCNGeometrySource* boneWeights = skinner.boneWeights;
      NSInteger influences = boneWeights.componentsPerVector;
      if (influences <= 4) {
        NSLog(@" This node %@ with %lu influences is skinned on the GPU",
              skinnedNode.name, influences);
      } else {
        NSLog(@" This node %@ with %lu influences is skinned on the CPU",
              skinnedNode.name, influences);
      }
    }
  }
}

You pass the SCNScene and the names of nodes which have SCNSkinner attached to check if the animation is done on the GPU or the CPU.

However, there is one other hidden piece of information about animation on the GPU which is that if your skeleton has more than 60 bones, it won't be executed on the GPU. The trick to know that is to print the default vertex shader, by attaching an invalid shader modifier entry as explained in this post.

The vertex shader contains the following skinning related code:

#ifdef USE_SKINNING
uniform vec4 u_skinningJointMatrices[60];

....

    #ifdef USE_SKINNING
  {
    vec3 pos = vec3(0.);
    #ifdef USE_NORMAL
    vec3 nrm = vec3(0.);
    #endif
  #if defined(USE_TANGENT) || defined(USE_BITANGENT)
    vec3 tgt = vec3(0.);
    #endif
    for (int i = 0; i < MAX_BONE_INFLUENCES; ++i) {
#if MAX_BONE_INFLUENCES == 1
        float weight = 1.0;
#else
        float weight = a_skinningWeights[i];
#endif
      int idx = int(a_skinningJoints[i]) * 3;
      mat4 jointMatrix = mat4(u_skinningJointMatrices[idx], u_skinningJointMatrices[idx+1], u_skinningJointMatrices[idx+2], vec4(0., 0., 0., 1.));
            pos += (_geometry.position * jointMatrix).xyz * weight;
      #ifdef USE_NORMAL
            nrm += _geometry.normal * mat3(jointMatrix) * weight;
      #endif
      #if defined(USE_TANGENT) || defined(USE_BITANGENT)
            tgt += _geometry.tangent.xyz * mat3(jointMatrix) * weight;
      #endif
    }
    _geometry.position.xyz = pos;

which clearly implies that your skeleton should be restricted to 60 bones.

If all your characters have the same skeleton, then I would suggest just check if the animation is executed on CPU or GPU using the above tips. Otherwise you may have to fix your character skeleton to have less than 60 bones and not more than 4 influences per vertex.

3d-indiana-jones
  • 879
  • 7
  • 17
  • Well... it says that my character has 9 influences more than twice times 4 :-( So the only way is to reduce the influences? – Coldsteel48 Dec 26 '16 at 15:12
  • 1
    Unfortunately yes, you have to reduce the influences. But it is not a lot of work as most 3D modeling software has the option to limit the influences. For example, I faced a similar situation (I am using Blender) and wrote a python script to redistribute the weights: https://gist.github.com/dmsurti/b502074eae2976d5e7a0e0829520132b. So no weight repainting, only apply such an operator and reexport the animation data. Or the 3D artist you work with should be able to do it for you. – 3d-indiana-jones Dec 26 '16 at 15:48
  • My 3D artists uses 3ds max and he told that it is going to take a lot of time for him :-(. Thank you very much for your answer I will try to work ot around somehow. – Coldsteel48 Dec 26 '16 at 16:15
  • Is it possible to programmatically assign weights within SCNSkinner or is it a suicide way to kill the animations ? – Coldsteel48 Dec 26 '16 at 16:22
  • Yes, you could assign weights programmatically, and as long as you do it that before-hand and not when you add the animations, it is not a suicide. Otherwise, it is a nuclear disaster! But really, the best option for you is to write a 3DS max plugin that just does weight distribution, so your 3D artist does not have to do any weight painting again. Unfortunately, i have no experience with 3DS max. – 3d-indiana-jones Dec 26 '16 at 17:03
  • Also, one other thing to note is that your exported skeleton should have less than 60 bones and it is better to have only the deform bones; not any other control bones etc which are usually part of animation rigs. For example; in Blender if one is using Rigify animation rig, there is the bake-rigify add-on which ensures that only deform bones are exported. https://github.com/felixSchl/bake-rigify. – 3d-indiana-jones Dec 26 '16 at 17:07
  • I changed the skinner 36 bones and your function returns 4 influences - however I don't see any performance change :( – Coldsteel48 Dec 27 '16 at 11:15
  • Can you check the performance for just 1 character and 1 animation? Also changing the number of bones should not alter the influences unless you have modified the number of weights as well. – 3d-indiana-jones Dec 27 '16 at 12:16
  • I made comparison in OpenGL context - the old with 9 Influences takes more time to render than the new with 4 influences - however the Animation time takes ~ the same 0.3ms – Coldsteel48 Dec 27 '16 at 13:14
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/131599/discussion-between-dmsurti-and-coldsteel). – 3d-indiana-jones Dec 27 '16 at 13:35