Blomst : atmosphere & saturation fx
Another technical post to talk about some of the shader stuff and related things done for our jam game, Blomst ๐ป! There's also part one about procedural planets and their shaders.
Go here if you want to play Blomst ๐ป!
This time, I'll talk about two more minor effects that play into the game's mechanics: the rendering of the atmosphere that builds up around the planet as you plant more things on it, and the change from a brownish, dead planet coloured in sepia to a lush, green world with sharp colours!
The transformation

This picture shows clearly the contrast between what the planet looks like at the start of the game and nearing the end of a completed level. Initially, the colours just blend together into a brown, lifeless mush, and not much of an atmosphere is there; what little there is is also brown.
As the atmosphere builds up over the course of the mission to terraform the planet with various plants, the sepia effect tones down and the true, radiant colours emerge, while the atmosphere also grows, and reveals its blue colour. ๐
Atmosphere
Let's start with the simplest effect to pull off. I've seen a bunch of other LD38 games with planets use the same trick, actually! As you can see, the atmosphere is at all times rendered behind the planet itself and everything on it, no matter how it twists and turns:

Normals
The answer lies in the surface normals, and how front/back face culling works. The normal vector of a surface (generally a triangle) in 3D space is perpendicular to that surface, as illustrated by the blue arrows in this figure from the Blender documentation:

These basically tell us in which direction a surface is facing.
Culling
As a way to optimise rendering, most rendering systems have a system where faces that are facing away from the camera are completely disregarded and not rendered, as they can generally be safely assumed to be invisible to the camera, as generally 3D objects are properly filled in without any holes, and are not usually just flat triangles.
Here's a wireframe example to illustrate what it means. To the left is a sphere with the back faces (the ones facing away from the camera, that are supposed to be invisible due to being obscured by front facing geometry) rendered, and to the right is one with these faces culled, as they need not be rendered:

The trick to the atmosphere is inverting these normals, so that all the faces of the (atmo)sphere point inwards. This has the interesting effect that faces that are closer to the camera are actually pointing away from it, and are therefore culled, while the faces far from the camera are facing towards it, and therefore being rendered! Another way to achieve this could've been to actually cull front faces in the shader for the atmosphere, but this allowed me to use existing back face culling shaders.
Essentially, with the regular back face culling, we would get this, with the planet obscured by the atmosphere:

But instead we get this, where we see into the atmosphere like a bowl, rendered only behind the planet and not in front:

Mission accomplished!
Sepia to saturation
So this one involves shader programming. Shaders allow us to mess with pixels before they get rendered to the screen, and so changing the colour of them is one of the simplest things one can do! The little shader program works on each pixel individually and applies the same operation to each.
So we start out with this fully saturated planet before applying any effects, of course:

Greyscale
The first thing we need to do in order to achieve a sepia effect is to simply convert the image to greyscale, giving us a single intensity value for each pixel instead of three values (red, green and blue) to encode a colour.
We can convert an RGB triad to a single greyscale value by treating the values as a three-dimensional vector and extracting the dot product between this vector and another vector containing NTSC conversion weights, a set of values chosen on the basis that the human eye experiences different wavelengths of colour differently; we are especially good at greens, which is clearly reflected in the weights vector:
fixed grey = dot(col.rgb, fixed3(0.299, 0.587, 0.114));
Now we get this:

Sepia
Once we have this, we can convert the greyscale into sepia, which needs the three colour channels again, so we take a vector where the red, green and blue values respectively are all equal to the single grey value we extraced, and multiply this by a sepia vector, which boosts up the red, leaves the green as is, and decreases the blue a bit, giving us that characteristic brownish tint:
fixed3 sepia = fixed3(grey, grey, grey) * fixed3(1.2, 1.0, 0.8);
Done! Voilร :

Interpolation
Finally, I needed to make the planet interpolate from full sepia to full colour throughout the course of the game depending on the current atmosphere levels.
The game presents the player with the goal to make the atmosphere 100% completed, so 0% would mean full sepia and 100% full colour. Thus I passed a value between 0 and 1 into the shader to use for blending. Easy-peasy!
The blending is a regular alpha blending algorithm. This one:
source colour * source alpha + destination colour * (1 - source alpha)
Or, in actual code:
source.rgb * source.a + destination.rgb * (1.0 - source.a)
In our case, source colour would be the full colour (the colour to fade in depending on the atmosphere percentage), and the destination colour would be the full sepia. The alpha value is simply the number between 0 and 1 representing the atmosphere levels. Thus:
result.rgb = full.rgb * atmosphere + sepia.rgb * (1.0 - atmosphere);
And so we get a smooth transition:

Then it was just a matter of applying the same effect to the atmosphere as well as the bar showing how much of it is complete, and presto:

Moving on
Next writeup will be about lighting, including the rim lighting of the planet and all the stuff on it, as well as the revolving sun that gives plants the power to grow as it shines upon them each new day on the very small world! ๐ See you around soon!