Skip to content

Commit c20b80e

Browse files
Merge branch 'develop-2.0.0' into fix/half-float-delta-position-dither
2 parents 5627fcf + 2ea75a3 commit c20b80e

4 files changed

Lines changed: 153 additions & 11 deletions

File tree

com.unity.netcode.gameobjects/CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,10 @@ Additional documentation and release notes are available at [Multiplayer Documen
2424

2525
### Fixed
2626

27+
- Issue where lerp smoothing was applied per frame instead of over time, which caused the `Lerp` and `SmoothDampening` interpolation types to smooth by different amounts at different frame rates. Results at 60fps are unchanged. (#4130)
28+
- Issue where setting a maximum interpolation time of 1.0 would stop a `NetworkTransform` from interpolating at all when using the `Lerp` or `SmoothDampening` interpolation types. (#4130)
2729
- Issue where objects using `NetworkTransform.UseHalfFloatPrecision` appeared to jitter on non-authority instances while they were stationary or coming to rest, even though the authority was not moving them. (#4128)
2830

29-
3031
### Security
3132

3233

com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -209,12 +209,23 @@ public void Reset(T currentValue)
209209
internal bool LerpSmoothEnabled;
210210

211211
/// <summary>
212-
/// Determines how much smoothing will be applied to the 2nd lerp when using the <see cref="Update(float, double, double)"/> (i.e. lerping and not smooth dampening).
212+
/// The frame rate that <see cref="MaximumInterpolationTime"/> is relative to when lerp smoothing.
213+
/// </summary>
214+
private const float k_LerpSmoothReferenceFrameRate = 60.0f;
215+
216+
/// <summary>
217+
/// Keeps a <see cref="MaximumInterpolationTime"/> of 1.0f from retaining the entire delta each frame,
218+
/// which would stop the value from ever advancing towards the target.
219+
/// </summary>
220+
private const float k_MaximumLerpSmoothRetention = 0.99f;
221+
222+
/// <summary>
223+
/// Determines how much smoothing will be applied to the 2nd lerp.
213224
/// </summary>
214225
/// <remarks>
215-
/// There's two factors affecting interpolation: <br />
216-
/// - Buffering: Which can be adjusted in set in the <see cref="NetworkManager.NetworkTimeSystem"/>.<br />
217-
/// - Interpolation time: The divisor applied to delta time where the quotient is used as the lerp time.
226+
/// Higher values are smoother, lower values are more precise. The amount of smoothing applied is
227+
/// frame rate independent.<br />
228+
/// Buffering also affects interpolation and can be adjusted via <see cref="NetworkManager.NetworkTimeSystem"/>.
218229
/// </remarks>
219230
[Range(0.016f, 1.0f)]
220231
public float MaximumInterpolationTime = 0.1f;
@@ -420,6 +431,26 @@ internal void ResetCurrentState()
420431
}
421432
}
422433

434+
/// <summary>
435+
/// Calculates the frame rate independent lerp smoothing "t" for the current frame.
436+
/// </summary>
437+
/// <remarks>
438+
/// Raising the retained portion to the number of reference frames elapsed makes the smoothing rate
439+
/// a function of elapsed time rather than of how often this is called.
440+
/// </remarks>
441+
/// <param name="deltaTime">The last frame time.</param>
442+
/// <returns>The lerp smoothing time to apply for this frame.</returns>
443+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
444+
private float GetLerpSmoothTime(float deltaTime)
445+
{
446+
var retained = Mathf.Clamp01(MaximumInterpolationTime);
447+
if (retained >= 1.0f)
448+
{
449+
retained = k_MaximumLerpSmoothRetention;
450+
}
451+
return 1.0f - Mathf.Pow(retained, deltaTime * k_LerpSmoothReferenceFrameRate);
452+
}
453+
423454
/// <summary>
424455
/// Interpolation Update to use when smooth dampening is enabled on a <see cref="Components.NetworkTransform"/>.
425456
/// </summary>
@@ -459,7 +490,7 @@ internal T Update(float deltaTime, double tickLatencyAsTime, double minDeltaTime
459490
if (LerpSmoothEnabled)
460491
{
461492
// Apply the smooth lerp to the target to help smooth the final value.
462-
InterpolateState.CurrentValue = Interpolate(InterpolateState.CurrentValue, InterpolateState.NextValue, Mathf.Clamp(1.0f - MaximumInterpolationTime, 0.0f, 1.0f));
493+
InterpolateState.CurrentValue = Interpolate(InterpolateState.CurrentValue, InterpolateState.NextValue, GetLerpSmoothTime(deltaTime));
463494
}
464495
else
465496
{

com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1136,7 +1136,7 @@ public enum InterpolationTypes
11361136
/// Uses a 1 to 2 phase interpolation approach where:<br />
11371137
/// <list type="bullet">
11381138
/// <item><description>The first phase lerps from the previous state update value to the next state update value.</description></item>
1139-
/// <item><description>The second phase (optional) performs lerp smoothing where the current respective transform value is lerped towards the result of the first phase at a rate of 1.0 minus the respective maximum interpolation time.</description></item>
1139+
/// <item><description>The second phase (optional) performs lerp smoothing where the current respective transform value is lerped towards the result of the first phase at a frame rate independent rate determined by the respective maximum interpolation time.</description></item>
11401140
/// </list>
11411141
/// </summary>
11421142
/// <remarks>
@@ -1156,7 +1156,7 @@ public enum InterpolationTypes
11561156
/// Uses a 1 to 2 phase smooth dampening approach where:<br />
11571157
/// <list type="bullet">
11581158
/// <item><description>The first phase smooth dampens towards the current tick state update being processed by the accumulated delta time relative to the time to target.</description></item>
1159-
/// <item><description>The second phase (optional) performs lerp smoothing where the current respective transform value is lerped towards the result of the first phase at a rate of delta time divided by the respective max interpolation time.</description></item>
1159+
/// <item><description>The second phase (optional) performs lerp smoothing where the current respective transform value is lerped towards the result of the first phase at a frame rate independent rate determined by the respective maximum interpolation time.</description></item>
11601160
/// </list>
11611161
/// </summary>
11621162
/// <remarks>
@@ -1236,7 +1236,11 @@ public enum InterpolationTypes
12361236
/// Controls position interpolation smoothing.
12371237
/// </summary>
12381238
/// <remarks>
1239-
/// When enabled, the <see cref="BufferedLinearInterpolator{T}"/> will apply a final lerping pass where the "t" parameter is calculated by dividing the frame time divided by the <see cref="PositionMaxInterpolationTime"/>.
1239+
/// When enabled, the <see cref="BufferedLinearInterpolator{T}"/> will apply a final lerping pass towards
1240+
/// the interpolated result at a rate determined by <see cref="PositionMaxInterpolationTime"/>.<br />
1241+
/// This smoothing pass is frame rate independent under <see cref="InterpolationTypes.Lerp"/> and
1242+
/// <see cref="InterpolationTypes.SmoothDampening"/>. <see cref="InterpolationTypes.LegacyLerp"/> keeps its
1243+
/// original frame rate dependent smoothing, so the same value does not produce the same result there.
12401244
/// </remarks>
12411245
public bool PositionLerpSmoothing = true;
12421246
private bool m_PreviousPositionLerpSmoothing;
@@ -1257,7 +1261,11 @@ public enum InterpolationTypes
12571261
/// Controls rotation interpolation smoothing.
12581262
/// </summary>
12591263
/// <remarks>
1260-
/// When enabled, the <see cref="BufferedLinearInterpolator{T}"/> will apply a final lerping pass where the "t" parameter is calculated by dividing the frame time divided by the <see cref="RotationMaxInterpolationTime"/>.
1264+
/// When enabled, the <see cref="BufferedLinearInterpolator{T}"/> will apply a final lerping pass towards
1265+
/// the interpolated result at a rate determined by <see cref="RotationMaxInterpolationTime"/>.<br />
1266+
/// This smoothing pass is frame rate independent under <see cref="InterpolationTypes.Lerp"/> and
1267+
/// <see cref="InterpolationTypes.SmoothDampening"/>. <see cref="InterpolationTypes.LegacyLerp"/> keeps its
1268+
/// original frame rate dependent smoothing, so the same value does not produce the same result there.
12611269
/// </remarks>
12621270
public bool RotationLerpSmoothing = true;
12631271
private bool m_PreviousRotationLerpSmoothing;
@@ -1278,7 +1286,11 @@ public enum InterpolationTypes
12781286
/// Controls scale interpolation smoothing.
12791287
/// </summary>
12801288
/// <remarks>
1281-
/// When enabled, the <see cref="BufferedLinearInterpolator{T}"/> will apply a final lerping pass where the "t" parameter is calculated by dividing the frame time divided by the <see cref="ScaleMaxInterpolationTime"/>.
1289+
/// When enabled, the <see cref="BufferedLinearInterpolator{T}"/> will apply a final lerping pass towards
1290+
/// the interpolated result at a rate determined by <see cref="ScaleMaxInterpolationTime"/>.<br />
1291+
/// This smoothing pass is frame rate independent under <see cref="InterpolationTypes.Lerp"/> and
1292+
/// <see cref="InterpolationTypes.SmoothDampening"/>. <see cref="InterpolationTypes.LegacyLerp"/> keeps its
1293+
/// original frame rate dependent smoothing, so the same value does not produce the same result there.
12821294
/// </remarks>
12831295
public bool ScaleLerpSmoothing = true;
12841296
private bool m_PreviousScaleLerpSmoothing;

com.unity.netcode.gameobjects/Tests/Editor/InterpolatorTests.cs

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,5 +301,103 @@ public void TestDuplicatedValues()
301301
Assert.That(interp, Is.EqualTo(2f));
302302
// Since there is no extrapolation, the rest of this test was removed.
303303
}
304+
305+
#region Lerp Smoothing
306+
307+
// Deliberately not round numbers, so exactly representable values cannot mask a defect.
308+
private const double k_SmoothTickInterval = 1.0d / 30.0d;
309+
private const int k_SmoothTickLatency = 2;
310+
private const float k_SmoothStartValue = 3.17f;
311+
private const float k_SmoothVelocity = 2.3f;
312+
private const double k_SmoothMoveDuration = 1.53d;
313+
private const double k_SmoothTotalDuration = 2.11d;
314+
315+
/// <summary>
316+
/// Drives the lerp and smooth dampening interpolation path with lerp smoothing enabled, where an
317+
/// authority moves at a constant velocity and then holds still while the non-authority renders at
318+
/// <paramref name="frameDeltaTime"/>.
319+
/// </summary>
320+
/// <returns>The interpolated value once <see cref="k_SmoothTotalDuration"/> has elapsed.</returns>
321+
private float RunLerpSmoothing(float maximumInterpolationTime, float frameDeltaTime, bool lerp)
322+
{
323+
var interpolator = new BufferedLinearInterpolatorFloat
324+
{
325+
MaximumInterpolationTime = maximumInterpolationTime,
326+
LerpSmoothEnabled = true,
327+
};
328+
interpolator.ResetTo(k_SmoothStartValue, 0.0d);
329+
330+
var restValue = k_SmoothStartValue + (float)(k_SmoothVelocity * k_SmoothMoveDuration);
331+
var maxDeltaTime = k_SmoothTickLatency * k_SmoothTickInterval;
332+
var nextTick = 1;
333+
var currentValue = k_SmoothStartValue;
334+
335+
for (var time = 0.0d; time < k_SmoothTotalDuration; time += frameDeltaTime)
336+
{
337+
// Deliver every state update whose send time has already passed.
338+
while (nextTick * k_SmoothTickInterval <= time)
339+
{
340+
var sentTime = nextTick * k_SmoothTickInterval;
341+
var sentValue = sentTime <= k_SmoothMoveDuration
342+
? k_SmoothStartValue + (float)(k_SmoothVelocity * sentTime)
343+
: restValue;
344+
interpolator.AddMeasurement(sentValue, sentTime);
345+
nextTick++;
346+
}
347+
348+
currentValue = interpolator.Update(frameDeltaTime, time - maxDeltaTime, k_SmoothTickInterval, maxDeltaTime, lerp);
349+
}
350+
351+
return currentValue;
352+
}
353+
354+
/// <summary>
355+
/// Lerp smoothing must still advance the value at 1.0f, the maximum legal value of the
356+
/// <see cref="Components.NetworkTransform.PositionMaxInterpolationTime"/> family of fields.
357+
/// </summary>
358+
[Test]
359+
public void LerpSmoothingDoesNotFreezeAtMaximumInterpolationTime([Values] bool lerp)
360+
{
361+
var result = RunLerpSmoothing(1.0f, 1.0f / 60.0f, lerp);
362+
363+
Assert.That(result, Is.GreaterThan(k_SmoothStartValue + 1.0f),
364+
$"Interpolated value only advanced {result - k_SmoothStartValue} from {k_SmoothStartValue} over " +
365+
$"{k_SmoothTotalDuration}s of authority motion. The maximum interpolation time froze the transform.");
366+
}
367+
368+
/// <summary>
369+
/// The rate at which lerp smoothing converges must not depend on the frame rate.
370+
/// </summary>
371+
[Test]
372+
public void LerpSmoothingIsFrameRateIndependent()
373+
{
374+
// Heavier than the default, where the frame rate dependency is measurable.
375+
const float maximumInterpolationTime = 0.87f;
376+
377+
var atThirtyFps = RunLerpSmoothing(maximumInterpolationTime, 1.0f / 30.0f, true);
378+
var atTwoFortyFps = RunLerpSmoothing(maximumInterpolationTime, 1.0f / 240.0f, true);
379+
380+
Assert.That(atThirtyFps, Is.EqualTo(atTwoFortyFps).Within(0.01f),
381+
$"The same elapsed time and interpolation settings produced {atThirtyFps} at 30fps but " +
382+
$"{atTwoFortyFps} at 240fps. The smoothing rate is scaling with the frame rate.");
383+
}
384+
385+
/// <summary>
386+
/// Only 1.0f is substituted for, so settings below it keep their own smoothing rate and a heavier
387+
/// setting stays smoother than a lighter one.
388+
/// </summary>
389+
[Test]
390+
public void LerpSmoothingPreservesSettingsBelowTheMaximum()
391+
{
392+
// 0.99f is the retention substituted for 1.0f, so clamping to it would collapse these two.
393+
var lighter = RunLerpSmoothing(0.99f, 1.0f / 60.0f, true);
394+
var heavier = RunLerpSmoothing(0.995f, 1.0f / 60.0f, true);
395+
396+
Assert.That(heavier, Is.LessThan(lighter),
397+
$"0.995 converged to {heavier} and 0.99 to {lighter} over the same motion. A higher maximum " +
398+
"interpolation time has to retain more of the previous value, so it cannot converge first.");
399+
}
400+
401+
#endregion
304402
}
305403
}

0 commit comments

Comments
 (0)