Is there a general approach in OpenGL that will allow pixel-perfect 2D drawing of textures from an atlas when drawn at native size (including edge pixels), and good quality scaling when filtering to non-native size?
Suppose you’re writing some kind of system for drawing sprites from a texture atlas, such that the user can specify the sprite, and the size and location at which it is to be drawn. Additionally, they may want only to draw a sub-rectangle of the sprite.
For instance, here is a 64x64 checkerboard:
...and alone in its texture atlas:
Note: We’ll assume that the viewport is set up to map 1:1 to device pixels.
Edit for clarity: note that the first 2 approaches below do not give the desired result, and are there specifically to outline what doesn’t work.
1. To draw the sprite at native size, simply use GL_NEAREST
This is fine for drawing at native size, but NEAREST filtering gives poor results when the user draws the object at a size other than 64x64. It also forces texels to align to the pixel grid, giving poor results when the object is drawn at non-integer pixel locations:
2. Enable GL_LINEAR
(0.5/atlas_size,0.5/atlas_size)
to (63.5/atlas_size,63.5/atlas_size)
. Otherwise, for the outermost pixels, linear filtering will sample the sprite’s neighbours in the atlas.Note that we get pixel accurate drawing, except for edge pixels, which are blended with the background, since the vertex boundary falls halfway between pixels.
Edit: also note, this behaviour also depends on whether antialiasing is enabled. If not, the previous approach does in fact give pixel perfect rendering, but doesn’t provide a good transition when the sprite moves from a pixel-aligned to a sub-pixel position. Plus antialiasing is a must in many cases.
3. Pad sprites in the texture atlas
The two obvious solutions to the edge pixel problem involve padding the edge of the sprite with a 1px border in the texture atlas. You could either:
This basically gives us pixel accurate drawing with linear scaling. However, if we then allow the user to draw only a sub-rect of a sprite, either of these approaches fails because the sub-rect obviously does not have the required padding.
Have I missed anything? Is there a general solution to this problem?
It's difficult to know exactly what the "Right Thing" you're looking for is. If you have a 64x64 sprite, and you want to scale it to 65x65 or to 63x63, no amount of filtering is going to make it look good. When you throw antialiasing into the mix, remember that multisampling is not supersampling, and so your textures won't magically get softer interiors.
That said, really non-nearest filtering is supposed to work nicely out of the box with multisampling. I think your GL_LINEAR approach is on the right track, but I think you may have implementation issues.
In particular, linear filtering is supposed to filter when along texel boundaries. This can happen, for example, if you have two adjoining triangles. Point of fact, you should expect linear filtering along sprite edges, and this filtering is the Right Thing.
You shouldn't try to correct for this by adjusting texture coordinates (and therefore vertices), since this will incorrectly scale the texture coordinate across the texture. I'd instead recommend clamping texture coordinates to the desired range plus/minus 0.5/texture_res in your shader.
You'll find that this solves the problem for pixel-perfect results at native scaling and also good quality magnification. Minification will look okay, but I'd recommend trilinear (mipmap) filtering for best results.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With