Full notes
Full The Void Project update
Read the full published notes in a cleaner layout. The original post stays linked below.
What changed
- Performance
- Gameplay
- Balance
Welcome to Devlog #1, well the second devlog to be exact.. this devlog will be more focused on how we achieved this pixelated dual-tone art style for our game.
Note: this shader was not entirely ours as we vastly expanded upon a similar in nature shader with CC0 License.
Here's a breakdown for the shader..
Understanding the Foundations
At its core, achieving that retro look comes down to three key elements:
Pixelation: We deliberately reduce the resolution to make the image blocky. Think of the iconic chunky pixels of early game characters!
Color Banding: The smooth gradients that we take for granted today simply weren't possible. Colors transition in distinct bands, creating a bold, almost posterized effect.
Dithering: To soften the harshness of the color banding, old games use dithering. This involves strategically placing pixels of different colors from the palette near each other, tricking the eye into seeing a wider range of shades.
Breaking Down the Code
This shader tackles each of these elements step by step. Let's peek under the hood:
The Luminosity Factor: Calculating the pixel's brightness (luminosity) accurately is essential for proper color banding. Many simple formulas just average the red, green, and blue, but We're using a more accurate one that accounts for how our eyes perceive these colors differently.
Controlling Contrast: Adjusting contrast in this case isn't just about shifting brightness; it's about exaggerating it. Our shader multiplies the luminosity, pushing light areas even lighter and dark ones darker, creating a more stylized look.
Snap! – Creating Those Bands: This is where we force colors into distinct bands based on the pixel's brightness. By reducing the "bit depth" using clever math, we simulate the old hardware limitations.
Mapping the Palette: The shader dynamically figures out which two colors from our palette texture the pixel falls between. It's like carefully placing the pixel on a color ruler!
The Magic of Dithering: Time to break up the harsh bands! We use a dithering texture (basically a noise pattern), and compare each pixel's luminosity to a corresponding value on the texture. This determines whether to pick the higher or lower color from the palette.
In-Depth Explanation:
1. Luminosity Calculation and Contrast Adjustment
Accurate Luminosity: The shader uses a more accurate luminosity formula than simple averaging. Different color channels contribute differently to how we perceive brightness. This formula is essential for realistic color banding and dithering later in the process.
- Contrast as a MultiplierNotice how contrast is not added or subtracted, but rather multiplies the normalized luminosity. This has a non-linear effect, pushing darks darker and brights brighter without simply shifting the overall range. This can create a more visually punchy retro aesthetic.
2. Color Reduction for Banding
Intentional Floor Function: The floor function is used to snap the continuous lum value into discrete steps. This creates the characteristic bands of color, simulating the limited bit depth of old displays.
Adaptable Bit Depth: The u_bit_depth (uniform var. in shader) uniform lets you control the number of distinct bands. A lower bit depth gives you a stronger, more obvious retro effect.
4. Dithering Logic
Dithering's Purpose: Our eyes have incredible color accuracy, so the color banding alone would look harsh. Dithering breaks up these bands with a noise pattern, creating the illusion of additional shades between the palette entries.
- Thresholding the NoiseThe dither texture isn't directly added to the color decision. Instead, a pixel from the dither
Source
Changelog.gg summarizes and formats this update. How we read updates.
