Search Unity

Resolved How to properly fix camera and rigidbody stutter / jitter / lag?

Discussion in 'Physics' started by Qriva, Mar 11, 2022.

Thread Status:
Not open for further replies.
  1. Qriva

    Qriva

    Joined:
    Jun 30, 2019
    Posts:
    1,306
    It is 2022, there are plenty posts about this topic and I still do not know how to properly fix shaking in the game.
    I don't know anymore. Am I missing something blindly obvious or this problem cannot be solved?

    Let's start from the beginning.
    There is rigidbody player character/vechicle and camera following the player.
    Camera position is updated in LateUpdate and for this reason player will appear jittery and the cause is that physics has different update interval than rendering.
    Someone is going to say "enable rigidbody interpolation for player" and this is true, it fixes the problem! until you meet another rigidbody... Interpolation makes movement smooth, so when player hit/push another rigidbody, then it will appear super laggy, because it is not interpolated. This is also visible when we watch projectiles or any other moving body.
    Ok, then I can enable interpolation everywhere! however everyone says not to do that, even documentation.
    Let's go another approach - I have seen thread about updating camera in fixed update, it would cap the frame rate to 50, but fine, let's try it. At first sight it's nice, player is not lagging, everything around is moving smooth, but when player moves, then everything else (background) is not smooth.
    Some other people try increasing the timestep, but I am not even gonna mention that as solution.

    Can someone explain what I am supposed to do to properly get rid of jittery movement in the game?
     
    Exogenex likes this.
  2. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,425
    I always wondered why people state the year. ;)

    It's good that you're hesitant on making arbitrary changes based upon "what you heard" or the common responses you see. All too often we see posts where a user states that they changed a whole bunch of unrelated properties and we're left with a complete unknown guessed state.

    This isn't true at all. Interpolation is NOT a physics thing at all, it's a rendering thing only. We maintain a previous and current body pose, we interpolate the position and slerp the rotation. This happens and it doesn't matter what the previous or current body poses are. Was it moving in constant motion along a single axis? Was it moving then hit something and moved in another direction? Doesn't matter, for this 1/50th second it went from position A to position B and if your framerate is higher than 50fps then you'll get an intermediate frame rendered but this'll be because the Transform is changing only.

    This is why you should always be aware that the Transform.position != Rigidbody.position when interpolating. Rigidbody.position will always update when the simulation runs, the Transform won't match that and will update per-frame if interpolation/extrapolation is on; in this case, the Transform will be historic and be behind the current Rigidbody.position moving towards the current position.

    I'm going to assume your code is correct here. I'm also going to assume you know the following but others read this stuff too.

    The problem with terms like "lagging" and "smooth" are that they do NOT describe a specific problem and are highly subjective. I'm not saying this is what you're saying here but some people say it's "smooth" when the FPS is high and not smooth when the FPS is lower. Some people refer to their monitor rise/fall times as "laggy" and it's a device issue.

    Something you didn't mention was if you get the same in a build (outside of the Editor). The editor has a whole lot of stuff going on in the background. You can reduce this by going game-scene fullscreen so it's not drawing but it won't get rid of it.

    If you can identify when it happens, check for GC stuff happening or even you're seeing multiple back-to-back FixedUpdate because Unity is trying to catch-up to game time because something overspent on time the last frame or so.

    Having a Camera follow a single Rigidbody smoothly in a new empty project is something you should do (assuming you've not already) to verify the most minimal case. Fighting problems inside a working project can be infuriating only to find that you forgot you were doing X. Do it in a new empty project. Again, not saying you are, just food for though.

    The main thing is to split the problem in half first. Is the Transform of the GameObject that the Rigidbody is on smooth? If not, forget the camera. If it is then moving the Camera Transform without tracking using a simple linear movement. Is that smooth under all your play conditions? If not then it's likely the camera code it Editor just causing hiccups etc.

    It sounds like you're combining the two correctly by only updating the Camera late in the rendering or at least only after "Update" because at that point, the Transform interpolation has completed.
     
    Kasperrr, Exogenex and Qriva like this.
  3. AlTheSlacker

    AlTheSlacker

    Joined:
    Jun 12, 2017
    Posts:
    326
    Can I just add that, having been down this road, it is well worth taking a look at CineMachine.
     
    MelvMay likes this.
  4. Edy

    Edy

    Joined:
    Jun 3, 2010
    Posts:
    2,508
    That's it. I can't see any reason not to do that. Indeed, my recommendation from the experience is to just enable interpolation in all rigidbodies, or at least in those that are evidently moving near the camera. Otherwise, non-interpolated rigidbodies will stutter. Cameras should be moved in LateUpdate to provide smooth visuals. That's how physics (fixed rate) vs. visuals (varying, unpredictable frame rate) work. I can't see anything new to reinvent or reconsider here.

    Unity documentation is not always 100% exact, and in some cases (very rare though) it may be years behind the actual Unity versions. The page for Rigidbody.interpolation states a recomendation in this matter, but it doesn't provide any actual reasons or logic behind it (performance? hassle of enabling the option through every rigidbody?).

    I've even implemented interpolation myself in some visual elements that depend on physics (i.e. steering wheel rotation in cars). The impact is negligible: just storing an additional pose and performing a Lerp between them on each visual frame.

    As an additional note, Unity versions from 5.0 to 2020.1 have a significant flaw in Time.deltaTime that may cause sporadic visual stutter even in absolutely correct setups and very powerful machines. An in-depth description of this problem can be found here. It was fixed in Unity 2020.2, as described in this Unity blog post. So apart from enabling interpolation in all rigidbodies, I also recommend using Unity 2020 LTS or above to achieve perfectly smooth visual motion.
     
    Last edited: Mar 12, 2022
    EfremenkovAlex, Exogenex and Qriva like this.
  5. Qriva

    Qriva

    Joined:
    Jun 30, 2019
    Posts:
    1,306
    Oh my, thank you everyone for replies!
    Me too haha :D Well, it is known problem and I can find posts 10+ years old mentioning it, so I felt like "I don't believe I can't find solution to this", but let's move to the topic:
    Sorry, I wrote it in wrong way. To clarify, I know interpolation is only visual thing and they are desynchronized for update part. In this case jitter happens, because one player is interpolated and the other is updated 'less often' compared to player.
    It happens in build too and the project is quite new and empty, actually there is almost nothing on the scene.
    In my case this is not editor thing, but pure nature of how unity works.
    Just to be clear for someone else who might read this in the future, there are multiple other reasons for stutters and what @MelvMay wrote is very valid way to search the problem, however if I am not mistaken, these things can't fix my problem. I think I am going to prepare super simple example today.
    Actually before I started this thread I checked cinemachine to be sure I am sane. I made super simple setup and even tried different update modes in brain and problem is exactly the same as always.
    This is the closest viable solution currently in my mind. I made simple performance test and it looks like interpolation is not so expensive (most likely multi threaded). However enabling it to every single rigidbody still might be overkill.
    My current version 2021.2 :rolleyes:
     
  6. Qriva

    Qriva

    Joined:
    Jun 30, 2019
    Posts:
    1,306
    @MelvMay Here we go, super simple example of interpolation case:

    1. Create new scene
    2. Add flat plane with scale at least (5,1,5) in world origin - this will be our ground.
    3. Create empty gameobject with name 'CameraController' and put Camera as it's child, then add following script on parent (CameraController):
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class CameraController : MonoBehaviour
    4. {
    5.     [Header("Camera")]
    6.     public int targetFramerate = 60;
    7.     public Camera cam;
    8.     public float distance = 10f;
    9.     [Range(0f, 90f)]
    10.     public float angle = 45f;
    11.     [Range(-180f, 180f)]
    12.     public float rotation = 0f;
    13.  
    14.     [Header("Movement")]
    15.     public Transform followTarget;
    16.     [Range(0f, 1f)]
    17.     public float followSmoothTime = 0.2f;
    18.  
    19.     [SerializeField]
    20.     [HideInInspector]
    21.     private Quaternion cameraRotation = Quaternion.identity;
    22.     [SerializeField]
    23.     [HideInInspector]
    24.     private Vector3 cameraPosition = Vector3.zero;
    25.     private Vector3 velocity = Vector3.zero;
    26.  
    27.     private void Awake()
    28.     {
    29.         // Setup framerate
    30.         Application.targetFrameRate = targetFramerate;
    31.     }
    32.  
    33.     void Reset()
    34.     {
    35.         cam = GetComponentInChildren<Camera>();
    36.         OnValidate();
    37.     }
    38.  
    39.     void OnValidate()
    40.     {
    41.         if (distance <= 0)
    42.         {
    43.             distance = 0;
    44.         }
    45.         if (cam == null || cam.transform.parent != transform)
    46.         {
    47.             cam = null;
    48.             Debug.LogWarning("Camera object is missing or it is not child of this gameobject.");
    49.             return;
    50.         }
    51.         // Camera position
    52.         cameraRotation = cam.transform.localRotation = Quaternion.Euler(angle, rotation, 0);
    53.         cameraPosition = cam.transform.localPosition = cam.transform.localRotation * new Vector3(0f, 0f, -distance);
    54.     }
    55.  
    56.     void LateUpdate()
    57.     {
    58.         /* Movement */
    59.         if (followTarget != null)
    60.         {
    61.             // Move camera
    62.             transform.position = Vector3.SmoothDamp(
    63.                 transform.position,
    64.                 followTarget.position,
    65.                 ref velocity,
    66.                 followSmoothTime,
    67.                 Mathf.Infinity,
    68.                 Time.unscaledDeltaTime
    69.             );
    70.         }
    71.     }
    72. }
    73.  
    4. Add cube just above the ground for the player and add following component:
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. [RequireComponent(typeof(Collider))]
    4. [RequireComponent(typeof(Rigidbody))]
    5. public class PlayerController : MonoBehaviour
    6. {
    7.     private Rigidbody body;
    8.     public float speed = 10f;
    9.     public float acceleration = 30f;
    10.     private Vector3 input;
    11.  
    12.     void Awake()
    13.     {
    14.         // Rigidbody setup
    15.         body = GetComponent<Rigidbody>();
    16.         body.constraints = RigidbodyConstraints.FreezeRotation;
    17.         // Bonus Collider setup for better movement
    18.         PhysicMaterial physicMaterial = new PhysicMaterial();
    19.         physicMaterial.staticFriction = 0f;
    20.         physicMaterial.dynamicFriction = 0f;
    21.         physicMaterial.frictionCombine = PhysicMaterialCombine.Minimum;
    22.         Collider collider = GetComponent<Collider>();
    23.         collider.material = physicMaterial;
    24.     }
    25.  
    26.     void Update()
    27.     {
    28.         input = new Vector3(Input.GetAxis("Horizontal"), 0f, Input.GetAxis("Vertical"));
    29.     }
    30.  
    31.     void FixedUpdate()
    32.     {
    33.         body.velocity = Vector3.MoveTowards(body.velocity, input * speed, acceleration * Time.deltaTime);
    34.     }
    35. }
    36.  
    5. Add second cube next to player and add Rigidbody to it.
    6. Enter playmode and move around - as expected player is stuttering.
    7. Enable Rigidbody interpolation for player and enter playmode - everything is perfectly smooth, but if you try to push the second cube there will be very visible jittering of the other cube or any other potential rigidbody movin around.
     
  7. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,425
    Because the other cube is visually only updating during the simulation step, it does not have interpolation on.

    This does not affect the camera though if it is tracking the Transform.position of the PlayerController so I don't follow.

    Doesn't all the above show, that when interpolation is off then you don't get interpolation for that body? The only "smooth" movement is on the Rigidbody you turned interpolation on.
     
  8. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,425
    To add as a test, if you turn off Physics.AutoSimulation in 3D and run the simulation per-frame to verify all the per-frame actions are working okay you should always get smooth motion. Obviously this is running the physics at a variable time-step but it should remove any "other" things from why it might not be smooth.

    For instance, turn off interpolation on any Rigidbody and add this script to any GameObject in the scene.
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class SimulationManager : MonoBehaviour
    4. {
    5.     bool oldAutoSimulation;
    6.  
    7.     private void OnEnable()
    8.     {
    9.         oldAutoSimulation = Physics.autoSimulation;
    10.         Physics.autoSimulation = false;
    11.     }
    12.  
    13.     private void OnDisable()
    14.     {
    15.         Physics.autoSimulation = oldAutoSimulation;
    16.     }
    17.  
    18.     void Update()
    19.     {
    20.         Physics.Simulate(Time.deltaTime);
    21.     }
    22. }
    23.  
     
  9. Qriva

    Qriva

    Joined:
    Jun 30, 2019
    Posts:
    1,306
    The main goal is to get smooth looking game, so my point is that whatever it's camera movement stuttering or objects in the world, the result is bad. Fixing jitter of one object (camera in this case) is quite easy, but the point is to get whole game (other objects) running smooth too, and not just move the source of the problem somewhere else.

    Example above is simplification of how my setup works, so my question is: what am I supposed to do to make it run smooth? Shall I just enable interpolation everywhere as viable or only solution?
     
  10. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,425
    To be clear here, you started by asking about jitter/stutter/lag however that is often used to describe unexpected behaviour. Now you're talking about something completely different and expected!

    If your "smooth" standard is (say) 60Hz frame-rate and you're running something else (Physics) at 50Hz then you have to expect that the physics isn't going to be "smooth" unless you do something to mitigate it such as running the physics closer to the frame-rate or "smooth" frequency or interpolation.

    Not everywhere, on everything that you need to be "smooth", yes. Why is this a problem though? It shouldn't be unexpected. It's essentially a form of aliasing. :)

    For 2D this is why I'm implementing sub-stepping so you can have sub-stepped physics per-frame but still at a (smaller) fixed-rate interval.
     
    Qriva likes this.
  11. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,425
    Initially, try setting the Project Settings > Time > Fixed Timstep to 1/60 and use VSync, assuming you'll get more-or-less a steady 60fps.

    0.01666667 (60 fps)
    0.02 (50 fps) - Default.

    This doesn't really prove anything, only that you're running physics in-sync with rendering albeit at a fixed frequency so you don't loose so much determinism.
     
    Exogenex likes this.
  12. Qriva

    Qriva

    Joined:
    Jun 30, 2019
    Posts:
    1,306
    Hm, I am not native english, so it might be that I mixed it a bit, but for sure I meant something undesirable.
    Anyway there are many games made in unity and (at least at first sight) they don't have problems like that, so it made me wonder how is that possible and feeling that I am doing something horribly wrong, and this is the reason why I started this thread.
    This is possibly what I came for! The thing is I don't remember any sources saying it is completely fine to do that and "definitely go for it" - you and @Edy are first (although I don't have the whole internet in my head).
    Solution with changing fixed step to 60 per second is probably the other possible way, however I guess performance will vary per project, so it should be measured.

    Anyway thank you all for the help and I can consider this as solved, unless there is anything else that can be added.
     
    Exogenex and Edy like this.
  13. Edy

    Edy

    Joined:
    Jun 3, 2010
    Posts:
    2,508
    Most likely, if you have so many Rigidbodies in the scene that interpolation could become an issue, then you'll surely already have other more heavy performance issues to worry about.

    If it depended on me, I'd just have Rigidbody.interpolation enabled by default and a note in the documentation like "disable in case of performance issues". It would avoid *a lot* of headaches.

    I don't think so. You can't assume the visual update rate to be 60 Hz. Vsync might be disabled in the driver. Or forced to something else, like 144 Hz. Even if it's 60 Hz, it might be 59 in reality so you'll have 1 stutter per second.
     
    Qriva likes this.
  14. Qriva

    Qriva

    Joined:
    Jun 30, 2019
    Posts:
    1,306
    One per second is not perfect, but for sure more acceptable than 10, but I think it would not be a problem anyways, because it should be rare for physics to be "behind" rendering, however if vsync and framerate can be enforced by other settings or device, then it does not sound as good anymore.
     
    Exogenex and Edy like this.
  15. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,425
    Just a tiny extra info here: With interpolation, rendering is always "behind" the actual body position because it is interpolating from the last position/rotation to the current position/rotation. Interpolation is therefore historic and is therefore always behind. This is the opposite of extrapolation which is predictive because it's based upon the current position and velocity projected into the future.

    It's also why it's critical to not use the Transform as the authority on position/rotation but instead the Rigidbody(2D) position/rotation because when using Interpolation or Extrapolation they are not the same thing.
     
    Qriva likes this.
  16. AndrewSpalato

    AndrewSpalato

    Joined:
    Sep 2, 2017
    Posts:
    13
    Is there any way to have these features at the same time?

    - A networked physics based or character controller player object.
    - A camera system which follows the local player without jitter.
    - Connected clients do not experience jitter on themselves or when looking at other players. (Ignoring server or other networking jitter).
    - No jittering even if Time.fixedDeltaTime = 0.02f and the screen's refresh rate is above 60Hz (144Hz or 165Hz)

    Please let me know if there is an asset that does this, I'd be very happy to buy it if it does these things.
    I know Unity has their own templates but are either not networked friendly or do not support higher refresh rates.
     
  17. tomkail_betterup

    tomkail_betterup

    Joined:
    Nov 17, 2021
    Posts:
    106
    Since this is the thread people get redirected to when searching "rigidbody camera stutter" or similar; the TL;DR is that if moving your camera in Update you need to set Interpolate on any Rigidbody that is moved in view of the camera.

    An alternative is to move the camera in FixedUpdate instead.

    Also, for what it's worth, enabling Interpolation by default seems sensible to me; it's nice when things behave as a layman would expect out of the box.
     
    Edy likes this.
  18. criwe

    criwe

    Joined:
    Apr 16, 2013
    Posts:
    5
    I fixed my camera jittering/tearing problems by NOT rotating my player capsule at all, only the camera, and have the camera as the vector to follow, jittering was completely gone after that.
     
  19. EfremenkovAlex

    EfremenkovAlex

    Joined:
    Mar 13, 2017
    Posts:
    4
    Thanks man! That's the reason why I always try to read all messages in the topic
     
Thread Status:
Not open for further replies.