Quick answer: Jitter happens when your follow script and the Pixel Perfect Camera round to the pixel grid at different times. Run follow logic in LateUpdate with an execution order before the camera, round the follow target in floats to 1/pixelsPerUnit, and keep the sprite on a parent offset so sub-pixel player motion never reaches the renderer.

You enable the Pixel Perfect Camera, and everything looks great while standing still. The second the player moves, the entire scene jitters like you are viewing it through a washing machine. Your sprite is crisp, the camera says it is aligned to the pixel grid, but the combination shimmers. This is a timing bug, not a pixel bug, and it has a clean fix if you know where the rounding happens.

How the Pixel Perfect Camera Rounds

The PixelPerfectCamera component runs in LateUpdate. It computes an orthographic size that produces integer pixel-per-unit mapping, then it snaps the camera’s world position to the nearest multiple of 1 / pixelsPerUnit. The renderer then uses that snapped position to raster sprites.

Your follow script probably looks like this:

public class CameraFollow : MonoBehaviour {
    public Transform target;
    public float smooth = 5f;

    void Update() {
        transform.position = Vector3.Lerp(
            transform.position, target.position, smooth * Time.deltaTime);
    }
}

Three problems here. First, Update runs before physics, so the player may move again after you follow. Second, the target position is a float that changes every frame by an arbitrary sub-pixel delta. Third, the Pixel Perfect Camera’s snap happens after your Update, so your smooth intent is rounded away, but the rounding error is not stable — it changes each frame by up to half a pixel. Result: 1-pixel jitter.

Step 1: Move Follow to LateUpdate

The first fix is to run follow logic after physics and before the camera’s own LateUpdate. Move the script to LateUpdate and set its script execution order to a negative value (e.g. −100) so it runs before PixelPerfectCamera:

[DefaultExecutionOrder(-100)]
public class CameraFollow : MonoBehaviour {
    public Transform target;
    public float smooth = 5f;

    void LateUpdate() {
        var desired = Vector3.Lerp(
            transform.position, target.position, smooth * Time.deltaTime);
        transform.position = new Vector3(desired.x, desired.y, transform.position.z);
    }
}

Step 2: Round the Follow Target Yourself

Even in LateUpdate, a smoothed follow writes a sub-pixel position. Compute the smoothed position in floats, then snap to 1/pixelsPerUnit before assigning:

[SerializeField] private int pixelsPerUnit = 16;

void LateUpdate() {
    float unit = 1f / pixelsPerUnit;
    Vector3 desired = Vector3.Lerp(
        transform.position, target.position, smooth * Time.deltaTime);

    desired.x = Mathf.Round(desired.x / unit) * unit;
    desired.y = Mathf.Round(desired.y / unit) * unit;
    desired.z = transform.position.z;

    transform.position = desired;
}

Now the camera arrives at a position that is already exactly on the pixel grid. The Pixel Perfect Camera’s own snap becomes a no-op, which means there is no rounding error to fluctuate.

Step 3: Handle Sub-Pixel Player Motion

Your player probably moves in floats too. If the camera snaps but the player does not, the player’s sprite appears to swim by sub-pixels each frame while the camera jumps in whole pixels. Use a two-level transform: a root node that moves freely (physics, input, pathfinding), and a sprite child that renders a rounded offset:

public class PixelSpriteRounder : MonoBehaviour {
    [SerializeField] private int pixelsPerUnit = 16;
    private Vector3 initialLocal;

    void Awake() { initialLocal = transform.localPosition; }

    void LateUpdate() {
        var rootWorld = transform.parent.position;
        float unit = 1f / pixelsPerUnit;
        Vector3 snapped = new Vector3(
            Mathf.Round(rootWorld.x / unit) * unit,
            Mathf.Round(rootWorld.y / unit) * unit,
            rootWorld.z);
        transform.position = snapped + initialLocal;
    }
}

The root node still has sub-pixel positions for game logic; only the visible sprite is rounded. This also makes hit detection precise, because your collision can keep float precision.

Step 4: Pixel Perfect Camera Settings

In the component inspector, the settings that matter:

Step 5: Parent the Camera Inside a Shaker

If you have a screen shake, nest the pixel-perfect camera inside a shaker parent. The shaker moves in floats; the camera holds a pixel-rounded local offset of zero. When you shake, you rotate or translate the parent and the pixel camera follows along while its own local position remains grid-snapped. Without the nesting, a shake translates the camera to a sub-pixel position and reintroduces jitter.

“Pixel perfect is not about turning on one setting. It is about making sure nothing in the chain that ends at the renderer carries sub-pixel noise.”

Verifying the Fix

Run at 60 fps, move the player at a constant low speed, and record video. Step through frame by frame in your editor: the background should shift by exactly 1 pixel per N frames, never by half a pixel, and never by 0 then 2. If you still see half-steps, something in your follow chain is writing to transform.position after the rounder runs — search the project for transform.position = and audit the execution order.

Related Issues

If sprites wobble relative to each other on the screen even without camera movement, see Sprite Atlas Texel Seams. For sub-pixel tilemap artifacts, read Tilemap Half-Pixel Offset.

LateUpdate follow + self-round in floats + rounded sprite parent = rock-solid pixel perfect motion.