Interpolation

Overview

For an object to appear to move smoothly on the screen, it must be rendered at a high rate, usually 60 frames per second or more. However, depending on the settings in your project, and the conditions of your internet connection, data may not always arrive at a smooth 60 frames per second across the network. This is completely okay, but in order to make state changes appear smooth on the Client, we use interpolation.

Interpolation is a type of estimation, a method of constructing new data points within the range of a discrete set of known data points.

When you select a variable to replicate in the Configure window, it is automatically assigned a default interpolation setting. The default settings are usually good to get started, but you can modify or create your own interpolation settings that better fit your specific needs.

Configuring interpolation settings

In the Configure window, each binding displays its interpolation settings next to it.

Built-in interpolation settings for position and rotation are provided out-of-the-box, but you are free to create your own and use them instead.

You can also create an interpolation settings asset: Assets > Create > coherence > Interpolation Settings

Interpolation types

Linear interpolation blends values by moving along straight lines from sample to sample. This makes the networked object move in a zig-zag pattern, but this is usually not noticeable when sampled at a sufficient rate and with some additional smoothing applied (see section Other settings > Smoothing below).

Spline interpolation blends between samples using the Catmull-Rom spline method which gives a smoother movement than linear interpolation without any sharp corners, at the cost of increased latency (see: Latency below). Spline interpolation requires at least 4 samples to produce good results.

If interpolation type is set to None, the value will simply snap to the most recent sample without any blending. This is recommended for binding types that have no obvious blending methods, e.g., string, byte array and object references.

You could also implement your own interpolation type (see: Custom Interpolators below).

Latency

Interpolation will add some additional latency to synced bindings. That's because incoming network samples must first be put in a buffer that is then used to calculate the interpolated value.

The amount of latency depends on the binding's sample rate and interpolation type. The lower the sample rate, the higher the latency.

Linear Interpolation requires a headroom of one sample while Spline Interpolation requires two samples. If interpolation type is set to None, there is no additional latency added, and samples will be rendered as soon as they arrive over the network.

Example: A Prefab that uses Spline Interpolation for its position binding with a sample rate of 30 Hz and network latency of 100 ms will appear to be 2*1/30+0.100 = 0.16 s behind the local time.

Since a Prefab can define separate interpolation types and sample rates for its different bindings, it is possible that not all bindings share the same latency. If, for example, position and rotation are interpolated with different latency, the position and rotation of a vehicle might not match on the remote object.

Other Settings

There are a few settings you can tweak:

  • Smoothing

    • Smooth Time: additional smoothing can be applied (using SmoothDamp) to clear out any jerky movement after regular interpolation has been performed.

    • Max Smoothing Speed: the maximum speed at which the value can change, unless teleporting.

  • Latency

    • Network Latency Factor: fudge factor applied to the network latency. A factor of 1 means adapting to network latency with no margin, so the incoming sample must arrive at its exact predicted time to prevent the buffer from becoming stale. In general, a factor of 1.1 is recommended to prevent network fluctuations from causing dead reckoning due to latency peaks.

    • Network Latency Cooldown: when network latency decreases, wait this amount of time (in seconds) before recalculating network latency. This prevents network fluctuations from causing dead reckoning due to latency valleys.

    • Additional Latency: increases latency by a fixed amount (in seconds) to add an additional margin for the sample buffer.

  • Overshooting

    • Max: how far into the dead reckoning to venture when the time fraction exceeds 100%, as a percentage of the sample rate.

    • Retraction: how fast to pull back to 100% when overshooting the allowed dead reckoning maximum (in seconds)

  • Teleport Distance: if two consecutive samples are further apart than this, the value will teleport or snap to the new sample immediately without interpolating or smoothing in between.

  • Stale Factor: defines when to insert a virtual sample in case of a longer time gap between the samples. High stale factor puts the virtual sample close to first sample leading to a smooth transition between two distant samples. This is suitable for parameters that do not change rapidly - the position of a big ship for example. Low stale factor places the virtual sample near the second sample resulting in initial lack of change in value during interpolation followed by a quick transition to the second sample. This is best suited for parameters that can change rapidly, e.g. position of a player.

Dead reckoning is a form of replicated computing so that everyone participating in a game winds up simulating all the entities (typically vehicles) in the game, albeit at a coarse level of fidelity.

The basic notion of dead reckoning is an agreement in advance on a set of algorithms that can be used by all player nodes to extrapolate the behavior of entities in the game, and an agreement on how far reality should be allowed to get from these extrapolation algorithms before a correction is issued.

Interpolation settings can be tweaked in Play mode where you can see the result on the screen immediately, but the changes you make will be reverted again once you exit Play mode. This is because - in Play mode - a copy of the interpolation settings is created.

Remember that interpolation only happens on remote objects, so you need to select a remote object to experiment with interpolation settings in Play mode.

Interpolation works both in Baked and Reflection modes. You can change these settings at runtime via the Configure window (editor) or by accessing the binding and changing the interpolation settings yourself:

if (coherenceSync.TryGetBinding(typeof(Transform), "position", out Binding binding))
{
    // change your interpolation settings at runtime
    binding.interpolationSettings = someSettings;
}

Custom interpolators

The Linear and Spline interpolators that are provided by coherence are sufficient for most common use cases, but you can also implement your own interpolation algorithm by sub-classing Interpolator.

You can choose to override one or more of the base methods depending on which type or types of values you want to support. The method signatures usually take two adjacent samples and a fractional value (from 0 to 1) to blend between them. There are also method signatures that provide four samples, which is useful for the Catmull-Rom spline interpolation.

Here's an example of a custom interpolator that makes the remote object appear at an offset distance from the object's actual position.

using Coherence.Interpolation;
using UnityEngine;

public class InterpolatorWithOffset : Interpolator
{
    public Vector3 offset;
    public override float NumberOfSamplesToStayBehind => 1;

    public override Vector3 InterpolateVector3(Vector3 value1, Vector3 value2, float t)
    {
        var interpolatedValue = Vector3.Lerp(value1, value2, t);
        return interpolatedValue + offset;
    }
}

The NumberOfSamplesToStayBehind property controls the internal latency.

Catmull-Rom splines require four samples to blend between, so its NumberOfSamplesToStayBehind property must be set to 2.

Interpolation timing

By default, each binding is interpolated on every Update call. This can be changed using the Interpolate On property on the CoherenceSync under Advanced Settings. Possible values are:

  • Update / LateUpdate / FixedUpdate - bindings will be updated with interpolated values on every Update / LateUpdate / FixedUpdate call

  • Combination - you can combine any of the above, so that bindings are updated in more than one Unity callback

  • Nothing - bindings will completely stop receiving new values because interpolation is fully disabled

If you are using Rigidbody for movement of a GameObject, it is recommended to set Interpolate On to FixedUpdate. Also, to achieve completely smooth movement, Rigidbody interpolation should be enabled and you should avoid setting the position of a GameObject directly using Transform.position or RigidBody.position.

Extrapolation

Extrapolation uses historical data to predict the future state of a binding. By predicting the state of other players before their network data actually arrives, network lag can be reduced or removed entirely. This will cause mispredictions that need to be corrected when the incoming network data does not match the predicted state.

Extrapolation is not yet supported by coherence.