Pixel art is a very popular visual style for games, but displaying it correctly in a modern game engine is quite difficult. The look can fall apart aesthetically if not rendered the right way. I want everyone’s pixel art to display beautifully, so I’m releasing a free Unity Pixel Art camera that solves this problem elegantly.
For an example of my tool in practice, check out my recently released “GirlJail” jam game. This is a good demo showcasing the production reliability of this approach across iOS, Android, PC, Mac and Linux.
At the moment the common approaches to pixel art in Unity are based on just setting your camera’s size in perfect ratio with your art’s pixel size, then setting point filtering on every texture and hoping for the best. This is woefully inadequate for maintaining consistency and displaying pixel art well.
As per the name, pixel art came about because every individual pixel a low resolution screen displayed was carefully decided and hand drawn. On modern high resolution displays it is not practical to draw every single pixel. We need to come up with new methods of displaying pixel art that preserve it as a style, while keeping it a viable low-cost choice.
To render pixel art correctly in our modern 3D-based game engine, there are a number of principles a Unity pixel art camera should follow:
Display sprites without distortion
Simply enough, when we display a sprite, it shouldn’t be stretched or distorted. Most existing solutions focus on addressing this, but don’t try to solve any of the following points.
Enforce a consistent pixel size
Failing to abide by this rule is by far the most common mistake visible in modern pixel art games. All pixels should be the same size. This consistency lets players eyes process the image comfortably, allowing sharp edges to not disrupt the experience.
Conform to a pixel grid
Related to the consistent pixel size, pixels should all align to the grid, a pixel should never be able to half overlap another pixel. Either it occupies the whole pixel space or it doesn’t. If there isn’t a consistent grid, the visual result is inconsistent pixel sizes, even if your images maintain correct scale.
Pixels are the atoms of your game universe, they are quantised and it’s safest not to split them.
Retain position stability while the camera moves
The position of objects in the world should not be affected by the movement of the camera. In the warning example above, look at the shadow under the character or the fence relative to the dirt path; observe that as the camera moves, the sprites jitter out from their expected position.
Fill the screen in a predictable way
When designing the screen composition of your game, you want a high degree of certainty about what will and will not be displayed on screen. You want to choose the resolution that works best for the experience you want to provide. Many Unity pixel art camera approaches prioritise multiplying pixels by integer values, resulting in a completely unpredictable composition based on the resolution of the screen.
So a larger or smaller resolution screen should still display the same composition, and a change in aspect ratio should add as few pixels as possible onto our design.
Avoid floating point precision errors
We want our pixel art to always display consistently, however because we’re not working in an old school 2D integer space, our viewport is all based on imprecise floating-point maths. Even if we carefully round our sprite positions, distortion can still occur when sprites are placed on certain pixel boundaries, it is critical to be aware of this and prevent it.
So how do we create such a consistent display of pixel art while also scaling to any resolution or aspect ratio?
Simply by rendering at the true designed resolution of our game, then scaling twice.
- First we set up an internal texture of the ideal pixel resolution. The size of this is determined by an input target resolution, with pixels added on either the width or height to accommodate the screen’s aspect ratio difference, if any. The whole scene is rendered to this small texture. This step automatically eliminates any possibility of broken pixel grids, or inconsistent pixel sizes, because it is impossible to draw to anything smaller than a pixel.
- Secondly we want to scale to any possible percentage, but scaling pixel art by non-integer values often leads to obvious distortion, or blurriness. To solve this problem as a first step we scale by integer values, using point filtering up to the first resolution larger than our screen resolution.
- Finally, we scale this large texture back down to our destination screen resolution using bilinear filtering.
Update (April 2018): In version 2 of this package, I achieve the upscaling/downscaling effect in a single step via a bilinear-upscale shader, which looks almost identical but saves a significant amount of memory and GPU time on high resolution devices.
The result preserves the clean pixel look, while allowing us to target any resolution or aspect ratio we want. This has the side-effect of introducing some interpolated pixels at pixel grid boundaries, but with the high DPI of modern screens, it is not a visible issue. Take this image as an example (your browser will apply bilinear downsampling to it as it displays):
But we’re not done yet, we still need to prevent sprite distortion and keep positional stability etc. The other major part required for this is a sprite shader to keep all the textures drawing at precise pixel positions. Inside this shader:
- the first step is to effectively align the camera to a pixel position, so all vertices are offset by the position difference of the camera to the nearest pixel. This is the key to ensuring stability.
- Then they are snapped to the nearest pixel in viewport space, so boundaries will sit on pixel edges and draw without distortion.
- And finally offset by half a pixel if there is an odd-numbered pixel resolution as viewport space is always aligned to the centre.
It requires a minimum Unity version of 2017.2. If you’ve tested it and it functions as you want, but you need it to work on an older version, let me know and I might invest some time in making it backwards compatible.
If you end up using this, I’d love to hear about your game (and if you’re nice I always appreciate a credit!)
It’s super easy to get started:
- Put the PixelArtCamera component anywhere in your scene (I recommend on your camera)
- Connect the Camera and Canvas in the appropriate fields if they’re not automatically filled in.
- Set the resolution on the PixelArtCamera component to your preference (and match the Pixels Per Unit to your texture imports, if you haven’t left it on the default ‘100’.)
- Put a material using the ‘Ocias/Pixel Art Sprite’ shader on your sprites.
A Personal Recommendation
A major point of contention in pixel art display is the choice of displaying pixel art with hard edges or smoothly interpolated. Generally the consensus has been on hard edges, despite the fact that pixel art at native resolution is realistically not as sharp. Why is this? We know sharpness is not authentic, and possibly not even optimal, but on some level we just feel smooth pixel art looks “wrong”. I propose there a very simple reason for this. We dislike pixel art when interpolated due to incorrect gamma color blending.
Consider these three images:
- a gamma blended image
- a photo of native 1 to 1 pixel display of the scene on my monitor
- and a linearly blended image:
It is completely clear how far away the gamma blended version is from the ground truth. Black lines become much thicker, characters especially appear less pleasant to look at. When blended correctly – linearly – pixel art captures the same warm feeling we like from native display.
My recommendation: For your next game consider setting your color space to linear, and your pixel art camera to “smooth”. It’s actually more authentic and, in my view, more pleasant to look at than sharp pixels.