4 min read
Parallax Skies

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.

clouds-v7

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.

NN Cloud

Linear filtering adds antialiasing, but it allows for butter smooth floating point movement.

Linear Cloud

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.

Smooth pixels

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!

front-idles-done side-idles-done