Sub-pixel camera movement with pixel perfect stretched viewport?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By makingstuff

I’m trying to set up a 2D pixel art game where the viewport is small (e.g. 320x180) but I also want to have the camera move smoothly when the game is upscaled to modern resolutions (e.g. 1280x720)

I’m using the “Viewport” stretch mode with pixel perfect snapping turned on. In terms of rendering and visuals everything looks fine.

Here’s my problem: The camera seems to be tied to the pixel grid of the viewport (320x180) but I want the camera to be able to move along the pixel grid of the screen (e.g. 1280x720).

The problem now is that let’s say the camera moves slowly pixel by pixel. Since 1 pixel in the viewport turns into a jump of 4 pixels on screen the camera visually appears to stutter and it’s very unpleasant.

If the camera follows the player instantly it’s not so bad. But as soon as I smooth the camera (either with the built-in smoothing or manually moving with lerp) you can see the stutter when the camera slows down as it gets closer to the player.

Here are some solutions I’ve tried, but each of them have downsides:

  1. Turn off pixel perfect snapping. This lets the camera move in sub-pixels but results in visual artifacts, understandably.
  2. Change the stretch mode to “2D” instead of “Viewport”. This lets the camera move smoothly, but it imposes an unnecessary rendering and performance cost because instead of rendering a tiny viewport (320x180) it must render the full resolution (1280x720). This is fine for simple graphics, but adding lights or bloom quickly kills the performance. If there’s a way around this let me know, I could be misunderstanding how this works under the hood.

So I’m hoping maybe I’m overlooking some middle ground. Is there a way to render the game at 320x180 and scale it up to 1280x720 but have the camera move in the 1280x720 grid instead of the 320x180 grid?

I’m not sure how modern games do this. For example the game dead cells has very smooth camera movement and great performance. From what I can tell the world pixel resolution is 640x360 but the game can obviously be run at higher resolutions depending on your monitor. I’m not sure if the Heaps engine is just much more performant so it’s able to process lights, etc at full resolution, or if they found a way to render at 640x360 and upscale while keeping the camera smooth at the screen’s resolution. Anyway that’s just an example. I think Celeste also renders at 320x180 and upscales that tiny canvas up to the screen it runs on. Celeste has a more steady camera but when it moves I don’t remember seeing any stutter.

I’ve read and googled every article/post I could find on this but none of the solutions I’ve found worked for me. When the camera is smooth the performance is bad, and vice versa. I’d love to find some middle ground.

Any tips/advice would be welcome. I’m hoping this is not some inherent limitation of the way Godot deals with viewports and cameras. I’m guessing more likely I’m lacking some knowledge for how to set things up properly.