I’ve daydreamed a thousand moments about skymagi skies. The skies should morph over time, a slice of land should be visible and change based on altitude, the clouds should use parallax movement to add depth. Gorgeous cloudy days, jaw-dropping sunsets and sunrises, twilight and moon that screams this needs a kalimba.
Unfortunately, I’m still learning pixel art, so… I did my best.
I learned a few techniques that’ll aid making the next sky:
- Use large circular pixel brushes. The better pixel art skies use these exclusively.
- Background should be less saturated than foreground.
- Broad strokes of color matters more than detail.
- Skies would’ve been easier at a smaller resolution.
But, segmented into layers, I can build out the parallax effect.
Parallax
BackgroundRenderSystem
had a new drawCloudySky
function. I could’ve abstracted it into entities/components, but I’ll save that
for background #2.
Each layer (ground, farthest, far, mid, front) moves at a different speed. A curTime
is used to determine their position with a modulo
so their texture wraps back around within the camera boundary.
As usual, the naive solution had issues:
- Oof, I did not make my skies seamless
- The largest clouds should fly in front of the castle
- The clouds jitter.
Fixing the Jitter
So, what is going on?
Here’s a sped-up parallax effect that makes it more apparent:
The issue is nearest neighbor and subpixel movement. Though the cloud’s position might be 0.5,0.5
, it must be drawn on a concrete pixel 1,1
.
The clouds are upscaled 2x from native 960x540
to 1920x1080
. Since the parallax effect is supposed to look perfectly smooth, each jump is distracting.
Additionally, when the renderer choses a subpixel (should 0.5,0.5
be mapped to 0,0
or 1,1
), floating point math can cause different decisions between
the cloud layers. The foreground layer picks 0,0
, the further picks 1,1
, and now the clouds have shifted erratically for one step.
Drawing the pixel art at pixel-perfect 960x540
stops the inconsistent subpixel rendering, but it is even more blocky and distracting.
The expected butter smooth motion can only be achieved with floating point positions, antialiasing, and bilinear filtering. Which is incompatible with pixel art.
…Or is it?
S H A D E R M A G I C
Nearest neighbor provides crisp pixel art. No antialiasing, but it has the jitter issue.
Linear filtering adds antialiasing, but it allows for butter smooth floating point movement.
What if we used linear filtering with mipmapping, but eliminated all of the antialiasing? I found a godot shader reference that managed to figure out the derivatives math and wrote my own in GLSL.
Though it isn’t as crisp as nearest neighbor, it has the butter smooth movement.
It occurred to me that I could use this shader for everything and solve scaling woes, but given the crisp NN works for most cases, it’ll stick to the skies.
Bonus Idle Animations
In between figuring out the jitter problem, I completed idle animations for all the mages!