Level of detail

Motivation

coherence can support large game worlds with many objects. Since the amount of data that can be transmitted over the network is limited, it's very important to only send the most important things.

You already know a very efficient tool for enabling this – the LiveQuery. It ensures that a client is only sent data when an object in its vicinity has been updated.

Often though, there is a possibility for an even more nuanced and optimized approach. It is based on the fact that we might not need to send as much data for an entity that is far away, compared to a close one. A similar technique is often used in 3D-programming to show a simpler model when something is far away, and a more detailed when close-up.

This idea works really well for networking too. For example, when another player is close to you it's important to know exactly what animation it is playing, what it's carrying around, etc. When the same player is far off in the horizon, it might suffice to only know it's position and orientation, since nothing else will be discernible anyways.

To use this technique we must learn about something called archetypes.

Archetypes

An archetype is a component that can be added to any prefab with the CoherenceSync component. It contains a list of the various levels of detail (LODs) that this particular prefab can have.

There must always exist a LOD 0, this is the default level and it always has all components enabled (it can have per-field overrides though, see below.)

There can be any number of subsequent LODs (e.g. LOD 1, LOD 2, etc.) and each one must have a distance threshold higher than the previous one. The coherence SDK will try to use the LOD with the highest number, but that is still within the distance threshold.

Example

An object has three LODs, like this:

  • LOD 0 (threshold 0)

  • LOD 1 (threshold 10)

  • LOD 2 (threshold 20)

If this object is 15 units away, it will use LOD 1.

Confusingly, the highest numbered LOD is usually called the lowest one, since it has the least detail.

One each LOD, there are two options for optimizing data being transferred:

  1. Components can be turned off, meaning you won't receive any updates from it.

  2. It's fields can be configured to use fewer bits, usually leading to less fine-grained information. The idea is that this won't be noticeable at the distance of the LOD.

Scale, bits and range

coherence allows us to define the scale of numeric fields and how many bits we want to allocate to them.

Here are some terms we will be using:

  • Scale. The minimum/maximum value of the field before it overflows. A scale of 2400 means the number can run from -2400 to 2400.

  • Bits. The number of bits (octets) user for the field. When used for vectors, the number defined the number of bits used for each component (x, y and z). A vector3 set to 24 bits will consume 3 * 24 = 72 bits.

  • Range. For integer values, we define a minimum and maximum possible value (e.g. Health can lie between 0 and 100).

More bits means more precision. Increasing the scale while leaving the bit count the same will lower the precision of the field.

The maximum number of bits used for any field/component is currently 32.

coherence allows us to define these values for specific components and fields. Furthermore, we can define levels of detail so that precision and therefore bandwidth consumption falls with the distance of the object to the point of observation.

Levels of detail are calculated from the distance between the entity and the center of the LiveQuery.

Defining levels of detail

On each LOD you can configure the individual fields of any component to use less data. You can only decrease the fidelity, so a field can't use more data on a lower (more far away) LOD. The Archetype editor interface will help you to follow these rules.

In order to define levels of detail, we have to add a CoherenceArchetype component to a prefab with CoherenceSync defined field bindings.

Clicking on the Edit LOD button opens the Archetype Editor (and adds a CoherenceArchetype component if there wasn't one already).

We can override the base component settings even without defining further levels of detail.

Clicking on Add new Level Of Detail will add a new LOD. We can now define the distance at which the LOD starts. This is the minimum distance between the entity and the center of the LiveQuery at which the new level of detail becomes active (i.e. the replicator will start sending data as defined here at this distance).

You can also disable components at later LOD levels if they are not needed. In the example above, you can see that the entire Animator component is disabled beyond the distance of 100 units. At 100 units (a.k.a. meters), we usually do not see animation details, so it does not make sense to replicate this data.

The Data Cost Overview shows us that this takes the original 416 bits down to just 81 bits at LOD level 2.

Field overrides per type

The primitive types that coherence supports can be configured in different ways:

Float, Vector2 & Vector3

These three types can all be configured in the same way:

  1. By setting the scale, which affects the maximum and minimum value that the data type can take on. For example, a scale of 100 means that a float ranges from -100 to 100.

  2. By setting the precision, which defines the greatest deviation allowed for the data type. For example, a precision of 0.5 means that a float of value 10.0 can be transmitted as anything from 9.5 to 10.5 over the network.

When using these scale setting for vectors, it affects each axis of the vector separately. Imagine shrinking its bounding box, rather than a sphere.

Based on the scale and the desired precision, a bit count will be calculated. The default precision and scale (which happens to be 2400) gives a bit count of 24. This means that for a Vector3 a total of 72 bits will be used, 24 x 3.

Integers

Integers can be configured to any span (that fits within a 32-bit int) by setting its minimum and maximum value.

For example, the member variable age in a game about ancient trolls might use a minimum of 100 and a maximum of 2000. Based on the size of the range (1900 in this case) a bit-count will be calculated for you.

For integers it usually make sense to not decrease the range on lower LODs since it will overflow (and wrap-around) any member on an entity that switches to a lower LOD. Instead, use this setting on LOD 0 to save data for the whole Archetype.

Quaternions

Right now quaternions (used for rotations) do not support field overrides, but this will be fixed in the near future.

Other types

All other types (strings, booleans, entity references) have no settings that can be overridden, so your only option for optimizing those are to turn them off completely at lower LODs.

LODs in the schema

When we bake, information from the CoherenceArchetype component gets written into our schema. Below, you can see the setup presented earlier reflected in the resulting schema file.

archetype FemZombie
  lod 0
    WorldPosition 
      value 
    WorldOrientation 
      value 
    FemZombie_UnityEngine_Animator 
      InputHorizontal [bits "11", scale "1"]
      InputVertical [bits "11", scale "1"]
      InputMagnitude [bits "11", scale "1"]
      TurnOnSpotDirection [bits "11", scale "1"]
      ActionState [bits "15", range-min "0", range-max "10"]
  lod 1 [distance "50"]
    WorldPosition 
      value [bits "18", scale "2400"]
    WorldOrientation 
      value 
    FemZombie_UnityEngine_Animator 
      InputHorizontal [bits "11", scale "1"]
      InputVertical [bits "11", scale "1"]
      InputMagnitude [bits "11", scale "1"]
      TurnOnSpotDirection [bits "11", scale "1"]
      ActionState [bits "15", range-min "0", range-max "10"]
      IdleRandom [bits "15", range-min "-9999", range-max "-9999"]
      RandomAttack [bits "15", range-min "-9999", range-max "-9999"]
      AttackID [bits "15", range-min "-9999", range-max "-9999"]
      DefenseID [bits "15", range-min "-9999", range-max "-9999"]
      RecoilID [bits "15", range-min "-9999", range-max "-9999"]
      ReactionID [bits "15", range-min "-9999", range-max "-9999"]
      HitDirection [bits "15", range-min "-9999", range-max "-9999"]
  lod 2 [distance "100"]
    WorldPosition 
      value [bits "10", scale "2400"]
    WorldOrientation 
      value 

Caveats

The most unintuitive thing about archetypes and LOD-ing is that it doesn't affect the sending of data. This means that a "fat" object with tons of fields will still tax the network and the replication server if it is constantly updated, even if it uses a very optimized Archetype.

Also, it's important to realize that the exact LOD used on an entity varies for each other client, depending on the position of their query (or the closest one, if several are used.)

Last updated