Search Unity

Background jitter with v-sync off in standalone build (high fps/300+ fps)

Discussion in 'Editor & General Support' started by SirWhiteBeard, Feb 12, 2020.

  1. SirWhiteBeard

    SirWhiteBeard

    Joined:
    Feb 21, 2014
    Posts:
    66
    Hi there,

    So the last 2 months I had been reworking my PlayerController to be rigidbody based for the sake of having more interactions with rigidbodies i.e. pushing objects. This went all pretty well, until I started testing my game in a standalone build. I immediately noticed strange jitter/stutter in the background (the player runs smooth) when v-sync is turned off (with v-sync is turned off my computer runs the game at 300fps+). At first I thought it had something to do with Cinemachine, I'm using CinemachineFreeLook, but to be honest as of now I'm unsure if Cinemachine is what's causing the jitter/stutter. Something worth noting is that the jitter/stutter is not constant, while the fps seems constant the jitter/stutter might go away for like 20 seconds and come back after. I have obviously done alot of research the past week and have been reading plenty of posts about FixedUpdate vs. Update, but unfortunately without any luck.

    I tried to add as much information as possible to this post to give a good understanding of my project, but first the stuff I already tried;

    • Changing the Fixed Timestep from 0,02 to 0,01. (This kind of works, but I would like to prevent changing the Fixed Timestep)
      Turning on interpolate/extrapolate. (Now there is jitter with both v-sync turned on and off)
    • Changing the CinemachineBrain Update Method to FixedUpdate/LateUpdate instead of SmartUpdate.
    • Deconstructing the scene and removing some heavy on performance stuff like Volumetric Lighting and Planar Reflections.
    • Creating a really barebones PlayerController with only the code to move the player.
    • Removing the library folder of my project. (Read this somewhere online, but no luck)
    • Turning of my Xbox One controller, to make sure there is only one input in use.
    • Swithed the building from Windowed Fullscreen to Exclusive Fullscreen.
    • Turned off one monitor while testing.
    • Move all my code to FixedUpdate.
    • Added these 3 scripts and changed the Script Execution Order (Couldn't get this to work)
    • Put the input logic in FixedUpdate.

    Below you can see both my movement scripts, one is part of my actual PlayerController, while the other was the one I wrote for the barebones Player.

    Code (CSharp):
    1. void FixedUpdate() {
    2.  
    3.     _isGrounded = Physics.CheckSphere(_groundChecker.position, GroundDistance, Ground, QueryTriggerInteraction.Ignore);
    4.  
    5.     _inputs = Vector3.zero;
    6.     _inputs.x = Input.GetAxis("Horizontal");
    7.     _inputs.z = Input.GetAxis("Vertical");
    8.     _inputs.Normalize();
    9.  
    10.     if (_inputs != Vector3.zero)
    11.         transform.forward = _inputs;
    12.         _body.MovePosition(_body.position + _inputs * Speed * Time.fixedDeltaTime);
    13. }
    This is the script I wrote for the barebones player controller.

    Code (CSharp):
    1. void FixedUpdate() {
    2.     if (pushControllerScr._pushBlocker) {
    3.         input = Vector3.zero;
    4.     } else {
    5.         input = new Vector3(InputManager.MainHorizontal(), 0, InputManager.MainVertical());
    6.     }
    7.     RunAxis();
    8.     inputDir = input.normalized;
    9.     inputDir.Normalize();
    10.  
    11.     // Makes the player able to move, while not landing or being dead
    12.     if (!landState && !climbManagerScr.isClimbing) {
    13.         Move(inputDir, running);
    14.     }
    15. }
    16.  
    17. void Move(Vector3 inputDir, bool running) {
    18.     if (inputDir != Vector3.zero) {
    19.         float targetRotation = Mathf.Atan2(inputDir.x, inputDir.z) * Mathf.Rad2Deg + cameraT.eulerAngles.y;
    20.  
    21.         transform.eulerAngles = Vector3.up * Mathf.SmoothDampAngle(transform.eulerAngles.y, targetRotation, ref turnSmoothVelocity, GetModifiedSmoothTime(turnSmoothTime));
    22.     }
    23.  
    24.     float targetSpeed = ((running) ? runSpeed : movementSpeed) * inputDir.magnitude;
    25.     stateInfoBaseLayer = _anim.GetCurrentAnimatorStateInfo(0);
    26.    
    27.     if (stateInfoBaseLayer.IsTag("Climbing") || stateInfoBaseLayer.IsTag("Standing")) {
    28.         currentSpeed = 0f;
    29.     } else {
    30.         currentSpeed = Mathf.SmoothDamp(currentSpeed, targetSpeed, ref speedSmoothVelocity, GetModifiedSmoothTime(speedSmoothTime));
    31.     }
    32.  
    33.     if (stateInfoBaseLayer.IsTag("AirIdle") && currentSpeed > walkSpeed) {
    34.         currentSpeed = walkSpeed;
    35.     }
    36.  
    37.     velocity = transform.forward * currentSpeed + Vector3.up * _rb.velocity.y;
    38.  
    39.     if (_capsuleCol.enabled) {
    40.         _rb.MovePosition(_rb.position + velocity * Time.deltaTime);
    41.     }
    42. }
    This is what I actually use in my PlayerController Script

    Below you can see some footage of the jitter/stutter. It was quite hard to capture it, since using any recording software like OBS dropped the fps in my game to a point where the jitter/stutter was disappearing. Sorry for the phone recording, but this was honestly the only way.

    Footage of my game showcasing the jitter/stutter especially noticable around the yellow ship.

    And finally here's an album with some relevant settings in the inspector. Imgur Album
     
    Last edited: Feb 12, 2020
  2. Madgvox

    Madgvox

    Joined:
    Apr 13, 2014
    Posts:
    1,317
    This may seem like an obtuse suggestion, but have you considered capping your game to like 60 or 120fps? Not much practical reason to hit 300 fps, and if the jittering disappears at lower frame rates then why not just use lower frame rates?
     
  3. SirWhiteBeard

    SirWhiteBeard

    Joined:
    Feb 21, 2014
    Posts:
    66
    This is a great question and you are correct 300fps isn't necessary. I would like to keep te option for users though so they are able to turn off v-sync. Also for this reason I simply have a feeling something is going wrong with my code/settings/project, since this wasn't happening when using a CharacterController component.
     
  4. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    There could be lots of jitter reasons. Few suggestions:
    - Poll inputs in Update, not in FixedUpdate.
    - Set your Cinemachine update to LateUpdate. SmartUpdate is kinda total trash.
    - Don't modify transform directly if you've got rigidbody on it.
    (Use MoveRotation instead of eulerAngles, same goes for transform.forward);
    - No need to enable vSync to limit fps. Use Application.targetFrameRate for that.
    - Run a profiler. Maybe there's something else eating ms?
    - Use Time.fixedDeltaTime in FixedUpdate instead of Time.deltaTime
    (even though its swapped internally to the Time.fixedDeltaTime)
    - Try parenting camera directly to the player instead of using Cinemachine, see if jitter persists.
    - Make sure you're not running into this:
    https://forum.unity.com/threads/time-deltatime-not-constant-vsync-camerafollow-and-jitter.430339/
     
    Madgvox likes this.
  5. SirWhiteBeard

    SirWhiteBeard

    Joined:
    Feb 21, 2014
    Posts:
    66
    Yeah I'll go back to this for sure, I had it in Update initially but someone advised me to put it in FixedUpdate as it shouldn't matter.

    I have changed these settings alot of times, but unfortunately without any luck. Will check these settings again when I have looked at one of the points from below.

    This is something I didn't know and didn't try, and in all honesty I'm not sure how to script this. Could you show me an example of how this would look within my own script? And is there an equivalent of transform.forward for MovePosition? This does sound promising!

    As of now I don't have any issues using v-sync, the jitter/stutter only occurs when v-sync is turned off and my game runs 320fps ish in a standalone build.

    Yeah this is a tough one, since profiling my standalone game drops the fps to about 200fps where the jitter/stutter isn't present anymore.

    I will make sure to do this.

    I have tried this, but it couldn't see any jitter like the severe jitter in the video attached to this post.

    My ms seem to be very consistant with both v-sync on aswel as off. I think this is what you're getting at?

    Thanks alot for your input thusfar! I hope you could help me out a little bit with the MoveRotation and transform.forward part.
     
  6. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    Specify rotation as world quaternion rotation. (Basically its identical to assigning transform.rotation but to rigidbody instead)

    So in theory transform.forward assignment should be identical to the rigidbody.MoveRotation(Quaternion.LookRotation(forward, up));
     
  7. SirWhiteBeard

    SirWhiteBeard

    Joined:
    Feb 21, 2014
    Posts:
    66
    I'm having alot of trouble converting it to my current rotation. since it has a couple of more variables:

    Code (CSharp):
    1. float targetRotation = Mathf.Atan2(inputDir.x, inputDir.z) * Mathf.Rad2Deg + cameraT.eulerAngles.y;
    2.         transform.eulerAngles = Vector3.up * Mathf.SmoothDampAngle(transform.eulerAngles.y, targetRotation, ref turnSmoothVelocity, GetModifiedSmoothTime(turnSmoothTime));
    The same goes for transform.forward, which I currently use here for my custom velocity:

    Code (CSharp):
    1. velocity = transform.forward * currentSpeed + Vector3.up * _rb.velocity.y;
    I hope you can show me how this should work and should be translated.
     
  8. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    Target rotation is your Y angle axis only (I think), so it could be represented as:
    Code (CSharp):
    1. Quaternion.AngleAxis(Mathf.Atan2(inputDir.x, inputDir.z) * Mathf.Rad2Deg + cameraT.eulerAngles.y, Vector3.up)
    What it basically does is creates a quaternion that has rotation on the Y axis only, with the specified angle.

    Then use https://docs.unity3d.com/ScriptReference/Quaternion.RotateTowards.html to interpolate between rotation values of the rigidbody. And feed rigidbody end values via MoveRotation afterwards.

    Note that reading transform values is fine, writing may screw up physics.


    On the side note, looking at that cameraT, try removing that rotation. Test if it prevents the jitter.

    I've had a situation once where transform was driven by the camera, and the camera was driven by transform.
    This caused jittering due to infinite feedback loop. It might not be the case, but its worth checking.
     
    Last edited: Feb 14, 2020
  9. SirWhiteBeard

    SirWhiteBeard

    Joined:
    Feb 21, 2014
    Posts:
    66
    So my code would look like this?

    Code (CSharp):
    1. float targetRotation = Quaternion.AngleAxis(Mathf.Atan2(inputDir.x, inputDir.z) * Mathf.Rad2Deg + cameraT.eulerAngles.y, Vector3.up);
    2. _rb.MoveRotation = Vector3.up * Mathf.SmoothDampAngle(transform.eulerAngles.y, targetRotation, ref turnSmoothVelocity, GetModifiedSmoothTime(turnSmoothTime));
    I'm not the best coder to be honest. So I really appreciate the help.

    And what about the transform.forward in:

    Code (CSharp):
    1. velocity = transform.forward * currentSpeed + Vector3.up * _rb.velocity.y;
     
  10. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    Quaternion.AngleAxis returns Quaternion. Not float.
    Rigidbody.MoveRotation is a method, so its .MoveRotation(*yourQuaternion*)

    transform.forward there is fine, since its not writing anything to the transform, just reading the direction.

    Read manual. There's some examples on how to use Quaternion.AngleAxis and other Quaternion methods.
     
  11. SirWhiteBeard

    SirWhiteBeard

    Joined:
    Feb 21, 2014
    Posts:
    66
    Yeah I'm always having a hard time working with quaternions as I'm someone with a background in visual design. Thanks for the help though!