Cinemachine and pixel perfect camera jitter with rigidbody2d (classic)

I’ve been researching and ive seen a lot of posts about this, but so far no solution I’ve found works, the player jitters heavily when followed by a cinemachine camera as seen here (the player is moved via rigidbody 2D):

unknown_2025.01.17-11.12

I really wanna keep the pixel perfect in my game but i cannot find a solution to this, ive added the cinemachine pixel perfect component, ive adjusted every setting i can think of being the problem and it persists, here are the camer and cinemachine camera settings for aid (can’t add more than 1 embed so text it is):

pixel perfect camera:

  • assets per pixel unit: 32
  • ref resolution: 320x180
  • crop frame: none
  • grid snapping: upscale render texture

Cinemachine (whatever is not mentioned its default)

  • Damping: x1, y1, z1
  • Lookahead: on
  • Time: 0.4
  • Smoothing: 0
  • Cinemachine Pixel Perfect Component

do i need to make a following camera from scratch, do i scrap pixel perfect, or is there a secret third option

And the camera also has its companion pixel perfect component?

Turn grid snapping off if you don’t need it. This causes jerky motion, and I assume this gets pronounced the more lowres your game is. Imagine if you had only an 10x10 screen resolution, this would snap objects by quite a large amount ie 1920/10 by 1080/10 would cause up to 96x54 pixel snapping (half pixel size) in the most extrem case.

I noticed this sort of jerkiness with grid snapping enabled and it went away after disabling grid snapping.

Other than that, check if the rigidbody is interpolated (not sure if 2d bodies have that). Or change Project Settings - Time so that fixed timestep is 60 Hz (0.0166666) to see if that smoothes out the movement.

Yes they do, not much use if they didn’t. :wink:

You can also run the simulation per-frame (even using sub-stepping if you want) if your game allows for reduced determinism.

1 Like

Yes the camera has the pixel perfect component, i have "upscale render texture"instead of grid snapping, and ive tried disabling that and jerkiness persists, and rigidbody is interpolated, ill see abput the project settings

Ill see if i can do that

Make sure you’re not modifying the Transform as that isn’t how you change the position/rotation of a Rigidbody2D and it won’t support interpolation if you do it.

1 Like

Im pretty sure i havent done that but i cant check for sure atm, ill see

1 Like

Try changing the Update Method of the CinemachineBrain. This setting can sometimes cause jittering issues.

1 Like

If you mean the dropdown menu where i can choose between fixed update, late update, and smart update ive changed them around, its currently on fixed update and late update

pixel perfect + smooth camera = jitter

thats why none of the other threads you saw ever had a solution

if you want to remove the jitter you will have to remove either the smooth camera or the pixel perfect

Every other thread and video i saw is about specifically this issue with cinemachine and pixel perfect camera with the exact same jitter

That won’t work with interpolation enabled. You’ll get jitter all the time that way.

  • If no interpolation and using only the correct RB API to move the RB: use Fixed update
  • If interpolation and and using only the correct RB API to move the RB: use LateUpdate
  • If not using only the correct RB API to move the RB: jitter in all cases
2 Likes

Right now this is my code for the movement (the player is moving at all times, this is all in FixedUpdate)

    Vector2 movementDirection = transform.up * movementSpeed;
    rb2d.linearVelocity = movementDirection;

turns out i was using the transform instead of the rigidbody to move it, thats my bad, and on cinemachine ive changed it to both be late update
image

this is the least amount of jitter so far but still a bit noticeable,
unknown_2025.01.18-10.55

i dont know if anything else can be done?

That’s good. The remainder of the jitter looks pixelperfect-related to me. Does it go away if you disable pixelperfect?

1 Like

im afraid it does, is there any other way of doing this without cinemachine or the pixel perfect camera package? i would give it up if it wasnt for the fact that ive seen hundreds of games with this type of thing so i know theres gotta be a way of having this

If @Gregoryl’s assumption is correct that the remaining jittering is caused by pixel-perfect rendering,
you should be able to reduce it by using a component like “Cinemachine Follow” that directly controls the follow offset, writing your own equivalent processing for damping and lookahead, and finally making a “slight adjustment” to the follow offset.

The cause of the current jittering can likely be simplified as something like the following code:

Vector2 cameraPos = playerPos + followOffset;
Vector2Int playerPixelPos = new Vector2Int((int)(playerPos.x * pixelPerUnit), (int)(playerPos.y * pixelPerUnit));
Vector2Int cameraPixelPos = new Vector2Int((int)(cameraPos.x * pixelPerUnit), (int)(cameraPos.y * pixelPerUnit));
Vector2Int pixelOffset = cameraPixelPos - playerPixelPos;

Since the pixel positions of the camera and the player are calculated and truncated separately, the pixel offset value becomes unstable whenever the player’s position updates, even if the follow offset value remains unchanged. This instability causes jittering.
To address this, you can pass a pre-adjusted follow offset value that considers this behavior in advance, which will enhance stability.

followOffset.x = (int)(followOffset.x * pixelPerUnit) / (float)pixelPerUnit;
followOffset.y = (int)(followOffset.y * pixelPerUnit) / (float)pixelPerUnit;

This adjustment ensures that the final pixel offset snaps to specific integer values.
As a result, the player offset as seen from the camera center during the final rendering will have a stable pixel offset, and the jittering should be reduced.

1 Like

I see, im still very new to this so do you have any examples on how this code is properly applied?

Something like this.

using Unity.Cinemachine;
using UnityEngine;

namespace PixelPerfectTest
{
    [DefaultExecutionOrder(-90)] // Call before LateUpdate of Cinemachine
    public class FollowOffsetCalculator : MonoBehaviour
    {
        public int pixelPerUnit = 32;
        public Vector3 targetOffset; // Offset from the target object (in target-local co-ordinates)
        public CinemachineCamera cinemachineCamera;

        CinemachineFollow cinemachineFollow;

        void Start()
        {
            if (cinemachineCamera != null)
                cinemachineFollow = cinemachineCamera.GetComponent<CinemachineFollow>();
        }

        void LateUpdate()
        {
            if (cinemachineCamera != null && cinemachineFollow != null && cinemachineCamera.Target.TrackingTarget != null)
            {
                var followOffset2D = cinemachineCamera.Target.TrackingTarget.rotation * targetOffset;

                followOffset2D.x = (int)(followOffset2D.x * pixelPerUnit) / (float)pixelPerUnit;
                followOffset2D.y = (int)(followOffset2D.y * pixelPerUnit) / (float)pixelPerUnit;

                cinemachineFollow.FollowOffset = new Vector3(followOffset2D.x, followOffset2D.y, cinemachineFollow.FollowOffset.z);
            }
        }
    }
}
1 Like

alrighty thanks a lot! does the script go on the main camera, the cinemachine camera, the player or?

Anywhere is fine, but CinemachineCamera is the natural choice.
Don’t forget to set the reference to CinemachineCamera in FollowOffsetCalculator and add the CinemachineFollow component.

1 Like