Skip to main content
added 669 characters in body
Source Link
Naros
  • 2k
  • 10
  • 13

What you should ask yourself is what is the actual difference between the two types?

An animated entity typically has both some form of a skeleton of bones which are animated through various animation tracks which a non-animated Sprite likely does not.

That would mean for animated entities, their skeletal pose needs to be properly set based on the current frame's time advancement, physics, and perhaps other external input factors. Perhaps there are multiple animations at play which need to be blended. All of these tasks are performed by a separate AnimationSystem and generates a final skeletal pose for rendering.

The render system then takes your mesh plus the pose and draws the final result. How the render system acquires the skeletal pose can occur multiple ways, through the animation component or even by asking for it to the animation system.

As to the render loop, my caution is to avoid if-else blocks inside a for-loop if you can. That means, try to decouple the data storage about renderable entities by somehow storing a separate list of those which are animated. This way you can easily deduce those which you need poses for and set that information.

void Draw(float deltaTime) {
  // for each animated entity, acquire the pose data and store it on the
  // renderable element.
  for( int i = 0; i < animatedEntities.size(); ++i ) {
    uint64_t entityId = animatedEntities[i];
    applyAnimatedPose( entityId, animationSystem.getSkeletalPose( entityId ) );
  }

  // draw all renderables in the batch
  std::vector<uint64_t>& renderables = getBatchRenderables();
  for ( int i = 0; i < renderables.size(); ++i ) {
    uint64_t entityId = renderables[i];
    draw( entityId );
  }
}

The psuedo code illustrates that its best to small incremental updates to data on very explicit subsets. This allows for better cache locality, thus improving performance. But more importantly, when operating on like subsets of data, this has the added benefit to avoid branching (if-else statements) inside tight loops, thus adding yet another performance benefit.

At the end, I show to iterate over all applicable renderables. That may mean to exclude those culled by the frustum, those which aren't visible or applicable to the scene's environment, etc. The returned list is properly sequenced so that the call to draw simply adds a specific draw command to the driver's batch list.

What you should ask yourself is what is the actual difference between the two types?

An animated entity typically has both some form of a skeleton of bones which are animated through various animation tracks which a non-animated Sprite likely does not.

That would mean for animated entities, their skeletal pose needs to be properly set based on the current frame's time advancement, physics, and perhaps other external input factors. Perhaps there are multiple animations at play which need to be blended. All of these tasks are performed by a separate AnimationSystem and generates a final skeletal pose for rendering.

The render system then takes your mesh plus the pose and draws the final result. How the render system acquires the skeletal pose can occur multiple ways, through the animation component or even by asking for it to the animation system.

As to the render loop, my caution is to avoid if-else blocks inside a for-loop if you can. That means, try to decouple the data storage about renderable entities by somehow storing a separate list of those which are animated. This way you can easily deduce those which you need poses for and set that information.

void Draw(float deltaTime) {
  // for each animated entity, acquire the pose data and store it on the
  // renderable element.
  for( int i = 0; i < animatedEntities.size(); ++i ) {
    uint64_t entityId = animatedEntities[i];
    applyAnimatedPose( entityId, animationSystem.getSkeletalPose( entityId ) );
  }

  // draw all renderables in the batch
  std::vector<uint64_t>& renderables = getBatchRenderables();
  for ( int i = 0; i < renderables.size(); ++i ) {
    uint64_t entityId = renderables[i];
    draw( entityId );
  }
}

The psuedo code illustrates that its best to small incremental updates to data on very explicit subsets. This allows for better cache locality, thus improving performance. But more importantly, when operating on like subsets of data, this has the added benefit to avoid branching (if-else statements) inside tight loops, thus adding yet another performance benefit.

What you should ask yourself is what is the actual difference between the two types?

An animated entity typically has both some form of a skeleton of bones which are animated through various animation tracks which a non-animated Sprite likely does not.

That would mean for animated entities, their skeletal pose needs to be properly set based on the current frame's time advancement, physics, and perhaps other external input factors. Perhaps there are multiple animations at play which need to be blended. All of these tasks are performed by a separate AnimationSystem and generates a final skeletal pose for rendering.

The render system then takes your mesh plus the pose and draws the final result. How the render system acquires the skeletal pose can occur multiple ways, through the animation component or even by asking for it to the animation system.

As to the render loop, my caution is to avoid if-else blocks inside a for-loop if you can. That means, try to decouple the data storage about renderable entities by somehow storing a separate list of those which are animated. This way you can easily deduce those which you need poses for and set that information.

void Draw(float deltaTime) {
  // for each animated entity, acquire the pose data and store it on the
  // renderable element.
  for( int i = 0; i < animatedEntities.size(); ++i ) {
    uint64_t entityId = animatedEntities[i];
    applyAnimatedPose( entityId, animationSystem.getSkeletalPose( entityId ) );
  }

  // draw all renderables in the batch
  std::vector<uint64_t>& renderables = getBatchRenderables();
  for ( int i = 0; i < renderables.size(); ++i ) {
    uint64_t entityId = renderables[i];
    draw( entityId );
  }
}

The psuedo code illustrates that its best to small incremental updates to data on very explicit subsets. This allows for better cache locality, thus improving performance. But more importantly, when operating on like subsets of data, this has the added benefit to avoid branching (if-else statements) inside tight loops, thus adding yet another performance benefit.

At the end, I show to iterate over all applicable renderables. That may mean to exclude those culled by the frustum, those which aren't visible or applicable to the scene's environment, etc. The returned list is properly sequenced so that the call to draw simply adds a specific draw command to the driver's batch list.

Source Link
Naros
  • 2k
  • 10
  • 13

What you should ask yourself is what is the actual difference between the two types?

An animated entity typically has both some form of a skeleton of bones which are animated through various animation tracks which a non-animated Sprite likely does not.

That would mean for animated entities, their skeletal pose needs to be properly set based on the current frame's time advancement, physics, and perhaps other external input factors. Perhaps there are multiple animations at play which need to be blended. All of these tasks are performed by a separate AnimationSystem and generates a final skeletal pose for rendering.

The render system then takes your mesh plus the pose and draws the final result. How the render system acquires the skeletal pose can occur multiple ways, through the animation component or even by asking for it to the animation system.

As to the render loop, my caution is to avoid if-else blocks inside a for-loop if you can. That means, try to decouple the data storage about renderable entities by somehow storing a separate list of those which are animated. This way you can easily deduce those which you need poses for and set that information.

void Draw(float deltaTime) {
  // for each animated entity, acquire the pose data and store it on the
  // renderable element.
  for( int i = 0; i < animatedEntities.size(); ++i ) {
    uint64_t entityId = animatedEntities[i];
    applyAnimatedPose( entityId, animationSystem.getSkeletalPose( entityId ) );
  }

  // draw all renderables in the batch
  std::vector<uint64_t>& renderables = getBatchRenderables();
  for ( int i = 0; i < renderables.size(); ++i ) {
    uint64_t entityId = renderables[i];
    draw( entityId );
  }
}

The psuedo code illustrates that its best to small incremental updates to data on very explicit subsets. This allows for better cache locality, thus improving performance. But more importantly, when operating on like subsets of data, this has the added benefit to avoid branching (if-else statements) inside tight loops, thus adding yet another performance benefit.