Now that I’ve walked through the history of OpenGL and seen what I can do at what points in history, I can attack one of the long-standing problems I had with my old pixmap library. It’s not easy to see the issue with the logo image I’ve been using, so I’ll be replacing it with some simple 1-bit checkerboard patterns to make it clearer. If I take a 256×256 checkerboard and scale it with the system we developed over the last two updates, some weird extra patterns emerge. We get a sort of secondary checkerboard falling out of what’s supposed to be an even grid:
Zooming in on a “corner” of that secondary checkerboard makes it clear what is happening:
Since we’re not rendering an even multiple of the 256×256 image here, the renderer is picking the nearest texel for each pixel. Each “checkerboard” pattern expands from a 2×2 block to something that isn’t quite a 3×3 block, and as the fractional parts of each pair of pixels adds up, we ultimately switch, in each dimension, from two black pixels to one white pixel over to two white per one black.
The usual solution to this is to switch to bilinear filtering of the image, to blend each pixel into its neighbors so that these intermediary pixels are grey. This isn’t hard: in our code we just alter the assignments of GL_TEXTURE_MAG_FILTER and GL_TEXTURE_MIN_FILTER from GL_NEAREST to GL_LINEAR . That, however, becomes a problem if we drop the underlying image from 256×256 all the way down to, say, 16×16:
Bilinear filtering begins blending at the center of each texel, which means that when each texel is 30 or so pixels across, they collapse into a blurry mess. What we want is to do this blending but only on pixels that encompass two of our texels, and blend only there. We should be zooming in to something like this:
and then that will look nice and sharp when we have a full display:
The most common name for this kind of magnification system seems to be sharp bilinear filtering. I’ve wanted one of these for awhile, but I’ve never gotten around to actually implementing any. In my previous reading I found some solutions that are almost immediately usable.
Scaling Pixel Art Without Destroying It, a 2017 blog post by Cole Cecil, is where I started. The shader built here is for the Unity game engine, and its format is markedly different than what I’ve been using. Somebody told me about this article long ago and I have completely forgotten who it was. Thank you in absentia!
Manual Texture Filtering for Pixelated Games in WebGL at the Algorithmic Pensieve blog inspired the Cole Cecil post, and it’s working with three.js and WebGL, a tech stack closer to my own.
Superior texture filter for dynamic pixel art upscaling is a followup article to the previous, correcting some blurring issues that could occur when the previous technique was used alone.
... continue reading