Even if you are just using the PixelPerfect camera from unity it can be used in different modes which have different tradeoffs between producing pixel perfect frames and smooth movement.
- image upscaling → this is one of the easiest ways to get pixel perfect still images, but depending on other settings you will get either shimmering look or jittery movement. When using this mode you can even put high resolution nonpixel art images and get (not necesarily great) but somewhat pixely looking results.
- pixel snapping → before rendering snaps the sprite renderer positions to pixel grid. As described helps aligning things on the pixel grid, but it also makes the movement more jittery
Overall that gives you 4 combinations:
- +image upscaling +pixel snapping → good look but potentially jittery movement, rotating image remain on pixel grid but shimmer
- +image upscaling -pixel snapping → replaces jittering movement with shimmering, rotating image remain on pixel grid but shimmer
- -image upscaling, +pixel snapping → no shimmering (even when rotating or scaling), rotating images slightly stand out since their pixel grid isn’t aligned with rest of image anymore, easier to use high resolution text. Movement can be somwhat jittery due to pixel snapping.
- -image upscaling, -pixel snapping → can produce smoothest movement and no shimmering compared to other modes, but this mode is also somewhat furthest from pixel perfect look. With regards to rotation same as previous mode. It is up to developer to ensure that object are positioned correctly so that it doesn’t look too messy. You want to ensure that at least the static background images and props are aligned and on integer coordinates. You can still get close to pixel perfect look, but it takes more work.
You might ask what’s the point of using pixel perfect camera with image upscaling and pixel snapping both disabled. There is a third big thing that pixel perfect camera does, it calculates the effective camera ortho size based on desired reference resolution and physical screen resolution (or window size) so that one pixelart image pixel maps to integer amount (typically 2-6) of screen pixels. Even with other features disabled this helps a lot with drawing the pixelart sprites more nicely. It is not that difficult to calculate ortho camera scaling yourself without pixel perfect camera, but it’s probably beyond many beginers as indicated by large amount posts where people are struggling to match the pixelPerfect camera content with overlay canvas scaling.
There is one more trick beyond what the PixelPerfect camera does to achieve smoother movement. Even with pixel snapping disabled, the incremental movement by one pixel at time (when using point sampling) can look slightly jittery. Especially when you have slow moving sprites or two sprites that move at slightly different but similar speeds (like multiple parallax background layers or slowly moving clouds). It’s less noticeable on a 4k screens where every pixel is very small. I know two major approaches to mitigate this.
One somewhat hacky solution which requires minimal technical skills is to have the pixelart sprites upscaled (3-8 times) in image editor (completely different from the upscaling during rendering) and using bilinear filtering mode in Unity instead of for pixelart more commonly used point sampling mode. This approach somewhat maintains blocky pixels without making them completely blurry (which would happen if you used bilinear filtering without manual asset upscaling), while maintaining smooth movement. There are two downside to this approach, one is that edges of pixels can look slightly fuzzy especially if the scaling factor for the current screen resolution is too different from the factor used during manual upscaling. Other major issues is that it increases memory usage by a lot. This might not be too much of a problem for some games that don’t have too many sprites, and the normal non upscaled sprites being very small anyway. You can also slightly mitigate the upscaling cost by using texture compression. Typically you don’t want to use texture compression for pixelart since it can ruin the colors and introduce other defects, but if you are smart and take into account technical implementation details of specific texture compression algorithms, choosing an upscaling factor that matches texture compression block size can avoid those defects completely. It would still be larger than non upscaled sprite, but better than non compressed. This also raises the lower end requirements for the game to run well and increases a chance of getting bad review. There are plenty of people playing games on older computers or laptops, they understand that they can’t play latest AAA games, but expectation is that they can at least play some simple indie games with pixel art graphics. When upscaling sprites be carefull when looking at size of them on disk. When saved as png it might look they barely changed in size, but the actual VRAM usage increasing 16-64 times. That’s because image formats like PNG can compress such repetition very well, but such compression schemes are unsuitable for runtime where GPU must be able to quickly query any pixel at any point of time. So VRAM usage will still be high even though files on disk are look small. 16-64 increase can be the difference between small enough for even the oldest GPUs as long as they are supported by by modern operating systems and graphic APIs and being so big that it barely fits on latest generation top end gaming GPUs.
Different approach to get maximum smoothness is to make a custom shader which implements this kind of mixed sampling mode without having to use upscaled texture. This approach requires a bit more techincal skills. The benefits is that unlike previous approach VRAM usage doesn’t increase, it should also look slightly sharper because custom shader will use the actual screen pixel size and scaling factor of current display, intstead of fixed manual upscaling factor used by previous technique. One more benefit is that it simplifies the workflow, you don’t need to do any work manually creating and managing upscaled copies of all the assets. It’s much easier just to swap out material, than changing a bunch of sprites with different dimensions. Downside is that the custom shader manually performing multiple texture pixel sampling and interpolating between them will be slightly more computationally expensive than previous technique which tricks the builtin GPU functionality to do the desired work. On the other hand savings from less memory usage might outweigh this loss, but without making exact benchmarks it’s hard to speculate what will perform better. One more thing you can do with this approach is apply it selectively only to the object where jittering is most obvious, like background parallax layer and maybe moving enemies, but keep it disabled for others.
It might also be possible to achieve similar results by using appropriate antialising mode. Since the previous shader is more or less doing the same thing as super sampling AA, but only at fragment shader level instead of whole screen level. Haven’t tried this approach myself. Either way if you are choosing this or the previous approach, it might be nice to make this an optional feature so that if this causes performance issues for people playing on older laptops (or more likely just people trying to maximize the batter life), they can choose to disable for tradeoff between maximum smoothness and better battery life.
Regardless of previously mentioned stuff. Additional jittering can be caused by not getting the update sequence right or chosen interpolation settings, what positions get calculated based on movement of other objects, and in which order they get refreshed each frame. Camera position calculated based on player position, parallax position calculated based camera position additional camera position calculations performed by Cinemachine or other system implementing the camera lookahead or clamping within level bounds. If the sequence is wrong you might be using position from previous frame which can introducing jittering. The hard part is that for smooth results you need to get all of it correct, but it’s sufficient to have just one thing wrong get jittering. You might often see people on forum saying “I tried all of that but nothing helped”. While in truth multiple of those factors where causing the problem, but just flipping them one at time will make it look like none of them made a difference. Even worse you can’t blindly “fix” all of them, because for some either available mode can be “correct” or “wrong” depending on other factors. It’s hard to come across right combination if you just randomly flip things without understanding what and why.
My general recommendations with regards to getting pixel perfect results (in addition to basic techniques everyone commonly talks). Don’t rely on the pixelperfect camera (or other tool) to magically fix things for you. Don’t place objects randomly in editor and hope that some system will later snap the position, make things so that result would look good even without extra help, and then add tools on top to catch any cases you missed. Be extra careful with stuff like sprites for which the dimensions are odd numbers (not multiple of two). Trying to center such sprites may easily end up with bad looking results. When editing sprite pivot do it in pixel coordinates instead 0-1 values. Having mix of even and odd sized sprites also means that you can’t just set their position to be identical and hope that they will be aligned. Even if the pixel perfect camera or something else snaps the position to proper integer position, if the rounding happens after going through chain of calculations (like transform hierarchy and world<->screen transformation) it may easily flip from being rounded one way to being rounded other way, that may mean jittering or one of the objects in a row of multiple being off by one pixel.
There are also tricks for making some jitter less obvious by avoiding problematic combinations. If you have multiple parallax layers make sure the speed difference is sufficiently high. It is also possible to position parallax layer content so that there is vertical gap between the layer → one layer closer to top part of screen, other layer closer to bottom, jittering caused by difference in speed is less obvious when object’s aren’t moving in parallel close to each other. Prevent the player moving at slow speed (especially when supporting stick based movement), when speed is sufficiently low stop, once player starts moving immediately start with min speed. Splitting the level into single screen size sections where background scrolls only during transition between section also minimizes the problematic background jitter.
Unity pixelperfect camera doesn’t do that much and doesn’t do it too well. You might think while it doesn’t provide a lot of options it should at least provided better compatibility with other Unity functionality and do the things it does really well. But that’s not really the case. PixelPerfect camera doesn’t properly support render textures and multiple cameras. Interactions with Cinemachine also has limitations. With just the scaling stuff you will see warning when display dimensions isn’t multiple of 2, also if you try to slowly change the dimensions of freely resizable window you can easily notice certain parts shifting along the diagonal and rectangular pixels indicating that things aren’t pixel perfect. If it only produces pixel perfect results in the easy cases when things would have looked good anyway, what good does it do.
If you aim for maximum control of how things behave and the best look you might endup replacing it with custom implementation.
Overall it will depend a lot on your chosen art style, how much of authentic retro look you are aiming for, how perfect you want the pixels too look, how much of dynamic movement your game has, and how much technical effort you want to put into this. Another factor for choosing this is how much and how complex text will your game have. If you will be stepping away from authentic retro look to the sake of more easily managable and easier to read long texts anyway, you might as well use it and get slightly smother movement in some other places.
There games like Heros hour and Peglin, that use low resolution pixelated sprites but don’t even try to get pixel perfect results. Once you start to freely rotate and scale everything, the there is no point obsessing over aligning every little thing. In case of peglin there are even plenty objects screens with mixed scale sprites.
There are also games like Shovel Knight and other Yacht club games which one hand not only carefully align every pixel on screen, but also embrace the color pallet, animation and other limitations, but at the same time are willing to step away from limitations where necessary to achieve smoother result.