7
\$\begingroup\$

How do I keep the sprites in my game nicely pixelated on screens of all sizes and resolutions? I use nearest-neighbor texture filtering when resizing pixels, but this works well only at really high magnifications, otherwise the pixels get distorted and uneven.

Jitter

I understand the limits of nearest-neighbor scaling and I wouldn't be asking this except there are so many games that use pixel art without this problem, so I figure there is some trick I don't know about.

\$\endgroup\$
  • 3
    \$\begingroup\$ What about using nearest neighbor for magnification, but linear filtering for minification? \$\endgroup\$ – János Turánszki Dec 2 '14 at 12:17
  • \$\begingroup\$ All three icons in the image were magnified using nearest neighbor, but only the 8x looks right. \$\endgroup\$ – TimSim Dec 2 '14 at 12:20
  • \$\begingroup\$ Maybe not the right term for others, but if you were in C# with XNA, I'd assume you forgot to change your sampling state to PointClamp. What you describe regarding nearest-neighbor filtering when resizing should work just fine as long as you are scaling by whole value magnifications (x1, x2, x3, etc) \$\endgroup\$ – Mythics Dec 2 '14 at 12:33
  • 1
    \$\begingroup\$ The problem is even if I magnify by integers, on different devices that will result in different pixel values that are not themselves exact multiples of the original sprite's dimensions, because my viewport scales to fit the screen. I guess I'll have to give up on pixel art, it's just easier to use high res art assets that can scale to anything. \$\endgroup\$ – TimSim Dec 2 '14 at 12:39
5
\$\begingroup\$

For one, nearest-neighbor will result in pixel perfect scaling only if it's used to upscale in integer multiples. Apart from that, you should not be conducting any scaling operations on your view/projection matrices. If you set your view to match the dimensions of the screen, and your projection matrix to be orthographic with dimensions that match the viewport, your "world" coordinates will be true on-screen pixels. Your viewport can resize to fit different screens, but it shouldn't actually impart a scale factor, you should just show more or less of the game world. Further, you should probably disable AA, and remember that in OpenGL the center of each pixel is located at (.5, .5), not (0, 0).

Take a look at these related questions:

https://stackoverflow.com/questions/10040961/opengl-pixel-perfect-2d-drawing

https://stackoverflow.com/questions/16511374/pixel-perfect-projection-matrix-in-opengl

\$\endgroup\$
  • 1
    \$\begingroup\$ Nox says: "remember that in OpenGL the center of each pixel is located at (.5, .5), not (0, 0)." <== super important info! \$\endgroup\$ – david van brink Dec 2 '14 at 19:34
2
\$\begingroup\$

You need to average the sprite pixels which are visible beneath each screen pixel. The sprite pixels can be sampled "nearest", but to move smoothly in subpixel increments on the display, you need some kind of oversampling.

If you're using OpenGL, you can do this in your shader by averaging 4, or 9 or more sprite samples offset near each fragment (calculated to be within a screen pixel). Alternatively, render your scene at 4 or 9 or more times the screen size, and then reduce with averaging. (This may be available directly on your platform, or implemented as a shader, but run just once at the end instead of per sprite.)

Which one is more performant depends on the scene, number of sprites &c.

-- EDIT: One more approach -- Make your sprites pre-magnified, pixelated but nice and big, and use linear interpolation. That way when a (big) "pixel edge" is under a fragment, you'll get a blend.

All of these methods run risk of washed-out strobing, if sprite pixels are close in size to screen pixels, or if they match but offset fractionally. I think the a good rule would be, if sprite and screen pixels match exactly and are aligned: Looks good. Else, sprites should be scaled at least 2.0+ and fractionally averaged, to guarantee at least one fully-colored screen pixel per sprite pixel.

\$\endgroup\$
1
\$\begingroup\$

You'll want to look into pixel-art upscaling filtering algorithms (http://en.wikipedia.org/wiki/Image_scaling). These have been specifically designed to upscale pixel art, having the properties of (1) retaining the original color palette (aka. no blurring) and (2) removing jaggies (aliasing that occurs due to nearest neightbour sampling).

Some names of algorithms are: Eagle, 2XSAI, Super Eagle, Super 2XSAI, Scale2X, Scale3X, HQ2X, HQ3X, HQ4X, 2XBR, 3XBR, 4XBRZ. They are more or less ordered from simple to complex, with 4XBRZ being my favorite in appearance.

They all work with integer magnification factors (2, 3 and 4). The usual approach for obtaining non-integer magnifications is to take the nearest integer larger than the desired magnification factor and downscale the resulting image (using either nearest neightbour or linear interpolation). An alternative is to take the nearest integer smaller than your desired magnification and add borders around the image. The choice between these two approaches comes down to the style of your pixel art and personal preference.

Edit: I ran your image through an implementation of Eagle, Scale2X and Scale3X I made a while ago. Here are the results in order (magnified by x2 so you can better see what the algorithm does to the pixels). Scale3X is the most complex algorithm, and it also looks best in my opinion. There are way more complex algorithms in the list I mentioned above though, so you'll want to experiment with more of them.

Eagle Scale2X Scale3X

\$\endgroup\$
-1
\$\begingroup\$

You need to apply a low pass filter before the down-sampling (down-scaling) occurs on the low resolution screens.

I have used simple gaussian blur (convolution transform) with good results:

enter image description here

This was done with a blur radius of 1 pixel.

\$\endgroup\$
  • 2
    \$\begingroup\$ Sorry, but I would not call these results good \$\endgroup\$ – Kromster Dec 3 '14 at 7:58
  • 1
    \$\begingroup\$ The solution of downsampling you propose works excellent for natural images that have mostly low-frequency spectral components. An example of natural images would be photographs. In the use case of TimSim, he is working with synthetic images. These have high spectral components due to sharp edges (caused by the limited color palette of pixel art). This is why most people will argue the solution of low-pass filtering is inappropriate here. \$\endgroup\$ – Jelle van Campen Dec 3 '14 at 10:55
  • \$\begingroup\$ The low-pass filtering method used is a simple convolution matrix and far more performant (and therefore appropriate to gaming) compared to the other scaling algorithms (Eagle, etc mentioned). Modern HDTVs use exactly this method, which is an artistic rather than technical solution. Comparing the two results above, it is a matter of taste as to which you would like to stare at longer. Downvoting a valid and accepted solution is counter productive. This solution prompted another solution which included results and provided a basis of comparison. \$\endgroup\$ – Dominic Cerisano Dec 3 '14 at 19:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.