Search Unity

Can't achieve a smooth framerate-independent following camera using Vector3.Lerp.

Discussion in 'Scripting' started by AlanGameDev, Sep 2, 2015.

  1. AlanGameDev

    AlanGameDev

    Joined:
    Jun 30, 2012
    Posts:
    437
    Hello there.

    I'm trying to achieve a very very simple 'camera follow' using linear interpolation, but there's no way i can get it working properly. No matter what I do, when the game stutters, the camera goes crazy. I've discussed this extensively on IRC and we couldn't find a proper solution, so I made an SSCCE and i'm attaching it to this post. Please note that you have to build in order to test because targetFrameRate doesn't work on the editor.

    I've tried smoothdamp and the same problem occurs.

    Here is the only script of the sample (component attached to the camera):
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class CameraController : MonoBehaviour {
    5.  
    6.     public Transform player;
    7.     public Vector3 cameraOffset;
    8.     public Vector3 speed;
    9.     public Transform staticReference;
    10.     public float cameraDelay;
    11.  
    12.     void Update () {
    13.         player.position += speed*Time.deltaTime;
    14.  
    15.         if (Vector3.Distance(player.position, staticReference.position) > speed.magnitude*1.01f){
    16.             staticReference.position = player.position+speed;
    17.         }
    18.     }
    19.  
    20.     void FixedUpdate(){
    21.         //simulate stutter / fps drops
    22.         if (Random.Range(0,30) == 0){
    23.             Application.targetFrameRate = 5;
    24.         }
    25.         else{
    26.             Application.targetFrameRate = -1;
    27.         }
    28.     }
    29.  
    30.     void LateUpdate() {
    31.         transform.position = Vector3.Lerp(transform.position,player.position+cameraOffset, 1.0f - (float)System.Math.Pow(cameraDelay, Time.deltaTime));
    32.     }
    33.  
    34. }
    35.  
    player is an ordinary sphere.
    cameraOffset is 0,1,-2
    speed is 0,0,30 (i know it's fast)
    staticReference is an object with two cubes for reference of speed
    cameraDelay is 0.001 (because the speed is fast)

    I'm using system.math.pow because cameraDelay used to be a double in the past to make sure the problem wasn't float imprecision (forgot to change back).

    I don't see what's wrong with that code, perhaps you guys could help me.

    Thank you in advance.

    PS: Unfortunately I can't attach the build here, even using the most compact 7z compression. Unity devs: if you read this, please, consider increasing the file size limit to 10mb so we can at least attach SSCCEs which are generally 6-7mb compressed.
     

    Attached Files:

  2. luispedrofonseca

    luispedrofonseca

    Joined:
    Aug 29, 2012
    Posts:
    940
  3. AlanGameDev

    AlanGameDev

    Joined:
    Jun 30, 2012
    Posts:
    437
    Thank you. I'm going to try the alternative lerp functions in that thread. I'm just curious on why it doesn't work. I mean, i'm pretty certain that dt is correct, and the formula is correct too I think:

    lerp( position, targetposition, 1 - ( delay ^ dt ) )

    I'm going to report back if any of the solution there worked properly.

    EDIT: Nah, none of them worked. I tried only the solutions that didn't involve ugly fixes, perhaps I did something wrong, here is how I've used the SuperSmoothLerp:
    EDIT2: Nevermind. I was using a broken snippet. SuperSmoothLerp works properly. I still wonder why the good ole maths aren't working :p.
     
    Last edited: Sep 2, 2015
  4. AlanGameDev

    AlanGameDev

    Joined:
    Jun 30, 2012
    Posts:
    437
    Hey guys, I'm still at loss here, there are lots of pages on the subject and most of them concluded the same as me:

    https://www.scirra.com/blog/ashley/17/using-lerp-with-delta-time
    http://howlingmoonsoftware.com/wordpress/useful-math-snippets/ (although the formula is incorrect here)

    but George Foot from the other thread seems to know better:
    not only he said that but he managed to make a function that actually works in practice.

    I've used a pencil and paper and did manually some 'frames' with different dts and I still don't know why the formula a = lerp(a, b, 1 - f ^ dt) doesn't work properly.

    Personally, I think this is not only an interesting, but an important subject, because if you check the sscce, you'll see that the speed isn't actually too fast, i'd say if the player was a car it would be at a normal city speed, so if you can't have a camera that works decently for that scenario with some occasional stutters and framedrops (say, a heavy game), then we're all glued :p. Perhaps Unity itself could provide other Lerp functions that works properly in the future...
     
  5. luispedrofonseca

    luispedrofonseca

    Joined:
    Aug 29, 2012
    Posts:
    940
    Code (csharp):
    1.  
    2. public static float SmoothApproach(float pastPosition, float pastTargetPosition, float targetPosition, float speed, float deltaTime)
    3. {
    4. float t = deltaTime * speed;
    5. float v = (targetPosition - pastTargetPosition) / t;
    6. float f = pastPosition - pastTargetPosition + v;
    7. return targetPosition - v + f * Mathf.Exp(-t);
    8. }
    9.  
    This is what I'm using on ProCamera2D and it gives super smooth results, even at very high speeds and also during frame drops.
     
  6. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    You've been abusing Lerp from the very beginning here. Lerp interpolates between a starting position and a target position... it does not interpolate between the current position and anything else.

    This is a very common abuse of Lerp, but it's still wrong. (In fact, at one point even the sample code in the Unity docs was using it wrong — not sure if that's still the case, but it might be.)

    Here is a good article that explains it with much more patience than I can muster. I'll only say that the common abuse sort of "works" when frame rates are high and steady, which is why people keep using it, but it breaks down when the frame rate gets too slow or variable, as you discovered.

    If you used Vector3.MoveTowards instead of Lerp, with a max delta computed from Time.deltaTime, I think you would find perfectly stable camera movement no matter what the frame rate is doing.
     
  7. JamesLeeNZ

    JamesLeeNZ

    Joined:
    Nov 15, 2011
    Posts:
    5,616
    The only problem with move position is if you want to jump (say because the camera went behind terrain/building) move towards can mess that up if your delta is too low
     
  8. AlanGameDev

    AlanGameDev

    Joined:
    Jun 30, 2012
    Posts:
    437
    Thank you for sharing your snippet. Isn't this the same thing the function oh the other thread does?

    Thank you for your reply. Just saying 'it's wrong' is not a good argument though, I see no reason why one couldn't use lerp to move stuff around, provided you of course use a correct formula for the fraction t, what's not the case in that link since this:
    Code (CSharp):
    1. float newPositionX = Mathf.Lerp (start, destination, Time.deltaTime);
    is totally wrong :p.

    I never used that function to be honest. I'm going to take a look on it.
     
  9. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    I didn't just say "it's wrong;" I also linked to an article that explains in detail (with picture!) why it's wrong. I didn't (and still don't) see the point of repeating what that article says, since it already says it so well.

    But if you've read through that and still don't understand something, by all means, ask questions and we'll try to help!
     
  10. AlanGameDev

    AlanGameDev

    Joined:
    Jun 30, 2012
    Posts:
    437
    That article has pretty much nothing to do with what I'm asking... I agree it's wrong to use plain dt with Lerp, but that article still uses it in all snippets, what, as I mentioned, is totally wrong :p hehe. It's obvious you can't use plain dt with lerp to achieve that, because every iteration is linearly affected by the current values which are in their turns changed by the previous iterations, I'm not arguing that; I'm just asking why a formula like 1 - (f ^ dt) doesn't work when used for t because that seems to be the correct way to counter balance the differences and achieve something that linearly interpolates according to dt and the distance, I mean, I understand why a differential equation works properly and perhaps it's the most guaranteed way to do that, but it needs extra information, I could just use a different approach as well.