Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Camera stutters in a simple rotation?!

Discussion in 'Scripting' started by JanDawid, Mar 5, 2016.

  1. JanDawid

    JanDawid

    Joined:
    Jul 7, 2014
    Posts:
    283
    Here's is my method for rotating the camera which is called every LateUpdate():

    Code (CSharp):
    1. void RotateCamera(float angle){
    2.         nextRotateAngle = Mathf.Lerp(nextRotateAngle, angle, Time.smoothDeltaTime * 2f);
    3.         this.transform.RotateAround(lookAtPos, Vector3.up, freeRotateSpeed * nextRotateAngle);
    4.     }
    Which, you may argue isn't lerping smoothly or what have you; so even when I change it to something as basic as this:

    Code (CSharp):
    1. this.transform.RotateAround(lookAtPos, Vector3.up, 2);
    Where 'lookAtPos' is the position of a stationary rigidbody (character) there is still some lag/stutter in the movement. The profiler isn't showing any unusual spikes during the stutter and the character is staying still at a velocity of zero (I can assure you this). The rotation is done in LateUpdate() and the movement in general is very smooth, even when the framerate is at a constant 60fps. The fixed timestep in the Time manager is set to (1/60) to match the framerate so... what on earth is causing this occasional stutter?

    It happens about 5% of the time during the movement (so if I leave the camera spinning around the character like in the second code snippet, it will just stutter ever so slightly for no apparent reason) and I don't understand why.

    Any ideas at all?

    Moving the method to FixedUpdate() or Update() doesn't work. Character is on Interpolate (even if that's not relevant as the camera isn't changing position).

    Edit: It seems as time goes on, the frequency of how often the stutter happens seems to increase...
     
  2. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    Time.smoothedDeltaTime? I've never used it, always used Time.deltaTime without issue. What is this for?

    In your basic example you should use Time.deltaTime because elapsed time between frames is not constant, which makes the movement jurky when the amount of movement is constant.
     
    JanDawid likes this.
  3. JanDawid

    JanDawid

    Joined:
    Jul 7, 2014
    Posts:
    283
    SmoothDeltaTime has given me smoother results everywhere I've used it so, even when changing it to DeltaTime, it's still stuttering. Honestly I don't think the issue lies with the code; maybe a setting somewhere?
     
  4. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    I don't know about Time.smoothedDeltaTime. So I have no idea if it is causing the problem or if it really helps to get smoother movement. Does someone knows exactly what it does? The documentation says nothing specific about it!

    However, Update is for sure not called at fixed interval; the time between two Update varies. That's why we have Time.deltaTime, which gives the elapsed time since the last frame. And that's why we have FixedUpdate, which is called at fixed interval of time.
     
  5. JanDawid

    JanDawid

    Joined:
    Jul 7, 2014
    Posts:
    283
    I'm not 100% sure of the difference myself but it has given me smoother results most of the time. I always test between the two but in this specific situation, the stutter must be affect by something else.

    It's definitely not an update issue and I'm almost certain that the issue is to do with some time setting somewhere. I'll try and show a visual representation of the stutter.

    So, imagine the '-' is a smooth movement, and the '|' is a jitter. Here's what I'm getting for a full 360 degree rotation around the object:

    -----------------------------------------|-------||--------|----------|-------|-------------|-------------|----|------------
    and then the next 360 rotation will be like:
    -------------------------|--------|----|---------------|-----------------|------|-------------------|----|-------|----------

    See how it's very inconsistent and that, for the most part, I get a very smooth movement?
     
  6. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
  7. JanDawid

    JanDawid

    Joined:
    Jul 7, 2014
    Posts:
    283
    Moving it to Update() and changing the deltaTime type doesn't make a difference (nor do all the different combinations).

    Even if it's the only thing running and it's been tested in both LateUpdate() and Update() with delta and smoothdelta it still persists.
     
  8. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    Do you mean there's still jerky movement with:
    Code (CSharp):
    1. void Update()
    2. {
    3.     this.transform.RotateAround(lookAtPos, Vector3.up, 2.0f*Time.deltaTime);
    4. }
    Though, 2.0f* Time.deltaTime means the rotation speed is 2 degrees per second. That's very slow. Is that what you mean by stuttering?

    Edit:
    I'm wondering. Is the lookAtPos also moving?
     
    Last edited: Mar 5, 2016
  9. JanDawid

    JanDawid

    Joined:
    Jul 7, 2014
    Posts:
    283
    Yes, EVEN with that plain and simple movement. When I add delta/smoothDeltaTime I would increase the float value so that's around the same speed; but yes, the jitter sill persists.

    Even when I have it rotate around the point (0,0,0) and there's nothing affecting it's movement/rotation to the point where the entire script for the camera is just simply calling that rotate function, the jitter still persists.

    The character, when still, is set to have a velocity of (0,0,0) with no gravity/forces affecting it so it's definitely not an issue with the camera's target object.
     
  10. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    Weird... Could post your project, or a simpler one reproducing the problem. I'm really curious about what's going on.
     
  11. JanDawid

    JanDawid

    Joined:
    Jul 7, 2014
    Posts:
    283
    That's the thing, my NDA doesn't permit me to share core information on the project so I'll have to make a little mock up and make a video of it. It probably is an issue with a setting in one of the project files but... I'll see what I can do.
     
  12. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    Of course you don't have to send your company assets. Start from an empty project with the minimum that reproduces the problem.
     
  13. JanDawid

    JanDawid

    Joined:
    Jul 7, 2014
    Posts:
    283
    Okay, here's a download link to a mock project with the same project settings, with the same jitter:
    https://dl.dropboxusercontent.com/u/56814081/Test.zip
     
  14. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    Thank you.

    I tested the scene. I rotated the camera using an analogue joystick at different speed. The movement is smooth, I don't perceive any jerky movement.

    Are you sure the problem is not specific on your computer? Do you have a program running in the background? Maybe you should check your performance monitor.
     
  15. JanDawid

    JanDawid

    Joined:
    Jul 7, 2014
    Posts:
    283
    My computer is definitely capable of giving a smooth performance so it can't be that; besides, the FPS is at a steady 60 the whole time. Even when it's the only thing running it still jitters. More-so in the actual project that the test mock one.
     
  16. JanDawid

    JanDawid

    Joined:
    Jul 7, 2014
    Posts:
    283
    Even on the lowest (fastest) quality setting it persists.
     
  17. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    Alright. I could reproduce it by setting a constant rotation speed, and not using the analogue joystick. I observed some random slow down and speed up during the rotation.

    However, using the following works as expected:
    Code (CSharp):
    1.     void RotateCamera(float angle)
    2.     {
    3.         this.transform.RotateAround(lookAtPos.transform.position, Vector3.up, 20.0f*Time.deltaTime);
    4.     }
    5.  
    I don't understand the usage of the Lerp here and the Time.smoothedDeltaTime. Do you have the possibility to discuss it with the author? Maybe you should consider another alternative.
     
    JanDawid likes this.
  18. JanDawid

    JanDawid

    Joined:
    Jul 7, 2014
    Posts:
    283
    Looking at the profiler, I can notice the in the 'Physics' part the 'active rigidbodies' goes quite nuts when rotating... but I don't see how the two are related, and the only rigidbody is the character which is remaining still and is coded very efficiently.

    The lerp function I added was to accel/decel the camera when moving it. The value changes very smoothly and when at the maximum value it doesn't fluctuate, yet the jitter persists.
    I'll try your code now...
    Edit: it still persists >_<
     
    Last edited: Mar 5, 2016
  19. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    I am trying hard. But this usage of the Lerp function does not make any sens to me. I know exactly how the lerp function works it's basically:
    p = (1-s) *p1 + s*p2, where s is between 0 and 1.

    That's the definition of linear interpolation. If you plug in s = Time.deltaTime (or Time.smoothDeltaTime). What the hell should be expected?

    Personally, when I'm in this situation. I would ask someone to explain/justify it to me. If there is no one, I would search online if that's a known technique. If I still find nothing, I'd toss it and rewrite it in a way it's clear to me.
     
    Last edited: Mar 5, 2016
  20. JanDawid

    JanDawid

    Joined:
    Jul 7, 2014
    Posts:
    283
    Okay, the value passed through the parameter is the player input. That goes from 0 to 1 very quickly, so that lerp function just slows that down. Therefore, rather than the camera going from 'not moving at all' to 'moving at full speed' is smoothed out, and when the player lets go, the camera will slow down to a stop rather than instantly stop.
    When value lerps from itself to a finish value, rather than a set start value to a finish value, it smooths that entire process out.

    Regardless, even if you remove that and just use 'angle' instead of 'nextRotateAngle', the stutter still persists.

    From this point on, lets just assume that the problem is not with the rotational code, but maybe in a setting somewhere. Or that physics oddity in the profiler.
     
  21. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    That's not correct.
    There is no jittering with:
    Code (CSharp):
    1.     void RotateCamera(float angle)
    2.     {
    3.         this.transform.RotateAround(lookAtPos.transform.position, Vector3.up, 20.0f*Time.deltaTime);
    4.     }
    Therefore, that Lerping method is causing the jittering.

    I know you want to give some momentum to the camera movement, so that it does not start or stop abruptly.

    I am just questioning if this lerping technique is a good way to do it.
     
  22. JanDawid

    JanDawid

    Joined:
    Jul 7, 2014
    Posts:
    283
    EVEN when I get rid of the lerping and just use 'angle' as it is, it persists.
    It's not the rotation code.

    And the stutter does persist even with the function you've shown.

    I think at this point, if the most basic rotation code makes the camera stutter then it's a massive oversight on Unity's bug team.
     
  23. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    I'll have an even closer look at it later. But I'm not convinced that's a Unity bug.

    Though you could try and convince the development team by submitting a bug report.
     
  24. JanDawid

    JanDawid

    Joined:
    Jul 7, 2014
    Posts:
    283
    I was more joking saying it's a Unity bug, because a simple 'rotate around an object while holding left/right' should work flawlessly, and it does on a new clean project. But on this project... it truly baffles me!
     
  25. rorydriscoll

    rorydriscoll

    Joined:
    Apr 14, 2015
    Posts:
    23
    You have a couple of problems here. Your first problem is that you've got your units confused. RotateAround takes and angle to rotate around this frame, so if you pass a constant into it you will get inconsistent rotation speed (since speed is rotation this frame / time delta).

    Secondly, using a lerp in this fashion is also frame rate dependent. You need to factor in the current time delta. I'm assuming here that you want to smooth out the rotation speed.

    Here's a fixed version with some logging in to show that the rotation is correct under varying time deltas.

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. public class CameraFollower : MonoBehaviour {
    6.  
    7.     float currentAngularSpeed;
    8.     [SerializeField] GameObject lookAtPos;
    9.     [SerializeField] float freeRotateSpeed; // You can treat this as degrees per second
    10.    
    11.     [SerializeField] [Range(0, 1)] float smoothing = 0.5f;
    12.  
    13.     void Start () {
    14.     //    Application.targetFrameRate = 60;
    15.     }
    16.    
    17.     void LateUpdate() {
    18.             RotateCamera(Input.GetAxisRaw("Horizontal") > 0 ? 1 : 0, Time.deltaTime);
    19.     }
    20.    
    21.     public static float Smooth(float source, float target, float rate, float dt)
    22.     {
    23.         return Mathf.Lerp(source, target, 1 - Mathf.Pow(rate, dt));
    24.     }
    25.  
    26.     void RotateCamera(float scale, float dt)
    27.     {
    28.         float desiredAngularSpeed = scale * freeRotateSpeed;
    29.        
    30.         currentAngularSpeed = Smooth(currentAngularSpeed, desiredAngularSpeed, smoothing, dt);
    31.        
    32.         Quaternion a = transform.rotation;
    33.         this.transform.RotateAround(lookAtPos.transform.position, Vector3.up, currentAngularSpeed * dt);
    34.         Quaternion b = transform.rotation;
    35.        
    36.         float actual = Mathf.Acos(Mathf.Min(1.0f, Quaternion.Dot(a, b))) * 2 * Mathf.Rad2Deg / dt;
    37.        
    38.         Debug.Log("dt: " + Time.deltaTime * 1000 + " ms, desired: " + currentAngularSpeed + " deg/s" + ", actual: " + actual + " deg/s");
    39.     }
    40. }
    41.  
    42.  
     
    Matisseio, no-luck and JanDawid like this.
  26. JanDawid

    JanDawid

    Joined:
    Jul 7, 2014
    Posts:
    283
    Really awesome rotating function there; ultimately it doesn't have much of a different effect to how it looked originally but it's still amazing.

    Unfortunately the stutter STILL persists. After trying this code snippet, I'm almost certain the problem lies elsewhere. It might be my imagination but trying your code, I think, has slightly reduced it... as it's still there and quite noticeable, I'll just say it IS my imagination, haha.
     
  27. rorydriscoll

    rorydriscoll

    Joined:
    Apr 14, 2015
    Posts:
    23
    One other thing - you have disabled vsync, so it's very likely you will get wildly different frame time deltas (mine ranged between 3 ms and 12 ms), so the frame rate won't actually be smooth. The camera motion will be correct, but it could be the inconsistent frame rate that you're picking up on. There's really nothing you can do about that apart from using vsync.

    I tried switching to setting the camera position directly and graphing out the rotation speed vs time and also frame rate against time. On my machine it's all looking correct.

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4. using System.Collections.Generic;
    5.  
    6. [ExecuteInEditMode]
    7. public class CameraFollower : MonoBehaviour {
    8.  
    9.     float currentAngularSpeed;
    10.     [SerializeField] GameObject lookAtPos;
    11.     [SerializeField] float freeRotateSpeed; // You can treat this as degrees per second
    12.    
    13.     [SerializeField] [Range(0, 1)] float smoothing = 0.5f;
    14.    
    15.     [SerializeField] [Range(0, 360)] float theta = 0;
    16.     [SerializeField] [Range(0, 90)] float phi = 20;
    17.    
    18.     public float radius = 20;
    19.    
    20.     private List<Vector3> samples = new List<Vector3>();
    21.  
    22.     void Start () {
    23.     //    Application.targetFrameRate = 60;
    24.     }
    25.    
    26.     void LateUpdate() {
    27.             RotateCamera(Input.GetAxisRaw("Horizontal") > 0 ? 1 : 0, Time.deltaTime);
    28.     }
    29.    
    30.     public static float Smooth(float source, float target, float rate, float dt)
    31.     {
    32.         return Mathf.Lerp(source, target, 1 - Mathf.Pow(rate, dt));
    33.     }
    34.  
    35.     void RotateCamera(float scale, float dt)
    36.     {
    37.         float desiredAngularSpeed = scale * freeRotateSpeed;
    38.        
    39.         currentAngularSpeed = Smooth(currentAngularSpeed, desiredAngularSpeed, smoothing, dt);
    40.        
    41.         theta += currentAngularSpeed * dt;
    42.        
    43.         Quaternion a = transform.rotation;
    44.        
    45.         float thetaRadians = theta * Mathf.Deg2Rad;
    46.         float phiRadians = phi * Mathf.Deg2Rad;
    47.        
    48.         Vector3 anchor = lookAtPos.transform.position;
    49.        
    50.         transform.position = anchor + new Vector3(Mathf.Cos(thetaRadians), Mathf.Sin(phiRadians), Mathf.Sin(thetaRadians)) * radius;
    51.         transform.LookAt(lookAtPos.transform.position);
    52.  
    53.         Quaternion b = transform.rotation;
    54.        
    55.         if (dt > 0)
    56.         {
    57.             float expected = currentAngularSpeed;
    58.             float actual = Quaternion.Angle(a, b) / dt;
    59.            
    60.             Debug.Log("dt: " + Time.deltaTime * 1000 + " ms, expected: " + expected + " deg/s" + ", actual: " + actual + " deg/s");
    61.  
    62.             samples.Add(new Vector3(Time.time, 1.0f / dt, actual));
    63.            
    64.             DrawGraph();
    65.         }
    66.     }
    67.    
    68.     private void DrawGraph()
    69.     {
    70.         if (samples.Count < 2)
    71.             return;
    72.        
    73.         float tm = samples[samples.Count - 1].x;
    74.        
    75.         const int maxSamples = 300;
    76.        
    77.         for (int i = Mathf.Max(samples.Count - maxSamples, 0); i < samples.Count - 1; ++i)
    78.         {
    79.             Vector3 sa = samples[i];
    80.             Vector3 sb = samples[i + 1];
    81.            
    82.             float scaleX = 0.1f;
    83.             float scaleY = 0.001f;
    84.  
    85.             float t0 = sa.x;
    86.             float t1 = sb.x;
    87.            
    88.             float x0 = (t0 - tm) * scaleX;
    89.             float x1 = (t1 - tm) * scaleX;
    90.            
    91.             float e0 = sa.y * scaleY;
    92.             float e1 = sb.y * scaleY;
    93.                            
    94.             Vector3 pea = new Vector3(x0, e0, 1);
    95.             Vector3 peb = new Vector3(x1, e1, 1);
    96.  
    97.             Debug.DrawLine(transform.TransformPoint(pea), transform.TransformPoint(peb), Color.yellow);
    98.            
    99.             float a0 = sa.z * scaleY;
    100.             float a1 = sb.z * scaleY;
    101.  
    102.             Vector3 paa = new Vector3(x0, a0, 1);
    103.             Vector3 pab = new Vector3(x1, a1, 1);
    104.  
    105.             Debug.DrawLine(transform.TransformPoint(paa), transform.TransformPoint(pab), Color.green);
    106.         }
    107.     }
    108. }
    109.  
    110.  
    111.  
     
    no-luck and JanDawid like this.
  28. JanDawid

    JanDawid

    Joined:
    Jul 7, 2014
    Posts:
    283
    For the graph, I'm getting a green line, slightly above but parallel to a horizontal yellow line. I assume that's correct?
    Even with the 2 v-sync options I'm STILL getting that occasional stutter... What could honestly be causing it?!
    It's like the camera rotation itself is completely smooth, but something keeps butting in to stutter it.
     
    Last edited: Mar 6, 2016
  29. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    Alright.

    I did some research about that way of using the Lerp function and I found this post:

    https://chicounity3d.wordpress.com/2014/05/23/how-to-lerp-like-a-pro/

    It's a good post about how to correctly use interpolation. He says that way of using the lerp function is wrong, but he didn't really explain why is wrong.

    Therefore, I am going to demonstrate why this technique is plain wrong. I am going to use the the position instead of rotation, because it makes the reasoning more intuitive to follow. But the same reasoning can be applied to rotation as well, or any value changing in time.

    So we want to move the camera smoothly. Let's be rigorous about what we mean by "smoothly": the movement is smooth if the position, function of time, is continuous. (see the mathematical definition of continuous function).

    In Unity we update the position frame by frame. The general equation of a movement is:

    position(f+1) = position(f) + displacement(f).

    Which means the position at the next frame f+1 is the position at the current frame plus some displacement at frame f.

    If we express the position as a function of time t, we have:

    position(t+deltaTime) = position(t) + displacement(deltaTime).

    Which means the position on the next time step is the position on the current step plus some displacement.

    We know that the displacement is:

    displacement(deltaTime) = speed(t)*deltaTime

    so we have:

    position(t+deltaTime) = position(t) + speed(t)*deltaTime.

    Nothing uncommon so far. But note that if the position is smooth, it implies that the speed has to be smooth as well. (The derivative of a continuous function is continuous or zero).

    Now we want to change the speed to a target speed. But if we change the speed at once, it will make an abrupt step in the speed function, which means the speed is not continuous. As a consequence the position won't be continuous neither. We don't want that, we want to make that change smooth over time.

    But we have a great idea (straight out of our hat)! Let's use the Lerp function in combination with deltaTime to make the speed smooth! So we happily write:

    speed(t+deltaTime) = speed(t) + Lerp( speed(t), targetSpeed, deltaTime).

    But wait a minute, deltaTime is not continuous, since its value changes from frame to frame randomly. Which means this magical lerp function is not continuous. Therefore the speed is not continuous. As a consequence the position won't be continuous neither. We are back at the same point...

    In conclusion, we can't use the Lerp function with deltaTime as an interpolation parameter to obtain a smooth movement. And more generally, deltaTime can't be used as an interpolation parameter.
     
    Last edited: Mar 6, 2016
    no-luck likes this.
  30. rorydriscoll

    rorydriscoll

    Joined:
    Apr 14, 2015
    Posts:
    23
    Sorry, I wasn't clear about that. The yellow line is the frame rate, and the green line is the camera speed. The camera speed should look perfectly smooth as you spin the camera. Take a look at the screenshot I took, and copy the settings. When you press right on the keyboard, it should smoothly spin up and when you let go it will smoothly go back down. You can see from the measured angles that the camera rotation is perfectly smooth.

    One thing you need to consider is that frame spikes will always cause stutter. We use the delta time from the last frame to advance the simulation (since we obviously don't know how long the current frame will take). This means that during a spike frame, the camera will not move enough for the current frame time, and the next frame it will compensate by moving it more. As far as the internal simulation goes, it's completely consistent and smooth, but if the view of the simulation is being presented in an inconsistent manner, then you will perceive jittering. The worse the spikes, the worse the jitter.

    Screen Shot 2016-03-06 at 9.30.37 AM.png
     
    no-luck likes this.
  31. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    Summary: As long as deltaTime is constant, the movement is smooth.

    However, in the Update function deltaTime is not guaranted to be constant. It is normally expected that it varies. It varies depending on the cpu load.

    If you want to have a constant deltaTime, you have to use FixedUpdate.
     
    Last edited: Mar 6, 2016
  32. rorydriscoll

    rorydriscoll

    Joined:
    Apr 14, 2015
    Posts:
    23
    This isn't true. It's quite possible to use lerp in a frame rate independent manner using delta time. In this case, we're using lerp to apply a damping effect, i.e. the output is going to be one of the inputs next frame:

    a = lerp(a, b, t)
    = a * (1 - t) + b * t

    If we define r = 1 - t, then we can flip the parameters and this can be written equivalently as:

    a = lerp(b, a, r)
    = b * (1 - r) + a * r

    If we imagine that our frame rate is fixed at one frame per second, let's look at what happens over the first few frames:

    a0 = a
    a1 = b * (1 - r) + a0 * r
    = b * (1 - r) + a * r

    a2 = b * (1 - r) + a1 * r
    = b * (1 - r) + (b * (1 - r) + a * r) * r
    = b * (1 - r) + b * (r - r^2) + a * r^2
    = b (1 - r^2) + a * r^2

    There's a pattern here. We can generalize this to n seconds:

    an = b * (1 - r^n) + a * r^n

    Looking back, this is exactly the same as:

    an = lerp(b, a, r^n)

    We can flip the parameters, remembering to invert the last parameter:

    an = lerp(a, b, 1 - r^n)

    Finally, remember that r = (1 - t), then:

    an = lerp(a, b, 1 - (1 - t)^n)

    The number of seconds, n, can be a fractional number, as is the case for our delta time. If you look carefully at the code I actually use, you'll see that I lerp with 1 - t^n. The only reason for this is to invert the meaning of the t parameter so that t = 0 means no smoothing (i.e. always accept the new parameter b), and 1 means 100% smoothing (which means don't change).

    One last point I'd like to make is that even if you have vsync turned on, your frame rate may vary. This means you need to make sure that your simulation deals with these changes appropriately in order to minimize the stuttering.
     
    no-luck likes this.
  33. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    Do we agreed that t here means deltaTime, not time?
    If that's the case, you get a smooth movement only with FixedUpdate, where deltaTime is constant. Not in Update.

    You've lost me there :). Why are we flipping the parameter?

    I totally agree here, no need to further develop to convince me: Fixed deltaTime => smooth.

    I'm not sure why you introduced vsync here. But I agreed that vsync does not imply constant frame rate. But could you explain why the stuttering occurs in the first place?

    To be sure we are at the same page, what I'm saying is:
    - constant deltaTime => it's possible to make smooth movement.
    - variable deltaTime => if deltaTime is used as interpolating parameter, you can't have a smooth movement.

    Now, we might perceive that the movement is smooth in the second case. That happens because deltaTime is approximately constant. As long as the frame rate is steady, we are fine with this approximation. But as soon the frame rate is unstable, there are more variation in deltaTime, therefore the movement is less smooth and it becomes noticeable.
     
  34. rorydriscoll

    rorydriscoll

    Joined:
    Apr 14, 2015
    Posts:
    23
    It's either, depending on your frame of reference.

    It just makes the equations easier to simplify. Doing it the other way around requires a lot more work for the same answer.

    Yes, the stuttering is due to the varying presentation interval. Importantly, with frame rate aware lerping, the stuttering is **not** caused by inconsistency in the internal movement.

    I disagree here. Let's say your fixed update runs at 60 fps. If you lerp a value x towards a target at a specific rate inside your fixed update, it looks like this:

    x = lerp(x, target, rate)

    Then this is equivalent to doing the following inside Update (or LateUpdate):

    x = lerp(x, target, 1 - pow(1 - rate, 60 * dt))

    In fact, the one inside Update is actually *more* accurate for the current frame time since it includes the fraction of the fixed update frame that couldn't be processed. The fixed update version may run once, twice or more for any given frame, even if the frame has the exact same delta time as the previous one. This is due to the accumulation of the remaining delta time. This is also why you really need to interpolate or extrapolate physics transforms to get them to look smooth.

    So basically, you can absolutely have a frame-rate independent lerp inside Update using deltaTime. Stuttering due to the presentation interval is unavoidable, and calculating things inside fixed update doesn't help this. The only thing that can help this is to minimized the difference in frame times, normally by using vsync.
     
    no-luck likes this.
  35. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    :eek:That does not make sens. In which frame of reference deltaTime corresponds to time in another frame of reference.
    Are you saying deltaTime and time can be interchangeable?
     
  36. rorydriscoll

    rorydriscoll

    Joined:
    Apr 14, 2015
    Posts:
    23
    No I'm not saying that. If the frame of reference is the start of the game we call it time, and if the frame of reference is the time that the previous frame started we call it deltaTime. Either way it's measured in seconds and makes no difference in this particular equation.
     
    no-luck likes this.
  37. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    The concept of frame of reference has a precise definition in science. I'm sure you don't mean it here.

    There is no need for convoluted wording to redefine time and deltaTime. They are pefectly defined in the Unity documentation.

    When you introduce a symbol, you have to define it unambiguously if you want people to take you writing seriously. So when you use the letter 't', what are you referring to? time or deltaTime. Even if they both have the same unit (second), they are not to be confused.
     
    Last edited: Mar 6, 2016
  38. rorydriscoll

    rorydriscoll

    Joined:
    Apr 14, 2015
    Posts:
    23
    I refer to either. I'm well aware of what a frame of reference is. Time in this context is simply an offset from a specific reference point. For Unity, Time.time is relative to (roughly) the start of the game, and Time.deltaTime is relative to the start of the previous frame. When using lerp like this, it doesn't matter as long as the values you're sending in are also relative to the same point.

    e.g. If we say x(0) is the position of an object at the start of the game, then we can find the smoothed position for any time (given a constant rate and target value) by:

    x(t) = lerp(x(0), target, 1 - pow(1 - rate, Time.time))

    Alternatively, lets say that we have the value from the last frame, x(t - dt) and we want to find the value for the current frame x(t), it's the same formula, but the frame of reference has changed from the beginning of the game to the beginning of the last frame, so we have to use deltaTime.

    x(t) = lerp(x(t - dt), target, 1 - pow(1 - rate, Time.deltaTime))

    I hope that you can see that the formula is the same, but it's just the frame of reference that is different.

    Perhaps this will help: I've created a spreadsheet showing a simple lerp performed at a fixed update rate of 25 fps. I've also included the frame rate independent lerp formula running at random intervals. I just picked some intervals by hand, but you can see that the delta time varies between 20 and 80 milliseconds.

    It's very clear looking at the graph that the frame rate independent version is always correct. In fact, given that the game renders once every Update, the Update version is more correct since it calculates the value for the current time. For the fixed update version, it may not have updated yet.

    On the graph, look at time = 0.07 seconds for example. The LateUpdate version has the correct position of 5.36 m, but the latest FixedUpdate version is from a 30 milliseconds ago, and is still at 7 m!

    https://docs.google.com/spreadsheets/d/1gdraMDdTOah1oyXtk8poUS042ikabqqGiRbv45VlUSQ/edit?usp=sharing

     
    no-luck likes this.
  39. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    I'm sorry but you can't come up with a formula and say that it does not matter whatever you plug in (either time or deltaTime), the result would be same.

    It's like saying position and velocity is the same. Or a function and its derivative is the same.

    It might 'work' in the sens that a bunch of numbers get crunched and other numbers are spat out. But what does it actually mean, physically?

    We've discuss this topic long enough.

    Using Lerp with deltaTime is, for you, a reliable smoothing method whereas, for me, this method is unreliable and unfounded.

    We just disagree about it. Nonetheless, it was a great discussion!

    I hope Jackiiii has in the meantime found a satisfying solution to its problem :).
     
    Last edited: Mar 7, 2016
  40. rorydriscoll

    rorydriscoll

    Joined:
    Apr 14, 2015
    Posts:
    23
    I'm prepared to admit I'm wrong if you can show me a mistake in my derivation, or if you can show me how it's wrong on the spreadsheet. You should probably go back and reread the derivation for the lerp function, then you will understand what I mean. I would challenge you to prove me wrong mathematically or plug in some numbers on the spreadsheet that don't work!

    I also think you're a bit confused with your terminology. Time and delta time both have units of seconds. They are both offsets in seconds from a particular point in time. It doesn't matter that one is the beginning of the game and one is the last frame as long as the other parameter you pass respects this (i.e. 'a' is position at the beginning of the game, or position last frame accordingly).

    Position and velocity have different units. A function and its derivative have different units. These are not interchangeable.

    You say that lerping with deltaTime (I assume you mean as I have used it - not how they guy in your link was trying to use it) is "unreliable and unfounded" when I have proven otherwise for both! Use mathematics rather that opinions and you will have a stronger argument. I can't argue against your opinion - I can only prove that it is wrong.

    I've added a column to the spreadsheet which calculates the damping using only the equivalent of Time.time and the position of the object at the start. It is the same as the others

    So I have shown what you and I both agree is correct - a regular lerp inside a fixed update. I have also shown a frame-rate independent lerp inside LateUpdate under varying timeDelta conditions that exactly matches the FixedUpdate version. Finally, I've shown that it's possible to use the time and position from the start of the simulation along with the framerate independent lerp to get the exact same numbers. Therefore they are all three equivalent! If the FixedUpdate version is correct, and the others are equivalent, that means the others must also be correct.

    image.png

    Perhaps I have failed to explain it clearly enough. If you would like to see it explained by someone else, take a look here:

    http://lolengine.net/blog/2015/05/03/damping-with-delta-time
    http://howlingmoonsoftware.com/wordpress/useful-math-snippets/
    https://www.scirra.com/blog/ashley/17/using-lerp-with-delta-time
     
    no-luck likes this.
  41. _met44

    _met44

    Joined:
    Jun 1, 2013
    Posts:
    633
    On the stuttering topic, have you had a look at the profiler ? Place the cursor on the spike frame and check out what comes up at the top of the list ?
     
  42. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    Alright.

    We were not talking about the same thing. Then it's hard to come to an agreement :).

    I was focused on this lerping method:

    velocity = Lerp( velocity, target_velocity, deltaTime) (or variations with k*deltaTime)

    This method is not stable and frame rate dependent.

    Whereas, you were talking about this one:

    velocity = lerp(velocity, target_velocity, 1.f - pow(1.f - K / 60.f, 60.f * delta_time));

    Which is frame rate independent (I've tested it). This damping technique is new me to, and it's really interesting. Thank you to have bring it here. It's an exponential decay function. This type of function has interesting derivatives properties, its nth derivatives is also an exponential function and that's why this function is stable... I guess (I would need to look at this further). I think that's the point you were trying to make across, while talking about similarities with time and deltaTime using the term "frame of reference". deltaTime is the discrete equivalent of the dt used in calculus, which appears in time derivatives (example: position(t) = velocity(t)*dt).

    You should have bring your references earlier. I would have less the impression that these formulas is coming out of nowhere. It could have save a lot of typing :).

    No, I'm not confused by time and deltaTime or what they mean, neither by their unit. It's not "my" terminology. Again, they're from the Unity API and are documented. But what exactly does not matter? That's where I'm confused.

    This it a great damping technique and it's working.

    I'm really glad your have introduced me to this technique, and again thank you for that!
     
    Last edited: Mar 7, 2016
  43. rorydriscoll

    rorydriscoll

    Joined:
    Apr 14, 2015
    Posts:
    23
    Ok, well that explains all our crosstalk then! I've only ever been talking about the function I wrote and derived in my posts. The upside of this is that it prompted me to write a blog post about it all, since it seems to crop up quite a lot with Unity, and references like the link you used don't really explain what the correct solution is. Thanks for the discourse.

    http://www.rorydriscoll.com/2016/03/07/frame-rate-independent-damping-using-lerp/
     
    no-luck likes this.
  44. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    Ok, well that explains all our crosstalk then! I've only ever been talking about the function the OP wrote and derived in my posts why it was wrong. :)

    The link I use tells how to correctly do frame rate independant interpolation in general, not only the specific case of damping.
     
    Last edited: Mar 8, 2016
  45. rorydriscoll

    rorydriscoll

    Joined:
    Apr 14, 2015
    Posts:
    23
    I read the article again. The example is somewhat limited, since in practice you don't often have a fixed source and target position that you know up front. For something like a camera following a path you obviously do, but as is the case in the OP example, and anything where input is involved for moving the target, you don't.

    With the code in the article (accumulating time, then lerping between source and target based on that normalized time), you might be tempted to just update the target position as you go along, but that can introduce large changes in the current position. You obviously can't update the source position to the current position because then we're back at the original problem.
     
    no-luck likes this.
  46. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    I see what you mean:
    To do interpolation you need two given points and an interpolation parameter. When these inputs are not available or somewhat dynamic, as in the OP problem, interpolation is inappropriate.

    The damping technique you proposed is one solution to the OP problem. It's smooth and frame rate independent.

    In my opinion a steering technique with a momentum applied to the camera is more appropriate and simpler.

    This damping technique is interesting (from a mathematical perspective) yet problematic. It uses/hacks the Lerp function in an unexpected way, somewhat recursively: the interpolant is also the start parameter. it exploits properties of the exponential function. It makes it complicated to understand and to control/tweak.
     
  47. rorydriscoll

    rorydriscoll

    Joined:
    Apr 14, 2015
    Posts:
    23
    That's the thing, if you read my blog post, it's actually quite easy to tweak. The smoothing parameter dictates how much of your current position will be remaining a second from now. Say you're at 10m damping towards zero, if you have smoothing of 0.5, you know you'll be at 5m one second from now, and 2.5m two seconds from now. In practice I expect most people would just create a slider and slide it until it feels good, but it's nice knowing what that parameter means.
     
    no-luck likes this.
  48. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    yeah, yeah, yeah. I agree.

    What I mean about tweaking and understanding. It's that it would be hard so "sell" this solution to a production team, where you can have different people not math-acquainted. I don't see myself explaining that to an artist, other than "Trust me, it works!". And I think I would have even a hard time introducing that to another programmer, only justifying that hack of the Lerp function. Furthermore, for the curve shape, there is no option than the exponential decay curve. People usually like to tweak curve in a visual way.

    But nonetheless, I think they are use cases to use this technique. That curve shape is satisfactory in a lot of situations, it's a damping after all.
     
    Last edited: Mar 9, 2016
  49. rorydriscoll

    rorydriscoll

    Joined:
    Apr 14, 2015
    Posts:
    23
    I don't think it should be too hard to sell. This 'hack' (as you persist in call it) is used pretty commonly in the games industry. The God of War games have used it for many, many years and the designers have had no trouble in tuning it despite not even using sliders.
     
    no-luck likes this.
  50. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    It's a hack, because the Lerp function is used in a way that is not intended to be used. This word is appropriate here.

    I saw the linear version once, a = lerp(a, b, dt), but didn't pay more attention to it because it was obviously wrong to me. But the exponential version is really new to me. I never knew it is was an established technique in the game industry. I'm glad to hear it from an insider.