Search Unity

  1. Good news ✨ We have more Unite Now videos available for you to watch on-demand! Come check them out and ask our experts any questions!
    Dismiss Notice

Camera Quaternion.Slerp jitter?

Discussion in 'Scripting' started by skeptika, Nov 5, 2018.

  1. skeptika

    skeptika

    Joined:
    Dec 31, 2011
    Posts:
    107
    I really can't see why on earth this jitters. This is an extremely simplistic "mouse look" camera, with the addition of spawning a First Person Container object (that contains a gun as a child), that matches the rotation of the Camera. To keep this simple, there is zero movement, just rotation.

    Code (csharp):
    1. [Header("Mouse Look")]
    2.     public float mouseSensitivityX = 1.5f;
    3.     public float mouseSensitivityY = 1.5f;
    4.  
    5.     [Header("First Person")]
    6.     public GameObject firstPersonContainerPrefab; //Blank GameObject Container with Gun as a Child
    7.  
    8.     //Private
    9.     private GameObject firstPersonContainer;  //Instantiated FPS Container where gun is a Child of the Container
    10.     private float rotationX;
    11.     private float rotationY;
    12.     private Quaternion initialRotation;
    13.  
    14.  
    15.     void Start()
    16.     {
    17.         //Cursor
    18.         Cursor.lockState = CursorLockMode.Locked;
    19.         Cursor.visible = false;
    20.  
    21.         //Initialize
    22.         if (firstPersonContainerPrefab != null)
    23.         {
    24.             firstPersonContainer = GameObject.Instantiate<GameObject>(firstPersonContainerPrefab);
    25.             firstPersonContainer.transform.position = transform.position;
    26.         }
    27.  
    28.         initialRotation = transform.rotation;
    29.     }
    30.  
    31.  
    32.     void Update()
    33.     {
    34.         //Rotation based on Input
    35.         rotationX += Input.GetAxisRaw("Mouse X") * mouseSensitivityX;
    36.         rotationY += Input.GetAxisRaw("Mouse Y") * mouseSensitivityY;
    37.  
    38.         //Rotations
    39.         Quaternion xQuaternion = Quaternion.AngleAxis(rotationX, Vector3.up);
    40.         Quaternion yQuaternion = Quaternion.AngleAxis(rotationY, Vector3.left);
    41.  
    42.         //Set Camera X and Y Rotation
    43.         transform.rotation = initialRotation * xQuaternion * yQuaternion;
    44.  
    45.         //Set FPS Container Rotation
    46.         //firstPersonContainer.transform.rotation = transform.rotation; //Works, No Jitter
    47.         firstPersonContainer.transform.rotation = Quaternion.Slerp(firstPersonContainer.transform.rotation, transform.rotation, Time.deltaTime * 50f); //Jitters
    48.     }
    If I simply set the rotation of the FPS Container to match the Camera, no problem, no jitter (Line 46). If I try to Slerp (or Lerp for that matter) between it's existing rotation and the Camera's rotation, significant jitter occurs (Line 47). Changing it to LateUpdate etc doesn't change this.

    Why does this jitter, it's on the same Update loop?! I'm on Unity 2018.3, but I tried this on other versions and got the same result.
     
  2. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    7,542
    Also, yo time multipler seams very high. Try lower it 0.1f, 0.5f, 1, 5 etc., to see if improves.
    Then check again, if the same happens, if you use LateUpdate, instead Update.
     
  3. skeptika

    skeptika

    Joined:
    Dec 31, 2011
    Posts:
    107
    Like I said, LateUpdate doesn't change anything. It shouldn't jitter, regardless of the # (I set it high to make the jitter very apparent if anyone wanted to cut and paste and see for themselves).

    I've got a potential hypothesis on what's happening though. I *think* the jitter is because of how Unity applies transforms to child objects. My above code basically doesn't produce jitter (it shouldn't), but the reason I see jitter, is because the child object (gun) is having it's transform changed to match the parent (the container) in a separate part of the loop, causing the transform mismatch and thus jitter. That's what I'm investigating at least, it's the only thing that marginally makes sense to me. The above code really shouldn't produce any jitter at all.
     
  4. Tarball

    Tarball

    Joined:
    Oct 16, 2016
    Posts:
    165
    What do you mean LateUpdate isn't changing anything? It should work fine if you handle your input in Update and then move the camera in LateUpdate. Camera movement in Update is exactly where jittering comes from in my experience.
     
    angrypenguin likes this.
  5. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    13,126
    I would expect that to jitter. Time.deltaTime fluctuates. You're multiplying that fluctuation by 50.

    What is the slerp for, smoothing? As it is, if your frame rate drops below 50fps your interpolation factor will hit 1, nullifying the smoothing. As your framerate increases the smoothing effect will get stronger. I know the 50 is to make the jitter obvious, but changing the number to make it less obvious doesn't change the underlying issue.

    Also... are there any Rigidbodies anywhere in the hierarchy? The update/render loop runs at a different rate to the physics loop, so any Ridibdody objects may be getting their positions updated out of sync with non-physics objects. Edit: If there's no jitter without the slerp, this isn't the issue.
     
    Antypodish likes this.
  6. skeptika

    skeptika

    Joined:
    Dec 31, 2011
    Posts:
    107
    I mean that 1) running the whole thing in LateUpdate or 2) putting Input (in Update) and Rotation (in LateUpdate), neither one works. The jitter is still present. For example, option 2:

    Code (csharp):
    1.  
    2.     void Update()
    3.     {
    4.         //Rotation based on Input
    5.         rotationX += Input.GetAxisRaw("Mouse X") * mouseSensitivityX;
    6.         rotationY += Input.GetAxisRaw("Mouse Y") * mouseSensitivityY;
    7.     }
    8.  
    9.     void LateUpdate()
    10.     {
    11.         //Rotations
    12.         Quaternion xQuaternion = Quaternion.AngleAxis(rotationX, Vector3.up);
    13.         Quaternion yQuaternion = Quaternion.AngleAxis(rotationY, Vector3.left);
    14.  
    15.         //Set Camera X and Y Rotation
    16.         transform.rotation = initialRotation * xQuaternion * yQuaternion;
    17.  
    18.         //Set FPS Container Rotation
    19.         //firstPersonContainer.transform.rotation = transform.rotation; //Works, No Jitter
    20.         firstPersonContainer.transform.rotation = Quaternion.Slerp(firstPersonContainer.transform.rotation, transform.rotation, Time.deltaTime * 50f); //Jitters
    21.     }
    Separating Input to Update, and the Rotations to LateUpdate still results in jitter. Did you mean something else?
     
  7. skeptika

    skeptika

    Joined:
    Dec 31, 2011
    Posts:
    107
    Help me understand what you mean, Unity itself uses Time.deltaTime in Quaternion.Slerp docs: https://docs.unity3d.com/ScriptReference/Quaternion.Slerp.html

    The point behind using Time.deltaTime is to make it frame rate independent, which seems at odds with what you said above if I understand you correctly. Even if you remove it, put in a value like 0.5f, it'll still jitter. Yes it's for smoothing, in this example it gives a slight "delay" to the object matching rotation, which is commonly used in making "weapon sway" in the FPS genre.

    There are no rigidbodies, there is legitimately nothing other than a plain old Camera, and an empty GameObject container as mentioned in the above script. I specifically made the scene and example as simple as possible to get rid of other causes. Camera. GameObject with synced rotation to Camera. GameObject has a child (a simple cube). Child jitters as you rotate. That's all it is.
     
    Last edited: Nov 6, 2018
  8. Tarball

    Tarball

    Joined:
    Oct 16, 2016
    Posts:
    165
    That's what I meant. But, why are you multiplying by 50? Won't that give you big steps? Don't you want small steps?
     
    Antypodish likes this.
  9. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    7,542
    Exactly, that was my initial point to reduce it.
    As far I remember from my long time ago experiments, such high value can cause jittering.
    And as mentioned, seams pointless.
     
  10. skeptika

    skeptika

    Joined:
    Dec 31, 2011
    Posts:
    107
    50 Controls how quickly the rotation is smoothed. It's the opposite of what you're thinking. 50 makes it match the rotation more quickly, smaller values take longer to match the rotation.

    If you go to low values (closer to 1), it will be unusably slow in matching the rotation (several real world seconds to match rotation). 50 isn't really very extreme, it gives a nice reactive "sway" to the object following the cameras rotation.

    Large or small, the jitters still there. A small value will make it less obvious, but it's just masking the problem, not solving it.
     
    Last edited: Nov 6, 2018
  11. skeptika

    skeptika

    Joined:
    Dec 31, 2011
    Posts:
    107
    No, the value of smoothing doesn't cause jitter. A mismatch (desync) in transform updates does. This is why people often recommend LateUpdate for the camera, because you can be sure all transforms have already occurred in FixedUpdate and Update.

    Likely you just thought the jitter went away when you went to a low value, because it's giving it more time to "smooth" it out, but it's still there. This is an unfortunately common understanding people have, it's not solving jitter, it's just hiding it "better." When things are properly synced, however, there is no jitter and changing the smoothing value doesn't cause jitter, it simply defines how "quickly" the object is reaching its intended value.
     
    Last edited: Nov 6, 2018
  12. Tarball

    Tarball

    Joined:
    Oct 16, 2016
    Posts:
    165
    Well, you got me curious, since I'm waiting for someone to answer my question anyway, and I tried it out in a new scene with the following code:

    Code (CSharp):
    1. public class Slerp_Camera : MonoBehaviour
    2. {
    3.     private Quaternion initialRotation;
    4.     private float rotationX;
    5.     private float rotationY;
    6.     [Range(0.1f, 2f)]
    7.     [SerializeField] private float mouseSensitivityX;
    8.     [Range(0.1f, 2f)]
    9.     [SerializeField] private float mouseSensitivityY;
    10.  
    11.     private void Start()
    12.     {
    13.         rotationX = 0f;
    14.         rotationY = 0f;
    15.         initialRotation = gameObject.transform.rotation;
    16.     }
    17.     void Update()
    18.     {
    19.         //Rotation based on Input
    20.         rotationX += Input.GetAxisRaw("Mouse X") * mouseSensitivityX;
    21.         rotationY += Input.GetAxisRaw("Mouse Y") * mouseSensitivityY;
    22.     }
    23.  
    24.     void LateUpdate()
    25.     {
    26.         Quaternion xQuaternion = Quaternion.AngleAxis(rotationX, Vector3.up);
    27.         Quaternion yQuaternion = Quaternion.AngleAxis(rotationY, Vector3.left);
    28.  
    29.         transform.rotation = initialRotation * xQuaternion * yQuaternion;
    30.  
    31.         gameObject.transform.rotation = Quaternion.Slerp(gameObject.transform.rotation, transform.rotation, Time.deltaTime);
    32.     }
    33. }
    The camera was not attached to anything, and I didn't notice any noticeable jitter. Leaving the *50 in, there was a little jitter, but barely noticeable still. Maybe the problem is related to something else?
     
  13. skeptika

    skeptika

    Joined:
    Dec 31, 2011
    Posts:
    107
    As I said above, "a small value will make it less obvious, but it's just masking the problem, not solving it." It effectively gives it more iterations to smooth over, so it seems smoother, but all it's really doing is masking the inherent jitter. There really shouldn't be *any* inherent jitter to mask.

    The code is dead simple and the containers transform is done immediately after the cameras transform, within the exact same loop (Update). There shouldn't be any sync problems (where one transform is getting changed in a different loop). All the code is doing is:

    1. Now that we've rotated the camera via a mouselook
    Code (csharp):
    1. transform.rotation = initialRotation * xQuaternion * yQuaternion;
    2. Please rotate my dummy object container to match the camera's rotation
    Code (csharp):
    1. firstPersonContainer.transform.rotation = Quaternion.Slerp(firstPersonContainer.transform.rotation, transform.rotation, Time.deltaTime * 50f);
    3. The fact that it's a slerp, means that it should provide me a value between the dummy gameobject's existing rotation, and the camera rotation, hence you get the nice "delayed" rotation effect, where it follows the camera, but with a slight delay.
    4. How quickly that value is reached is dependent upon, in this case, 50f. But the value is just a speed (time) value.
    5. That resulting slerp value *should* be a smooth interpolation between the current rotation (gameobject) and the desired rotation (camera) based on the speed I give it (50f in this example).

    That's why I'm so perplexed by the jitter. It's in the same update loop, it's done directly after the camera rotation, and slerp should be providing me a nice smooth value. It's not. Or it IS, but that nice smooth value isn't being carried over to the child object (the gun child of the dummy gameobject). I'm clearly missing something (hence the post), but I'm utterly perplexed!!!! :)
     
    Last edited: Nov 6, 2018
  14. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    7,542
    Lets make few steps back.
    And answer us few questions, so we can try to help you.
    1. Why you choose value of 50? What you try to achieve with such high value. Why10, or 1, or 0.1 don't work for you?
    2. Are you moving objects very fast?
    3. What is position of your objects? Is it grater than 10k by any chance?
    4. Describe hierarchy of affected objects.
     
    angrypenguin likes this.
  15. Tarball

    Tarball

    Joined:
    Oct 16, 2016
    Posts:
    165
    I guess I should have read op before just jumping in like I did. Why do you want to rotate them separately if one is a child of the other? If I understand correctly, you're slerping one while not slerping the other, but both move the camera, so therein lies the problem. Simply remove the dependency parent/child to remove the problem. If that's not what you're talking about, then I have no idea what the problem is. It's entirely possible I'm misunderstanding everything you're saying, because I'm not sure what you're trying to accomplish.
     
  16. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    13,126
    In this case it does not make it frame rate independent.

    Raw mouse input is already frame rate independent. Mouse movement measures how far the mouse has moved on a particular axis since the last frame. If the polls become further apart (eg: you're polling once per frame and the frame rate drops) then more movement can occur between polls.

    To state it differently, mouse movement values represent a distance rather than a speed, so you do not have to multiply them by the time delta to make them frame rate independent.

    Separately to that... the documentation does not use Time.deltaTime the same way that you are using it. They have fixed start and end rotations and are changing their interpolation factor over several frames by adding Time.deltaTime to it. So over multiple frames their interpolation factor will be (for example) 0, 0.1, 0.2, 0.3... 0.9, 1.0. This will have the effect of animating the rotation from the start value to the end value over a duration of 1 second. This is framerate independent because the amount of movement will always be directly proportional to the length of the frame.

    You are changing your start and end points, and you are multiplying Time.deltaTime by a number and using the result directly as the interpolation factor. So each frame you have a new start and end rotation, then you are calculating some fraction based on the current frame time and moving the camera to a rotation between the two based on that fraction. The resulting movement is not proportional to the length of the frame. For what it's worth, despite not being framerate independent this seems to be a commonly used approach for smoothing.

    I did a quick search earlier for a framerate independent mouse smoothing algorithm. I didn't have much luck, but the principles mentioned here probably also apply. If I understand correctly, convert the distance into a speed, smooth the speed, then use that to calculate a new distance.
     
    Last edited: Nov 6, 2018
  17. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    13,126
    No, it's not a speed or time value, regardless of the name of the variable in the documentation. It's a fraction. Perhaps the Mathf.Lerp(...) documentation and example might help, as it's slightly more informative?

    That parameter says how far between the start and end values you want the returned value to be. 0 gives you the start value, 1 gives you the end value, 0.5 gives you a value exactly in the middle.

    You are passing in Time.deltaTime * 50. So, if your game is running at 50fps then Time.deltaTime is 0.02 and the result is (0.02 * 50 = 1). The function just returns the end rotation. In other words, no smoothing.

    If your game is running at 100fps then Time.deltaTime is 0.01 and the result is (0.01 * 50 = 0.5), so the function returns a value half way between the two provided rotations. In other words, really strong smoothing.

    Time.deltaTime isn't perfectly stable, because frames don't usually take exactly the same time to render. The fluctuations are small, but when you multiply them by 50 and use them in this fashion they could quite possibly become visible.
     
    Last edited: Nov 6, 2018
    Antypodish likes this.
  18. skeptika

    skeptika

    Joined:
    Dec 31, 2011
    Posts:
    107
    Thanks for the posts and examples angrypenguin. I'll go try a few things based on the feedback, I appreciate it!

    What I still find... odd, is that based on the above, I can remove the deltaTime fluctuations by either manually hard coding 0.5f (it was one of the first things I tried), or using something like fixedDeltaTime, and it's still jittery. The jitter doesn't seem to be resulting from me forcing the fraction to 1 or by a fluctuating fraction.

    I gather from what you wrote that the primary cause is that mouse input itself is inherently not smooth (accurate but not smooth)? What seems... odd about this conclusion is that the mouselook itself does not seem to have jitter. Yet a smoothed slerp value from it (with a consistent fraction) ... does?
     
    Last edited: Nov 6, 2018
  19. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    13,126
    I think that there are two different issues with your overall algorithm.Your use of Time.deltaTime is one of them. Your changing of the start and end values is, potentially, another.

    Rather than a slerp, have you considered a MoveTowards or RotateTowards or equivalent method? This would make it easier to smooth based on distance to the target point rather than some time-based factor. (This still isn't fully framerate independent, but it's pretty darn close. Basically, higher frame rates result in a closer approximation of whatever smoothing curve you're after.)

    Separately to that, using Time.fixedDeltaTime outside of FixedUpdate is likely to cause issues of its own, because it's a fixed time value that doesn't represent the duration of your frames. It is correct only for calculations done in FixedUpdate.

    If you think through it frame by frame, doing the calculations assuming each frame takes different times, I think you'll see what's going on, and it won't seem odd any more.

    This stuff is more complicated than it seems, and you need a thorough understanding of how different aspects of a simulation interact with time to get this kind of stuff right. I've played blockbuster titles that don't feel like they've got it right, and from the link I posted earlier you can see people claiming that even Valve have related issues. (I suspect that in many cases developers assume their game will play at a constant frame rate, and it does... until someone ports it to a new platform.)
     
unityunity