Search Unity

Question Smooth looking pixel art in motion?

Discussion in '2D' started by Rocky_Unity, Jan 29, 2023.

  1. Rocky_Unity

    Rocky_Unity

    Joined:
    Oct 13, 2017
    Posts:
    96
    I am trying to figure out how some games do pixel art.

    There's the obvious, slap the pixel perfect camera and call it a day. But I don't like this, it causes things to move in a very jittery fashion, and you can forget about a smooth follow camera.

    Something like Stardew Valley is definitely not pixel perfect. I noticed that there's many instances that you can see sub-pixel movements, as well as a really smooth follow camera. So I was wondering how this can be achieved?

    Is StardewValley exporting sprites to a larger size, say, 400%, and that's how? Any input on the matter is greatly appreciated!
     
  2. karliss_coldwild

    karliss_coldwild

    Joined:
    Oct 1, 2020
    Posts:
    602
    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.
     
  3. Rocky_Unity

    Rocky_Unity

    Joined:
    Oct 13, 2017
    Posts:
    96
    Karliss, oh my gosh where do I begin, what an excellent write up!!
    Thank you so much for all of this knowledge, it is really helpful! I was able to understand everything you spoke about and even found a shader with the point sampling which is awesome. I'm not sure which route I will go yet, setting the orthographic camera size correctly and keeping all objects set a proper pixel unit definitely is the best place to start for now.
    I definitely don't want that "pixel perfect" result, like you mentioned, something like Hero's hour or Peglin is what I'm after. I want pixel art, but not the constraints of being pixel perfect.. Sometimes I have the issue of the tilemap "splitting" apart if not using the PixelPerfect camera, so I'll need to experiment here as well. I believe this is caused by the camera being at a position between proper pixel values, so maybe clamping the camera position each frame will help. I'll play with it.

    I am going to continue to reference your write up here, it has so much helpful knowledge, this should be pinned and turned into the first google search result somewhere! Pixel art is not as easy as some may think to get to look good!

    I like to keep my PPU at 100 (instead of commonly choosing a unit size of like 16, 32, 64, etc) because it makes positioning and clamping of objects really easy. Every 0.01 unit in unity is 1 pixel then.

    Again, thank you :)
     
  4. karliss_coldwild

    karliss_coldwild

    Joined:
    Oct 1, 2020
    Posts:
    602
    Ah yes the PPU. I sometimes even choose 1 PPU that way it's even simpler to align things, wouldn't want to do some of the stuff mentioned above with PPU > 1. 1PPU might not be ideal for physics based stuff, but for simple stuff it can be made to work.

    If 100 PPU works for you good, I would be slightly worried about using that. Floating point numbers can't precisely express decimal fractions so you can more easily end up in situations where you are unsure whether number is slightly of because you positioned things wrong, or whether it's due to floating point limitations. Using PPU which is power of two (16, 32, 64) have the opposite problem. Binary fractions (within limits) can be store and processed using floating point numbers exactly, but when viewed in editor they are shown in decimal format (like most human readable numbers) which makes it difficult to tell whether numbers are on the chosen pixel grid or not. I am fine with fractions down to 1/8, but I couldn't tell whether 0.20875 or 0.21875 is a multiple of 1/32. I guess in such setups the automatic snapping performed by PixelPerfect camera is more useful.
     
  5. antonsenlarsen

    antonsenlarsen

    Joined:
    Nov 20, 2020
    Posts:
    4
    ty karliss_coldwild. I wish I found your answer sooner. Been struggling a long time with my pixel art jitter. This should be pinned and pasted in every "pixel jitter issues" post so that people don't have to look for months to get a good answer.

    Do you know of any good filters though? what are you using in your project? I need something that can handle rotations and movement at least decently.