Creating An Effective Sun
This is an archived blog entry from 2009 and likely holds little relevance.
Developing the sun was suprisingly easy. The most essential thing to bare in mind is that it is a directional light slowly rotating it’s position around the Z-Axis a distance away from the origin at a given speed. With this, you can guess what the parameters from the sun would be:
- Distance (radius of circle which sun moves along)
- Speed (Rate at which sun moves)
- Starting position/time of day
The sun is expected to be in a certain position depending on the time of day. At 12:00 PM, the sun would be at it’s highest position, in our case, 90 degrees along it’s route. We therefore need to convert seconds into degrees, which is achieved with the following equation:
degrees = (seconds * 360) / 86400;
Note that there are 86,400 seconds in a day (60 seconds per minute, 60 minutes per hour, thus 60 * 60 = 3600, * 24 hours = 86,400 seconds). Because both ranges of values (0 – 360, 0 – 86,400) start from 0, we can strip out the minimum range values from the equation leaving the simplified version above. If this is new to you, you will see the full equation used to convert ranges later on, when it comes to working out the colour for the sun.
Each frame, the sun’s position, and colour are updated. To do this accurately, the system’s clock comes into play, to give us the time elapsed since the last frame. We use this, and our speed of time variable, to work out how much to adjust the sun’s position. Just apply this new value to the rotation method you use in your choice of graphics Framework. Remember to subtract any previous rotation from this value, otherwise the sun will be picking up speed frame after frame rather than travelling at a constant speed. My implementation handled this when calculating time, as such:
timeDelta = gameTime.ElapsedGameTime.TotalSeconds * speedOfTime – oldTimeDelta;
Where oldTimeDelta is set to timeDelta each frame.
A seperate “timeOfDay” variable is used to keep track of the time by incrementing itself with the timeDelta every frame. If this variable becomes greater or equal to 86,400, then 86,400 is deducted from timeOfDay, keeping itself accurately within the range of seconds in a day. Also a seperate “sunRotation” variable holds the exact amount the sun has rotated in total, and is incremented every frame, and kept within 0 – 360 in the same fashion as “timeOfDay”. This sun position is going to be important with the colour interpolation. “TimeOfDay” itself isn’t critical, but was used during debug to ensure the positioning was correct – it is not absolutely required.
Colour Interpolation
To keep a convincing simulation of the sun, we need to adjust it’s light depending on the time of day. A fresh, yellow-ish colour was chosen for dawn, a full white colour was chosen for noon, an orange colour was used to represent sunset, and night time (dusk) was given a dark blue, to simulate a moonlight. We still wanted the world to be visible during night, so using an absense of colour meant that the world was obviously too dark. The blue gave an effective representation of night time, and is a method used in a number of games.
Interpolation between the colours was actually the hardest part, but nicely, not actually hard. All it boils down to is maths, and the following method:
- Store each “keyframe” colour as a Vector3 (float3) with values between 0.0 – 1.0.
- Set the sun colour to an interpolated colour between the two colours representing the time nearest the current time.
The colours are set to four points during the day – it is dawn when the sun is at 0 degrees, 0 seconds. It is noon when the sun is at 90 degrees, 21,600 seconds. Sunset is when the sun is at 180 degrees, 43,200 seconds, and dusk is at midnight, 270 degrees, 64,800 seconds.
For each stage during the day, we need to know the following values:
- The colour to interpolate FROM
- The colour to interpolate TO
- The MINIMUM value in a range to convert from (degrees)
- The MAXIMUM value in a range to convert from (degrees)
For example, let’s say the sun is between dawn and noon. The “from colour” is set to dawn, the “to colour” is set to noon. The minimum value in the degree’s range is 0, the maximum is 90 degrees. With these values, we can create the interpolated colour using the following calculation:
newValue = ( ( (oldValue – oldMin) * (newMax – newMin) ) / (oldMax – oldMin) ) + oldValue
And in code:
sunColour.R = ( ( (sunRotation – degMin) * (toColour.R – fromColour.R) ) / (degMax – degMin) ) + fromColour.R;
sunColour.G = ( ( (sunRotation – degMin) * (toColour.G – fromColour.G) ) / (degMax – degMin) ) + fromColour.G;
sunColour.B = ( ( (sunRotation – degMin) * (toColour.B – fromColour.B) ) / (degMax – degMin) ) + fromColour.B;
Where sunRotation is the amount the sun has rotated cumulatively.
Just to clarify, if the sun was moving between noon and sunset, then the fromColour would be noon, the toColour would be sunset, the degreeMin would be 90, and the degreeMax would be 180.
The rest of the solution is just passing the colour and sun position to a directional light shader. This assumes you know how to handle this yourself, otherwise you really shouldn’t be jumping into this yet.
General Notes and Conclusion
As it currently stands, although the effects are nice to look at, it could be improved in a number of ways. Firstly, I have not mentioned the sky, which in reality, this would change considerably. The skydome in my scene is a good mid-day blue sky with a few clouds, which does a poor job of simulating sunset, and certainly does a bad job at night time. A hopeful solution to this would be to give the skydome a solid, non-textured surface, and represent the clouds as objects of some sort. The colour of the skydome would then be updated depending on the position of the sun. The sky at night is also missing the moon, which could be set up in much the same way as the sun, although as the sun is controlling the lighting, the moon could simply have to follow a path at the opposite side from the sun.
A vast improvement would be to involve shadows. Seeing large shadows cast from a mountain would be far better to look at than a scene without shadows. Where there is light, there is shadow, which is not currently the case with my scene.
The colours are also not very accurate. The yellow for dawn being the most blatant – it’s just too yellow. The colours were supposed to be very typical whilst also being strong enough to demonstrate that the sun’s colour is interpolating correctly.
Whilst care has been taken to ensure the artist’s use of the sun would be straight-forward, the biggest usability enhancement would be to allow the sun to be placed manually in the scene. This is a simple enhancement – the sun’s position would be set to the intersection between a bounding sphere set to the same radius and origin as the sun (the radius of the theoretical sphere the sun moves along, NOT the sun itself’s size). This uses basic picking techniques to check where the mouse’s “pick ray” intersects the bounding sphere of the world.
When I get around to any of these enhancements, I will be sure to write about it.