Procedural World Generation: Part 2 - Day and Night

As I explained in my last post, I've been playing around with procedural world generation in Unity. The first task was generating some terrain, having done the basics of this I thought it'd be fun to have a day/night cycle.

I came up with a basic system when making the terrain, and was playing with this last weekend, but I've not been able to make any progress I've been happy with so I'm going to explain the existing system in this post.

The first pass of this was pretty simple, a MonoBehaviour script with a reference to my directional light and a day length in minutes variable. Then on update calculated an angle to rotate the light by based on the current time.

var timePeriod = dayLengthInMinutes*60;
var angle = (((Time.time - _startTime)/timePeriod)%1)*360.0f;
this._sun.transform.rotation = Quaternion.Euler(angle, 45, 0);

This happily makes the directional light go round and round! Next up was to vary the directional light colour and ambient light colour according to the time of day. I did this by setting a colour for noon day light and a colour for dawn/dusk colour and then used Color.Lerp between them depending on the angle of the sun (I used 0 to 20, and 160 to 180 as the angles between which I lerped).

So far so unremarkable, what starts to make this system start to look quite a lot better is when you calculate a sky colour (again using a specified day and a specified night colour and lerping between depending on angle) and set the Skybox colour and fog colour to this colour! This time I used 350 to 20 and 160 to 190 to lerp the sky colour so that there was pre-dawn and post-dusk light. I also increased the fog density at dawn and dusk as I liked the effect.

The ambient light colour, fog colour and fog density can all be accessed via the static RenderSettings class, note that alpha has no effect on ambient light, if you don't want it to light as strongly you'll just have to lerp it towards black. To set the skybox's colour you can use RenderSettings again, but you need to know the texture names used in the sky's materials! Here's a very basic C# method to do this, including the magic strings:

void SetSkyBoxColour(Color skyColour) {
    var texture = new Texture2D(1,1);
    texture.SetPixel(0,0, skyColour);
    var skyBoxMaterial =  RenderSettings.skybox;
    skyBoxMaterial.SetTexture("_UpTex", texture);
    skyBoxMaterial.SetTexture("_FrontTex", texture);
    skyBoxMaterial.SetTexture("_BackTex", texture);
    skyBoxMaterial.SetTexture("_DownTex", texture);
    skyBoxMaterial.SetTexture("_LeftTex", texture);
    skyBoxMaterial.SetTexture("_RightTex", texture);

You either need to make sure that a material is assigned in RenderSettings or you can assign a blank one in script.

This generates a pretty nice set of basic effects, but night time is rather dark! So I added another directional light and set a moonlight colour, and set it to be at opposite angle to the sun's angle. This is pretty much everything you can see in the below video, although this also has a Unity Pro effect to have fog in the water, which also has to have it's colour altered to match the time of day otherwise you end up with bright blue glowing water at night. It's worth noting that this uses deferred rendering and soft shadows on the directional lights.

As an extension I tried to approximate light scattering from the sun to make nice directionally red skies at dawn and dusk, but I couldn't get an effect I was happy with and it occurred to me after eight hours that this kind of over the top simulation is what stops me from making more actual games, so I decided to give it a rest. I will share with you the formula for the depth of the atmosphere for an angle from the horizonal axis though.

For R = radius of planet; d = depth of atmosphere; theta = angle from horizontal plane; r = depth of atmosphere in direction theta.

r = -R*sin(theta) + sqrt(R^2 * sin^2(theta) + d^2 + 2Rd)

Which, if d = 1 and R = k*d simplifies to:

r = -k*sin(theta) + sqrt(k^2 * sin^2(theta) + 2k + 1)

If you prefer to use angle from the vertical you just need to replace sin with cos. You can use this on its own to calculate an effect to which mimics how the sky turns from blue to white at the horizons. If I go back to this I'll use this formula in conjunction with a light scattering approximation to make better procedural skyboxes (and actually draw the sun on the skybox too).